Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -368,8 +368,6 @@ csharp_style_throw_expression = true:suggestion
csharp_style_unused_value_expression_statement_preference = discard_variable:silent
csharp_style_unused_value_assignment_preference = discard_variable:suggestion

csharp_style_var_for_built_in_types = false:silent
csharp_style_var_for_built_in_types = never
csharp_style_var_when_type_is_apparent = true:warning
csharp_style_var_elsewhere = false:warning

csharp_prefer_simple_using_statement = false:silent
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -286,3 +286,4 @@ __pycache__/
*.btm.cs
*.odx.cs
*.xsd.cs
src/SharedInfrastructure/Guard.Numeric.cs
12 changes: 5 additions & 7 deletions src/SharedInfrastructure/Guard.Numeric.tt
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@
// Licensed under the Apache License, Version 2.0.

using System;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;

namespace SixLabors
Expand Down Expand Up @@ -34,7 +32,7 @@ for (var i = 0; i < types.Length; i++)
/// <exception cref="ArgumentException">
/// <paramref name="value"/> is greater than the maximum value.
/// </exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[MethodImpl(InliningOptions.ShortMethod)]
public static void MustBeLessThan(<#=T#> value, <#=T#> max, string parameterName)
{
if (value >= max)
Expand All @@ -53,7 +51,7 @@ for (var i = 0; i < types.Length; i++)
/// <exception cref="ArgumentException">
/// <paramref name="value"/> is greater than the maximum value.
/// </exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[MethodImpl(InliningOptions.ShortMethod)]
public static void MustBeLessThanOrEqualTo(<#=T#> value, <#=T#> max, string parameterName)
{
if (value > max)
Expand All @@ -72,7 +70,7 @@ for (var i = 0; i < types.Length; i++)
/// <exception cref="ArgumentException">
/// <paramref name="value"/> is less than the minimum value.
/// </exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[MethodImpl(InliningOptions.ShortMethod)]
public static void MustBeGreaterThan(<#=T#> value, <#=T#> min, string parameterName)
{
if (value <= min)
Expand All @@ -91,7 +89,7 @@ for (var i = 0; i < types.Length; i++)
/// <exception cref="ArgumentException">
/// <paramref name="value"/> is less than the minimum value.
/// </exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[MethodImpl(InliningOptions.ShortMethod)]
public static void MustBeGreaterThanOrEqualTo(<#=T#> value, <#=T#> min, string parameterName)
{
if (value < min)
Expand All @@ -111,7 +109,7 @@ for (var i = 0; i < types.Length; i++)
/// <exception cref="ArgumentException">
/// <paramref name="value"/> is less than the minimum value of greater than the maximum value.
/// </exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[MethodImpl(InliningOptions.ShortMethod)]
public static void MustBeBetweenOrEqualTo(<#=T#> value, <#=T#> min, <#=T#> max, string parameterName)
{
if (value < min || value > max)
Expand Down
216 changes: 13 additions & 203 deletions src/SharedInfrastructure/Guard.cs

Large diffs are not rendered by default.

3 changes: 1 addition & 2 deletions src/SharedInfrastructure/HashCode.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
#pragma warning disable SA1636, SA1600, SA1503, SA1202, SA1101, SA1132, SA1309, SA1520, SA1108, SA1203, SA1028, SA1512, SA1308

// <auto-generated />
// SOURCE: https://github.com/dotnet/corefx/blob/master/src/Common/src/CoreLib/System/HashCode.cs

// Licensed to the .NET Foundation under one or more agreements.
Expand Down
28 changes: 28 additions & 0 deletions src/SharedInfrastructure/InliningOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.

// Uncomment this for verbose profiler results. DO NOT PUSH TO MAIN!
// #define PROFILING
using System.Runtime.CompilerServices;

namespace SixLabors
{
/// <summary>
/// Global inlining options. Helps temporarily disable inlining for better profiler output.
/// </summary>
internal static class InliningOptions
{
#if PROFILING
public const MethodImplOptions HotPath = MethodImplOptions.NoInlining;
public const MethodImplOptions ShortMethod = MethodImplOptions.NoInlining;
#else
#if SUPPORTS_HOTPATH
public const MethodImplOptions HotPath = MethodImplOptions.AggressiveOptimization;

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JimBobSquarePants Possibly dumb quesiton, why are we just using MethodImplOptions.AggressiveOptimization in this case instead of MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization? I've seen them used at the same time quote often around CoreCLR, and I don't think that the optimization flag implies inlining as well - don't we want both those JIT perks for hot paths? 🤔

@JimBobSquarePants JimBobSquarePants Mar 5, 2020

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've only seen them used as flags

MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization

I haven't managed to find any documentation on the new attribute at all, nor could I find the issue that introduced it so I thought I better keep them separate as the original enum defines.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should combine them. @antonfirsov Do you have any insight?

@JimBobSquarePants JimBobSquarePants Mar 5, 2020

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found the original CoreFX issue.

dotnet/runtime#27370

Dunno why I couldn't before.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Sergio0694 Looks like you definitely want to keep them separate. Relevant conversation starts here.

dotnet/runtime#27370 (comment)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JimBobSquarePants I'm not sure what you mean there, what exception message? I mean, I can see the throw methods for the out of range errors with a formatted message already, what are you referring to here?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Sergio0694 I suppose inlining is not the only magic provided by AggressiveOptimization, but you're right Guard methods are so short, there really isn't anything to agressively optimize, and if you say it might bring other risks, let's leave it out for now!

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not 100% sure since that conversation was really long and I could just read up to some point, but apparently there were a lot of various implications depending on the scenario. The main takeaway I got was this:

AggressiveInlining just pushes the JIT to inline the (marked) callee into its caller
AggressiveOptimization pushes the JIT to try to inline callees from their (marked) caller (the one with the attribute). It also suggests to go straight to the best JIT tiering possible.

In out case, the Guard APIs don't call functions that could benefit from inlinig, in fact all the helpers should not be inlined, and are marked as such. We only care about those Guard APIs themselves being inlined into their callers, so yeah I'd say (at least for now) the safe bet would be to just use AggressiveInlining 👍

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Sergio0694 What I mean is that the unit tests expect a different value for the message than the actual output. Shall I just update the test to match?

https://github.com/SixLabors/SharedInfrastructure/pull/7/files#diff-f73475a089d950d0e0a672480a854ebcR45

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah my bad, I didn't consider the fact the tests were also matching the exact error message as well. Sure thing, yeah that would be perfect then 👍

#else
public const MethodImplOptions HotPath = MethodImplOptions.AggressiveInlining;
#endif
public const MethodImplOptions ShortMethod = MethodImplOptions.AggressiveInlining;
#endif
public const MethodImplOptions ColdPath = MethodImplOptions.NoInlining;
}
}
1 change: 1 addition & 0 deletions src/SharedInfrastructure/SharedInfrastructure.projitems
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
<Compile Include="$(MSBuildThisFileDirectory)ExcludeFromCodeCoverageAttribute.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Guard.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HashCode.cs" />
<Compile Include="$(MSBuildThisFileDirectory)InliningOptions.cs" />
<Compile Include="$(MSBuildThisFileDirectory)MathF.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ThrowHelper.cs" />
</ItemGroup>
Expand Down
19 changes: 9 additions & 10 deletions src/SharedInfrastructure/SharedInfrastructure.shproj
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Label="Globals">
<ProjectGuid>68a8cc40-6aed-4e96-b524-31b1158fdeea</ProjectGuid>
<MinimumVisualStudioVersion>14.0</MinimumVisualStudioVersion>
</PropertyGroup>
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.Default.props" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.props" />
<PropertyGroup />
<Import Project="SharedInfrastructure.projitems" Label="Shared" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.CSharp.targets" />
<PropertyGroup Label="Globals">
<ProjectGuid>68a8cc40-6aed-4e96-b524-31b1158fdeea</ProjectGuid>
<MinimumVisualStudioVersion>14.0</MinimumVisualStudioVersion>
</PropertyGroup>
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.Default.props" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.props" />
<Import Project="SharedInfrastructure.projitems" Label="Shared" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.CSharp.targets" />
</Project>
6 changes: 3 additions & 3 deletions src/SharedInfrastructure/ThrowHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,17 +87,17 @@ public static void ThrowArgumentOutOfRangeExceptionForMustBeBetweenOrEqualTo<T>(
[MethodImpl(MethodImplOptions.NoInlining)]
public static void ThrowArgumentOutOfRangeExceptionForMustBeSizedAtLeast(int minLength, string parameterName)
{
ThrowArgumentException($"Span-s must be at least of length {minLength}!", parameterName);
ThrowArgumentException($"Spans must be at least of length {minLength}!", parameterName);
}

/// <summary>
/// Throws a new <see cref="ArgumentException"/>.
/// </summary>
/// <param name="name">The argument name.</param>
/// <param name="message">The message to include in the exception.</param>
/// <param name="name">The argument name.</param>
/// <exception cref="ArgumentException">Thrown with <paramref name="message"/> and <paramref name="name"/>.</exception>
[MethodImpl(MethodImplOptions.NoInlining)]
public static void ThrowArgumentException(string name, string message)
public static void ThrowArgumentException(string message, string name)
{
throw new ArgumentException(message, name);
}
Expand Down
40 changes: 14 additions & 26 deletions tests/SharedInfrastructure.Tests/GuardTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public void NotNull_WhenNull_Throws()
[Fact]
public void NotNull_WhenNotNull()
{
Foo foo = new Foo();
var foo = new Foo();
Guard.NotNull(foo, nameof(foo));
}

Expand Down Expand Up @@ -133,10 +133,8 @@ public void MustBeLessThan_IsLess_ThrowsNoException()
[InlineData(1, 1)]
public void MustBeLessThan_IsGreaterOrEqual_ThrowsNoException(int value, int max)
{
ArgumentOutOfRangeException exception = Assert.Throws<ArgumentOutOfRangeException>(() =>
{
Guard.MustBeLessThan(value, max, "myParamName");
});
ArgumentOutOfRangeException exception =
Assert.Throws<ArgumentOutOfRangeException>(() => Guard.MustBeLessThan(value, max, "myParamName"));

Assert.Equal("myParamName", exception.ParamName);
Assert.Contains($"Value {value} must be less than {max}.", exception.Message);
Expand All @@ -153,10 +151,8 @@ public void MustBeLessThanOrEqualTo_IsLessOrEqual_ThrowsNoException(int value, i
[Fact]
public void MustBeLessThanOrEqualTo_IsGreater_ThrowsNoException()
{
ArgumentOutOfRangeException exception = Assert.Throws<ArgumentOutOfRangeException>(() =>
{
Guard.MustBeLessThanOrEqualTo(2, 1, "myParamName");
});
ArgumentOutOfRangeException exception =
Assert.Throws<ArgumentOutOfRangeException>(() => Guard.MustBeLessThanOrEqualTo(2, 1, "myParamName"));

Assert.Equal("myParamName", exception.ParamName);
Assert.Contains($"Value 2 must be less than or equal to 1.", exception.Message);
Expand All @@ -173,10 +169,8 @@ public void MustBeGreaterThan_IsGreater_ThrowsNoException()
[InlineData(1, 1)]
public void MustBeGreaterThan_IsLessOrEqual_ThrowsNoException(int value, int min)
{
ArgumentOutOfRangeException exception = Assert.Throws<ArgumentOutOfRangeException>(() =>
{
Guard.MustBeGreaterThan(value, min, "myParamName");
});
ArgumentOutOfRangeException exception =
Assert.Throws<ArgumentOutOfRangeException>(() => Guard.MustBeGreaterThan(value, min, "myParamName"));

Assert.Equal("myParamName", exception.ParamName);
Assert.Contains($"Value {value} must be greater than {min}.", exception.Message);
Expand All @@ -193,10 +187,8 @@ public void MustBeGreaterThanOrEqualTo_IsGreaterOrEqual_ThrowsNoException(int va
[Fact]
public void MustBeGreaterThanOrEqualTo_IsLess_ThrowsNoException()
{
ArgumentOutOfRangeException exception = Assert.Throws<ArgumentOutOfRangeException>(() =>
{
Guard.MustBeGreaterThanOrEqualTo(1, 2, "myParamName");
});
ArgumentOutOfRangeException exception =
Assert.Throws<ArgumentOutOfRangeException>(() => Guard.MustBeGreaterThanOrEqualTo(1, 2, "myParamName"));

Assert.Equal("myParamName", exception.ParamName);
Assert.Contains($"Value 1 must be greater than or equal to 2.", exception.Message);
Expand All @@ -216,10 +208,8 @@ public void MustBeBetweenOrEqualTo_IsBetweenOrEqual_ThrowsNoException(int value,
[InlineData(4, 1, 3)]
public void MustBeBetweenOrEqualTo_IsLessOrGreater_ThrowsNoException(int value, int min, int max)
{
ArgumentOutOfRangeException exception = Assert.Throws<ArgumentOutOfRangeException>(() =>
{
Guard.MustBeBetweenOrEqualTo(value, min, max, "myParamName");
});
ArgumentOutOfRangeException exception =
Assert.Throws<ArgumentOutOfRangeException>(() => Guard.MustBeBetweenOrEqualTo(value, min, max, "myParamName"));

Assert.Equal("myParamName", exception.ParamName);
Assert.Contains($"Value {value} must be greater than or equal to {min} and less than or equal to {max}.", exception.Message);
Expand All @@ -236,13 +226,11 @@ public void MustBeSizedAtLeast_Array_LengthIsGreaterOrEqual_ThrowsNoException(in
[Fact]
public void MustBeSizedAtLeast_Array_LengthIsLess_ThrowsException()
{
ArgumentException exception = Assert.Throws<ArgumentException>(() =>
{
Guard.MustBeSizedAtLeast<int>(new int[] { 1, 2 }, 3, "myParamName");
});
ArgumentException exception =
Assert.Throws<ArgumentException>(() => Guard.MustBeSizedAtLeast<int>(new int[] { 1, 2 }, 3, "myParamName"));

Assert.Equal("myParamName", exception.ParamName);
Assert.Contains("The size must be at least 3", exception.Message);
Assert.Contains("Spans must be at least of length 3", exception.Message);
}
}
}
62 changes: 32 additions & 30 deletions tests/SharedInfrastructure.Tests/SharedInfrastructure.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,21 @@

<!--
https://apisof.net/
+===================+=======+==========+=====================+=============+=================+====================+==============+
| SUPPORTS | MATHF | HASHCODE | EXTENDED_INTRINSICS | SPAN_STREAM | ENCODING_STRING | RUNTIME_INTRINSICS | CODECOVERAGE |
+===================+=======+==========+=====================+=============+=================+====================+==============+
| netcoreapp3.1 | Y | Y | Y | Y | Y | Y | Y |
| netcoreapp2.1 | Y | Y | Y | Y | Y | N | Y |
| netcoreapp2.0 | Y | N | N | N | N | N | Y |
| netstandard2.1 | Y | Y | N | Y | Y | N | Y |
| netstandard2.0 | N | N | N | N | N | N | Y |
| netstandard1.3 | N | N | N | N | N | N | N |
| net472 | N | N | Y | N | N | N | Y |
+===================+=======+==========+=====================+=============+=================+====================+==============+
+===================+=======+==========+=====================+=============+=================+====================+==============+=========|
| SUPPORTS | MATHF | HASHCODE | EXTENDED_INTRINSICS | SPAN_STREAM | ENCODING_STRING | RUNTIME_INTRINSICS | CODECOVERAGE | HOTPATH |
+===================+=======+==========+=====================+=============+=================+====================+==============+=========|
| netcoreapp3.1 | Y | Y | Y | Y | Y | Y | Y | Y |
| netcoreapp2.1 | Y | Y | Y | Y | Y | N | Y | N |
| netcoreapp2.0 | Y | N | N | N | N | N | Y | N |
| netstandard2.1 | Y | Y | N | Y | Y | N | Y | N |
| netstandard2.0 | N | N | N | N | N | N | Y | N |
| netstandard1.3 | N | N | N | N | N | N | N | N |
| net472 | N | N | Y | N | N | N | Y | N |
+===================+=======+==========+=====================+=============+=================+====================+==============+=========|
-->

<PropertyGroup Condition="'$(TargetFramework)' == 'netcoreapp3.1'">
<DefineConstants>$(DefineConstants);SUPPORTS_MATHF;SUPPORTS_HASHCODE;SUPPORTS_EXTENDED_INTRINSICS;SUPPORTS_SPAN_STREAM;SUPPORTS_ENCODING_STRING;SUPPORTS_RUNTIME_INTRINSICS;SUPPORTS_CODECOVERAGE</DefineConstants>
<DefineConstants>$(DefineConstants);SUPPORTS_MATHF;SUPPORTS_HASHCODE;SUPPORTS_EXTENDED_INTRINSICS;SUPPORTS_SPAN_STREAM;SUPPORTS_ENCODING_STRING;SUPPORTS_RUNTIME_INTRINSICS;SUPPORTS_CODECOVERAGE;SUPPORTS_HOTPATH</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)' == 'netcoreapp2.1'">
<DefineConstants>$(DefineConstants);SUPPORTS_MATHF;SUPPORTS_HASHCODE;SUPPORTS_EXTENDED_INTRINSICS;SUPPORTS_SPAN_STREAM;SUPPORTS_ENCODING_STRING;SUPPORTS_CODECOVERAGE</DefineConstants>
Expand Down Expand Up @@ -52,14 +52,10 @@
<PackageReference Include="System.Buffers" Version="4.5.0" />
<PackageReference Include="System.Memory" Version="4.5.3" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="1.2.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>

<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
<PackageReference Include="coverlet.collector" Version="1.2.0" />
<AdditionalFiles Include="..\..\stylecop.json" />
</ItemGroup>

Expand All @@ -78,19 +74,25 @@
<AutoGen>True</AutoGen>
<DependentUpon>Guard.Numeric.tt</DependentUpon>
</Compile>
</ItemGroup>

</ItemGroup>
<ItemGroup>
<Service Include="{508349b6-6b84-4df5-91f0-309beebad82d}" />
</ItemGroup>

<Import Project="..\..\src\SharedInfrastructure\SharedInfrastructure.projitems" Label="Shared" />

<ItemGroup>
<None Update="C:\Users\Sergio\Documents\GitHub\ImageSharp\shared-infrastructure\src\SharedInfrastructure\Guard.Numeric.tt">
<None Update="..\..\src\SharedInfrastructure\Guard.Numeric.tt">
<Generator>TextTemplatingFileGenerator</Generator>
<LastGenOutput>Guard.Numeric.cs</LastGenOutput>
</None>
</ItemGroup>
</ItemGroup>

<ItemGroup>
<Service Include="{508349b6-6b84-4df5-91f0-309beebad82d}" />
</ItemGroup>

<Import Project="..\..\src\SharedInfrastructure\SharedInfrastructure.projitems" Label="Shared" />

<Import Project="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v$(VisualStudioVersion)\TextTemplating\Microsoft.TextTemplating.targets" />

<PropertyGroup>
<TransformOnBuild>true</TransformOnBuild>
<TransformOutOfDateOnly>false</TransformOutOfDateOnly>
</PropertyGroup>
</Project>