Skip to content

Commit

Permalink
Adding target framework intrinsic functions (#5234)
Browse files Browse the repository at this point in the history
Implementing the first few intrinsic properties from #5171
  • Loading branch information
sfoslund authored Jun 10, 2020
1 parent 5292fed commit 51e8dd2
Show file tree
Hide file tree
Showing 22 changed files with 256 additions and 37 deletions.
1 change: 1 addition & 0 deletions eng/Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
<PackageReference Update="Microsoft.VisualStudio.Setup.Configuration.Interop" Version="1.16.30" />
<PackageReference Update="Microsoft.Win32.Registry" Version="4.3.0" />
<PackageReference Update="NuGet.Build.Tasks" Version="$(NuGetBuildTasksVersion)" />
<PackageReference Update="NuGet.Frameworks" Version="$(NuGetBuildTasksVersion)" />
<PackageReference Update="PdbGit" Version="3.0.41" />
<PackageReference Update="Shouldly" Version="3.0.0" />
<PackageReference Update="SourceLink.Create.CommandLine" Version="2.1.2" />
Expand Down
101 changes: 66 additions & 35 deletions src/Build.UnitTests/Evaluation/Expander_Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2800,34 +2800,23 @@ public void PropertyFunctionVersionComparisonsFailsWithInvalidArguments(string b
var expander = new Expander<ProjectPropertyInstance, ProjectItemInstance>(pg, FileSystems.Default);
string expectedMessage = ResourceUtilities.GetResourceString("InvalidVersionFormat");

AssertThrows($"$([MSBuild]::VersionGreaterThan('{badVersion}', '1.0.0'))");
AssertThrows($"$([MSBuild]::VersionGreaterThan('1.0.0', '{badVersion}'))");
AssertThrows(expander, $"$([MSBuild]::VersionGreaterThan('{badVersion}', '1.0.0'))", expectedMessage);
AssertThrows(expander, $"$([MSBuild]::VersionGreaterThan('1.0.0', '{badVersion}'))", expectedMessage);

AssertThrows($"$([MSBuild]::VersionGreaterThanOrEquals('{badVersion}', '1.0.0'))");
AssertThrows($"$([MSBuild]::VersionGreaterThanOrEquals('1.0.0', '{badVersion}'))");
AssertThrows(expander, $"$([MSBuild]::VersionGreaterThanOrEquals('{badVersion}', '1.0.0'))", expectedMessage);
AssertThrows(expander, $"$([MSBuild]::VersionGreaterThanOrEquals('1.0.0', '{badVersion}'))", expectedMessage);

AssertThrows($"$([MSBuild]::VersionLessThan('{badVersion}', '1.0.0'))");
AssertThrows($"$([MSBuild]::VersionLessThan('1.0.0', '{badVersion}'))");
AssertThrows(expander, $"$([MSBuild]::VersionLessThan('{badVersion}', '1.0.0'))", expectedMessage);
AssertThrows(expander, $"$([MSBuild]::VersionLessThan('1.0.0', '{badVersion}'))", expectedMessage);

AssertThrows($"$([MSBuild]::VersionLessThanOrEquals('{badVersion}', '1.0.0'))");
AssertThrows($"$([MSBuild]::VersionLessThanOrEquals('1.0.0', '{badVersion}'))");
AssertThrows(expander, $"$([MSBuild]::VersionLessThanOrEquals('{badVersion}', '1.0.0'))", expectedMessage);
AssertThrows(expander, $"$([MSBuild]::VersionLessThanOrEquals('1.0.0', '{badVersion}'))", expectedMessage);

AssertThrows($"$([MSBuild]::VersionEquals('{badVersion}', '1.0.0'))");
AssertThrows($"$([MSBuild]::VersionEquals('1.0.0', '{badVersion}'))");
AssertThrows(expander, $"$([MSBuild]::VersionEquals('{badVersion}', '1.0.0'))", expectedMessage);
AssertThrows(expander, $"$([MSBuild]::VersionEquals('1.0.0', '{badVersion}'))", expectedMessage);

AssertThrows($"$([MSBuild]::VersionNotEquals('{badVersion}', '1.0.0'))");
AssertThrows($"$([MSBuild]::VersionNotEquals('1.0.0', '{badVersion}'))");

void AssertThrows(string expression)
{
var ex = Assert.Throws<InvalidProjectFileException>(
() => expander.ExpandPropertiesLeaveTypedAndEscaped(
expression,
ExpanderOptions.ExpandProperties,
MockElementLocation.Instance));

Assert.Contains(expectedMessage, ex.Message);
}
AssertThrows(expander, $"$([MSBuild]::VersionNotEquals('{badVersion}', '1.0.0'))", expectedMessage);
AssertThrows(expander, $"$([MSBuild]::VersionNotEquals('1.0.0', '{badVersion}'))", expectedMessage);
}

[Theory]
Expand All @@ -2843,22 +2832,64 @@ public void PropertyFunctionVersionComparisons(string a, string b, int expectedS
var pg = new PropertyDictionary<ProjectPropertyInstance>();
var expander = new Expander<ProjectPropertyInstance, ProjectItemInstance>(pg, FileSystems.Default);

AssertSuccess(expectedSign > 0, $"$([MSBuild]::VersionGreaterThan('{a}', '{b}'))");
AssertSuccess(expectedSign >= 0, $"$([MSBuild]::VersionGreaterThanOrEquals('{a}', '{b}'))");
AssertSuccess(expectedSign < 0, $"$([MSBuild]::VersionLessThan('{a}', '{b}'))");
AssertSuccess(expectedSign <= 0, $"$([MSBuild]::VersionLessThanOrEquals('{a}', '{b}'))");
AssertSuccess(expectedSign == 0, $"$([MSBuild]::VersionEquals('{a}', '{b}'))");
AssertSuccess(expectedSign != 0, $"$([MSBuild]::VersionNotEquals('{a}', '{b}'))");
AssertSuccess(expander, expectedSign > 0, $"$([MSBuild]::VersionGreaterThan('{a}', '{b}'))");
AssertSuccess(expander, expectedSign >= 0, $"$([MSBuild]::VersionGreaterThanOrEquals('{a}', '{b}'))");
AssertSuccess(expander, expectedSign < 0, $"$([MSBuild]::VersionLessThan('{a}', '{b}'))");
AssertSuccess(expander, expectedSign <= 0, $"$([MSBuild]::VersionLessThanOrEquals('{a}', '{b}'))");
AssertSuccess(expander, expectedSign == 0, $"$([MSBuild]::VersionEquals('{a}', '{b}'))");
AssertSuccess(expander, expectedSign != 0, $"$([MSBuild]::VersionNotEquals('{a}', '{b}'))");
}

void AssertSuccess(bool expected, string expression)
{
bool actual = (bool)expander.ExpandPropertiesLeaveTypedAndEscaped(
[Theory]
[InlineData("net45", ".NETFramework", "4.5")]
[InlineData("netcoreapp3.1", ".NETCoreApp", "3.1")]
[InlineData("netstandard2.1", ".NETStandard", "2.1")]
[InlineData("foo", "Unsupported", "0.0")]
public void PropertyFunctionTargetFrameworkParsing(string tfm, string expectedIdentifier, string expectedVersion)
{
var pg = new PropertyDictionary<ProjectPropertyInstance>();
var expander = new Expander<ProjectPropertyInstance, ProjectItemInstance>(pg, FileSystems.Default);

AssertSuccess(expander, expectedIdentifier, $"$([MSBuild]::GetTargetFrameworkIdentifier('{tfm}'))");
AssertSuccess(expander, expectedVersion, $"$([MSBuild]::GetTargetFrameworkVersion('{tfm}'))");
}

[Theory]
[InlineData("net5.0", "net5.0", true)]
[InlineData("net45", "net46", false)]
[InlineData("net46", "net45", true)]
[InlineData("netcoreapp3.1", "netcoreapp1.0", true)]
[InlineData("netstandard1.6", "netstandard2.1", false)]
[InlineData("netcoreapp3.0", "netstandard2.1", true)]
[InlineData("net461", "netstandard1.0", true)]
[InlineData("foo", "netstandard1.0", false)]
public void PropertyFunctionTargetFrameworkComparisons(string tfm1, string tfm2, bool expectedFrameworkCompatible)
{
var pg = new PropertyDictionary<ProjectPropertyInstance>();
var expander = new Expander<ProjectPropertyInstance, ProjectItemInstance>(pg, FileSystems.Default);

AssertSuccess(expander, expectedFrameworkCompatible, $"$([MSBuild]::IsTargetFrameworkCompatible('{tfm1}', '{tfm2}'))");
}

private void AssertThrows(Expander<ProjectPropertyInstance, ProjectItemInstance> expander, string expression, string expectedMessage)
{
var ex = Assert.Throws<InvalidProjectFileException>(
() => expander.ExpandPropertiesLeaveTypedAndEscaped(
expression,
ExpanderOptions.ExpandProperties,
MockElementLocation.Instance);
MockElementLocation.Instance));

Assert.Equal(expected, actual);
}
Assert.Contains(expectedMessage, ex.Message);
}

private void AssertSuccess(Expander<ProjectPropertyInstance, ProjectItemInstance> expander, object expected, string expression)
{
var actual = expander.ExpandPropertiesLeaveTypedAndEscaped(
expression,
ExpanderOptions.ExpandProperties,
MockElementLocation.Instance);

Assert.Equal(expected, actual);
}

/// <summary>
Expand Down
5 changes: 5 additions & 0 deletions src/Build.UnitTests/Microsoft.Build.Engine.UnitTests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,17 @@

<!-- Define a constant so we can skip tests that require MSBuildTaskHost -->
<DefineConstants Condition="'$(MSBuildRuntimeType)' == 'Core' or '$(MonoBuild)' == 'true'">$(DefineConstants);NO_MSBUILDTASKHOST</DefineConstants>

<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="System.Collections.Concurrent" />
<PackageReference Include="Shouldly" />
<PackageReference Include="Microsoft.CodeAnalysis.Build.Tasks" />
<PackageReference Include="NuGet.Frameworks" >
<PrivateAssets>all</PrivateAssets>
</PackageReference>

<ProjectReference Include="..\Build\Microsoft.Build.csproj" />
<ProjectReference Include="..\Framework\Microsoft.Build.Framework.csproj" />
Expand Down
24 changes: 24 additions & 0 deletions src/Build/Evaluation/Expander.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3916,6 +3916,30 @@ private bool TryExecuteWellKnownFunction(out object returnVal, object objectInst
return true;
}
}
else if (string.Equals(_methodMethodName, nameof(IntrinsicFunctions.GetTargetFrameworkIdentifier), StringComparison.OrdinalIgnoreCase))
{
if (TryGetArg(args, out string arg0))
{
returnVal = IntrinsicFunctions.GetTargetFrameworkIdentifier(arg0);
return true;
}
}
else if (string.Equals(_methodMethodName, nameof(IntrinsicFunctions.GetTargetFrameworkVersion), StringComparison.OrdinalIgnoreCase))
{
if (TryGetArg(args, out string arg0))
{
returnVal = IntrinsicFunctions.GetTargetFrameworkVersion(arg0);
return true;
}
}
else if (string.Equals(_methodMethodName, nameof(IntrinsicFunctions.IsTargetFrameworkCompatible), StringComparison.OrdinalIgnoreCase))
{
if (TryGetArgs(args, out string arg0, out string arg1))
{
returnVal = IntrinsicFunctions.IsTargetFrameworkCompatible(arg0, arg1);
return true;
}
}
}
else if (_receiverType == typeof(Path))
{
Expand Down
19 changes: 17 additions & 2 deletions src/Build/Evaluation/IntrinsicFunctions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;

Expand All @@ -32,6 +30,8 @@ internal static class IntrinsicFunctions
private static readonly Lazy<Regex> RegistrySdkRegex = new Lazy<Regex>(() => new Regex(@"^HKEY_LOCAL_MACHINE\\Software\\Microsoft\\Microsoft SDKs\\Windows\\v(\d+\.\d+)$", RegexOptions.IgnoreCase));
#endif // FEATURE_WIN32_REGISTRY

private static readonly Lazy<NuGetFrameworkWrapper> NuGetFramework = new Lazy<NuGetFrameworkWrapper>(() => new NuGetFrameworkWrapper());

/// <summary>
/// Add two doubles
/// </summary>
Expand Down Expand Up @@ -480,6 +480,21 @@ internal static bool VersionLessThanOrEquals(string a, string b)
return SimpleVersion.Parse(a) <= SimpleVersion.Parse(b);
}

internal static string GetTargetFrameworkIdentifier(string tfm)
{
return NuGetFramework.Value.GetTargetFrameworkIdentifier(tfm);
}

internal static string GetTargetFrameworkVersion(string tfm)
{
return NuGetFramework.Value.GetTargetFrameworkVersion(tfm);
}

internal static bool IsTargetFrameworkCompatible(string target, string candidate)
{
return NuGetFramework.Value.IsCompatible(target, candidate);
}

public static string GetCurrentToolsDirectory()
{
return BuildEnvironmentHelper.Instance.CurrentMSBuildToolsDirectory;
Expand Down
1 change: 1 addition & 0 deletions src/Build/Microsoft.Build.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@
<Compile Include="BackEnd\BuildManager\CacheAggregator.cs" />
<Compile Include="BackEnd\Components\Caching\ConfigCacheWithOverride.cs" />
<Compile Include="BackEnd\Components\Caching\ResultsCacheWithOverride.cs" />
<Compile Include="Utilities\NuGetFrameworkWrapper.cs" />
<Compile Include="ObjectModelRemoting\ConstructionObjectLinks\ProjectUsingTaskParameterElementLink.cs" />
<Compile Include="ObjectModelRemoting\ExternalProjectsProvider.cs" />
<Compile Include="ObjectModelRemoting\LinkedObjectFactory.cs" />
Expand Down
3 changes: 3 additions & 0 deletions src/Build/Resources/Strings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -1825,4 +1825,7 @@ Utilization: {0} Average Utilization: {1:###.0}</value>
<data name="PropertyAssignment" xml:space="preserve">
<value>Property initial value: $({0})="{1}" Source: {2}</value>
</data>
<data name="NuGetAssemblyNotFound" xml:space="preserve">
<value>A required NuGet assembly was not found. Expected Path: {0}</value>
</data>
</root>
5 changes: 5 additions & 0 deletions src/Build/Resources/xlf/Strings.cs.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Build/Resources/xlf/Strings.de.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Build/Resources/xlf/Strings.en.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Build/Resources/xlf/Strings.es.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Build/Resources/xlf/Strings.fr.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Build/Resources/xlf/Strings.it.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Build/Resources/xlf/Strings.ja.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Build/Resources/xlf/Strings.ko.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 51e8dd2

Please sign in to comment.