Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Align TargetFramework pkg with NuGet Task #12041

Merged
merged 1 commit into from
Dec 30, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
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
2 changes: 2 additions & 0 deletions src/Common/Internal/AssemblyResolver.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#nullable disable

using System;
using System.Diagnostics;
using System.IO;
Expand Down
2 changes: 2 additions & 0 deletions src/Common/Internal/BuildTask.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#nullable disable

using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using System;
Expand Down
12 changes: 4 additions & 8 deletions src/Microsoft.DotNet.Build.Tasks.TargetFramework/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Supports copying to additional paths based on which `TargetFramework` among `Tar

- BinPlaceItem
- Typically computed by the BinPlacing targets to determine what assets to binplace.
- Identity: source of file to binplace. For example: the built output dll, pdb, content files, etc.
- Identity: source of file to binplace. For example: the built output dll, pdb, content files, etc.
- Metadata:
- TargetPath: when specified can indicate the relative path, including filename, to place the item.

Expand All @@ -26,14 +26,10 @@ Supports copying to additional paths based on which `TargetFramework` among `Tar
- RefPath: directory to copy `BinPlaceItem`s when `BinPlaceRef` is set to true.
- RuntimePath: directory to copy `BinPlaceItem`s when `BinPlaceRuntime` is set to true.
- TestPath: directory to copy `BinPlaceItem`s when `BinPlaceTest` is set to true.
- PackageFileNativePath: directory to write props file containing `BinPlaceItem`s when `BinPlaceNative` is set to true.
- PackageFileRefPath: directory to write props file containing `BinPlaceItem`s when `BinPlaceRef` is set to true.
- PackageFileRuntimePath: directory to write props file containing `BinPlaceItem`s when `BinPlaceRuntime` is set to true.
- ItemName: An item name to use instead of `BinPlaceItem` for the source of items for this `BinPlaceTargetFramework`.
- SetProperties: Name=Value pairs of properties that should be set.

## BinPlacing Properties
- BinPlaceNative: When set to true `BinPlaceItem`s are copied to the `NativePath` of active `BinPlaceTargetFramework`s. Props are written to the `PackageFileNativePath` directory.
- BinPlaceRef: When set to true `BinPlaceItem`s are copied to the `RefPath` of active `BinPlaceTargetFramework`s. Props are written to the `PackageFileRefPath` directory.
- BinPlaceRuntime: When set to true `BinPlaceItem`s are copied to the `RuntimePath` of active `BinPlaceTargetFramework`s. Props are written to the `PackageFileRuntimePath` directory.
- BinPlaceNative: When set to true `BinPlaceItem`s are copied to the `NativePath` of active `BinPlaceTargetFramework`s.
- BinPlaceRef: When set to true `BinPlaceItem`s are copied to the `RefPath` of active `BinPlaceTargetFramework`s.
- BinPlaceRuntime: When set to true `BinPlaceItem`s are copied to the `RuntimePath` of active `BinPlaceTargetFramework`s.
- BinPlaceTest: When set to true `BinPlaceItem`s are copied to the `TestPath` of active `BinPlaceTargetFramework`s.
Original file line number Diff line number Diff line change
@@ -1,87 +1,134 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.Build.Framework;
// Keep in sync with https://raw.githubusercontent.com/NuGet/NuGet.Client/dccbd304b11103e08b97abf4cf4bcc1499d9235a/src/NuGet.Core/NuGet.Frameworks/NuGetFrameworkUtility.cs

using System;
using System.Collections.Generic;
using System.IO;
using System.Globalization;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using NuGet.Common;
using NuGet.Frameworks;

namespace Microsoft.DotNet.Build.Tasks.TargetFramework
{
public class ChooseBestP2PTargetFrameworkTask : BuildTask
{
[Required]
public ITaskItem[] ProjectReferencesWithTargetFrameworks { get; set; }
private const string NEAREST_TARGET_FRAMEWORK = "NearestTargetFramework";
private const string TARGET_FRAMEWORKS = "TargetFrameworks";

[Required]
public string RuntimeGraph { get; set; }
public string? RuntimeGraph { get; set; }

/// <summary>
/// The current project's target framework.
/// </summary>
[Required]
public string TargetFramework { get; set; }
public string? CurrentProjectTargetFramework { get; set; }

/// <summary>
/// Optional TargetPlatformMoniker
/// </summary>
public string? CurrentProjectTargetPlatform { get; set; }

public bool OmitIncompatibleProjectReferences { get; set; }

/// <summary>
/// The project references for property lookup.
/// </summary>
public ITaskItem[]? AnnotatedProjectReferences { get; set; }

/// <summary>
/// The project references with assigned properties.
/// </summary>
[Output]
public ITaskItem[] AnnotatedProjectReferencesWithSetTargetFramework { get; set; }
public ITaskItem[]? AssignedProjects { get; set; }

public override bool Execute()
{
var annotatedProjectReferencesWithSetTargetFramework = new List<ITaskItem>(ProjectReferencesWithTargetFrameworks.Length);
var targetFrameworkResolver = new TargetFrameworkResolver(RuntimeGraph);
if (AnnotatedProjectReferences == null)
{
return !Log.HasLoggedErrors;
}

for (int i = 0; i < ProjectReferencesWithTargetFrameworks.Length; i++)
// validate current project framework
string errorMessage = string.Format(CultureInfo.CurrentCulture, "The project target framework '{0}' is not a supported target framework.", $"TargetFrameworkMoniker: {CurrentProjectTargetFramework}, TargetPlatformMoniker:{CurrentProjectTargetPlatform}");
if (!TryParseFramework(CurrentProjectTargetFramework!, CurrentProjectTargetPlatform, errorMessage, Log, out var projectNuGetFramework))
{
ITaskItem projectReference = ProjectReferencesWithTargetFrameworks[i];
string targetFrameworksValue = projectReference.GetMetadata("TargetFrameworks");
return false;
}

TargetFrameworkResolver targetFrameworkResolver = TargetFrameworkResolver.CreateOrGet(RuntimeGraph!);
List<ITaskItem> assignedProjects = new(AnnotatedProjectReferences.Length);

// Allow referencing projects with TargetFrameworks explicitely cleared out, i.e. Microsoft.Build.Traversal.
if (!string.IsNullOrWhiteSpace(targetFrameworksValue))
foreach (ITaskItem annotatedProjectReference in AnnotatedProjectReferences)
{
ITaskItem? assignedProject = AssignNearestFrameworkForSingleReference(annotatedProjectReference, projectNuGetFramework, targetFrameworkResolver);
if (assignedProject != null)
{
string[] targetFrameworks = targetFrameworksValue.Split(';');

string referringTargetFramework = projectReference.GetMetadata("ReferringTargetFramework");
if (string.IsNullOrWhiteSpace(referringTargetFramework))
{
referringTargetFramework = TargetFramework;
}

string bestTargetFramework = targetFrameworkResolver.GetBestSupportedTargetFramework(targetFrameworks, referringTargetFramework);
if (bestTargetFramework == null)
{
if (OmitIncompatibleProjectReferences)
{
continue;
}
Log.LogError($"Not able to find a compatible supported target framework for {referringTargetFramework} in Project {Path.GetFileName(projectReference.ItemSpec)}. The Supported Configurations are {string.Join(", ", targetFrameworks)}");
}

// Mimic msbuild's Common.targets behavior: https://github.com/dotnet/msbuild/blob/3c8fb11a080a5a15199df44fabf042a22e9ad4da/src/Tasks/Microsoft.Common.CurrentVersion.targets#L1842-L1853
if (projectReference.GetMetadata("HasSingleTargetFramework") != "true")
{
projectReference.SetMetadata("SetTargetFramework", "TargetFramework=" + bestTargetFramework);
}
else
{
// If the project has a single TargetFramework, we need to Undefine TargetFramework to avoid another project evaluation.
string undefineProperties = projectReference.GetMetadata("UndefineProperties");
projectReference.SetMetadata("UndefineProperties", undefineProperties + ";TargetFramework");
}

if (projectReference.GetMetadata("IsRidAgnostic") == "true")
{
// If the project is RID agnostic, undefine the RuntimeIdentifier property to avoid another evaluation. -->
string undefineProperties = projectReference.GetMetadata("UndefineProperties");
projectReference.SetMetadata("UndefineProperties", undefineProperties + ";RuntimeIdentifier");
}

projectReference.SetMetadata("SkipGetTargetFrameworkProperties", "true");
assignedProjects.Add(assignedProject);
}

annotatedProjectReferencesWithSetTargetFramework.Add(projectReference);
}

AnnotatedProjectReferencesWithSetTargetFramework = annotatedProjectReferencesWithSetTargetFramework.ToArray();
AssignedProjects = assignedProjects.ToArray();
return !Log.HasLoggedErrors;
}
}

private ITaskItem? AssignNearestFrameworkForSingleReference(ITaskItem project,
NuGetFramework projectNuGetFramework,
TargetFrameworkResolver targetFrameworkResolver)
{
TaskItem itemWithProperties = new(project);
string referencedProjectFrameworkString = project.GetMetadata(TARGET_FRAMEWORKS);

if (string.IsNullOrEmpty(referencedProjectFrameworkString))
{
// No target frameworks set, nothing to do.
return itemWithProperties;
}

string[] referencedProjectFrameworks = MSBuildStringUtility.Split(referencedProjectFrameworkString!);

// try project framework
string? nearestNuGetFramework = targetFrameworkResolver.GetNearest(referencedProjectFrameworks, projectNuGetFramework);
if (nearestNuGetFramework != null)
{
itemWithProperties.SetMetadata(NEAREST_TARGET_FRAMEWORK, nearestNuGetFramework);
return itemWithProperties;
}

if (OmitIncompatibleProjectReferences)
{
return null;
}

// no match found
Log.LogError(string.Format(CultureInfo.CurrentCulture, "Project '{0}' targets '{1}'. It cannot be referenced by a project that targets '{2}{3}'.", project.ItemSpec, referencedProjectFrameworkString, projectNuGetFramework.DotNetFrameworkName, projectNuGetFramework.HasPlatform ? "-" + projectNuGetFramework.DotNetPlatformName : string.Empty));
return itemWithProperties;
}

private static bool TryParseFramework(string targetFrameworkMoniker, string? targetPlatformMoniker, string errorMessage, Log logger, out NuGetFramework nugetFramework)
{
// Check if we have a long name.
#if NETFRAMEWORK || NETSTANDARD
nugetFramework = targetFrameworkMoniker.Contains(",")
? NuGetFramework.ParseComponents(targetFrameworkMoniker, targetPlatformMoniker)
: NuGetFramework.Parse(targetFrameworkMoniker);
#else
nugetFramework = targetFrameworkMoniker.Contains(',', System.StringComparison.Ordinal)
? NuGetFramework.ParseComponents(targetFrameworkMoniker, targetPlatformMoniker)
: NuGetFramework.Parse(targetFrameworkMoniker);
#endif

// validate framework
if (nugetFramework.IsUnsupported)
{
logger.LogError(errorMessage);
return false;
}

return true;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using NuGet.Frameworks;
using System.Collections.Generic;
using System.Linq;

Expand All @@ -11,31 +12,32 @@ namespace Microsoft.DotNet.Build.Tasks.TargetFramework
public class ChooseBestTargetFrameworksTask : BuildTask
{
[Required]
public ITaskItem[] BuildTargetFrameworks { get; set; }
public ITaskItem[]? BuildTargetFrameworks { get; set; }

[Required]
public string RuntimeGraph { get; set; }
public string? RuntimeGraph { get; set; }

[Required]
public string[] SupportedTargetFrameworks { get; set; }
public string[]? SupportedTargetFrameworks { get; set; }

// Returns distinct items only. Compares the include values. Metadata is ignored.
public bool Distinct { get; set; }

[Output]
public ITaskItem[] BestTargetFrameworks { get; set; }
public ITaskItem[]? BestTargetFrameworks { get; set; }

public override bool Execute()
{
var bestTargetFrameworkList = new List<ITaskItem>(BuildTargetFrameworks.Length);
var targetframeworkResolver = new TargetFrameworkResolver(RuntimeGraph);
List<ITaskItem> bestTargetFrameworkList = new(BuildTargetFrameworks!.Length);
TargetFrameworkResolver targetframeworkResolver = TargetFrameworkResolver.CreateOrGet(RuntimeGraph!);

foreach (ITaskItem buildTargetFramework in BuildTargetFrameworks)
{
string bestTargetFramework = targetframeworkResolver.GetBestSupportedTargetFramework(SupportedTargetFrameworks, buildTargetFramework.ItemSpec);
NuGetFramework framework = NuGetFramework.ParseFolder(buildTargetFramework.ItemSpec);
string? bestTargetFramework = targetframeworkResolver.GetNearest(SupportedTargetFrameworks!, framework);
if (bestTargetFramework != null && (!Distinct || !bestTargetFrameworkList.Any(b => b.ItemSpec == bestTargetFramework)))
{
var item = new TaskItem(bestTargetFramework);
TaskItem item = new(bestTargetFramework);
buildTargetFramework.CopyMetadataTo(item);
bestTargetFrameworkList.Add(item);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
<Title>Configuration system for cross-targeting projects.</Title>
<PackageDescription>This package provides the following MSBuild tasks: ChooseBestTargetFrameworksTask and ChooseBestP2PTargetFrameworkTask.</PackageDescription>
<DefaultItemExcludes Condition="'$(TargetFramework)' != 'net472'">**/*.Desktop.*</DefaultItemExcludes>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'net472'">
Expand All @@ -24,7 +25,7 @@
<PackageReference Include="Microsoft.Build.Tasks.Core" Version="$(MicrosoftBuildTasksCoreVersion)" />
<PackageReference Include="NuGet.Packaging" Version="$(NugetVersion)" />

<!-- This is here so that we agree with the project's transitive references to NewtonSoft.Json -->
<!-- This is here so that we agree with the project's transitive references to Newtonsoft.Json -->
<PackageReference Include="Newtonsoft.Json" Version="$(NewtonsoftJsonVersion)" />
</ItemGroup>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,20 @@ namespace Microsoft.DotNet.Build.Tasks.TargetFramework
/// This class uses NuGet's asset selection logic to choose the best TargetFramework given the list of supported TargetFrameworks.
/// This behaves in a same way as NuGet selects lib files from a nuget package for a particular TargetFramework.
/// </summary>
public class TargetFrameworkResolver
internal class TargetFrameworkResolver
{
private static readonly Dictionary<string, TargetFrameworkResolver> s_targetFrameworkResolverCache = new();
private readonly ManagedCodeConventions _conventions;
private readonly PatternSet _configStringPattern;

public TargetFrameworkResolver(string runtimeGraph)
private TargetFrameworkResolver(string runtimeGraph)
{
_conventions = new ManagedCodeConventions(JsonRuntimeFormat.ReadRuntimeGraph(runtimeGraph));
_configStringPattern = new PatternSet(
_conventions.Properties,
groupPatterns: new PatternDefinition[]
{
// In order to use Nuget's asset allocation, the input needs to file paths and should contain a trailing slash.
// In order to use Nuget's asset allocation, the input needs to be file paths and should contain a trailing slash.
new PatternDefinition("{tfm}/"),
new PatternDefinition("{tfm}-{rid}/")
},
Expand All @@ -37,17 +38,28 @@ public TargetFrameworkResolver(string runtimeGraph)
});
}

public string GetBestSupportedTargetFramework(IEnumerable<string> supportedTargetFrameworks, string targetFramework)
public static TargetFrameworkResolver CreateOrGet(string runtimeGraph)
{
var contentCollection = new ContentItemCollection();
contentCollection.Load(supportedTargetFrameworks.Select(t => t + '/').ToArray());
if (!s_targetFrameworkResolverCache.TryGetValue(runtimeGraph, out TargetFrameworkResolver? targetFrameworkResolver))
{
targetFrameworkResolver = new TargetFrameworkResolver(runtimeGraph);
s_targetFrameworkResolverCache.Add(runtimeGraph, targetFrameworkResolver);
}

string[] splitStrings = targetFramework.Split('-');
string targetFrameworkWithoutSuffix = splitStrings[0];
string targetFrameworkSuffix = splitStrings.Length > 1 ? splitStrings[1] : string.Empty;
return targetFrameworkResolver!;
}

public string? GetNearest(IEnumerable<string> frameworks, NuGetFramework framework)
{
NuGetFramework frameworkWithoutPlatform = NuGetFramework.Parse(framework.DotNetFrameworkName);

ContentItemCollection contentCollection = new();
contentCollection.Load(frameworks.Select(f => f + '/').ToArray());

// The platform is expected to be passed-in lower-case but the SDK normalizes "windows" to "Windows" which is why it is lowered again.
SelectionCriteria criteria = _conventions.Criteria.ForFrameworkAndRuntime(frameworkWithoutPlatform, framework.Platform.ToLowerInvariant());
string? bestTargetFrameworkString = contentCollection.FindBestItemGroup(criteria, _configStringPattern)?.Items[0].Path;

SelectionCriteria criteria = _conventions.Criteria.ForFrameworkAndRuntime(NuGetFramework.Parse(targetFrameworkWithoutSuffix), targetFrameworkSuffix);
string bestTargetFrameworkString = contentCollection.FindBestItemGroup(criteria, _configStringPattern)?.Items[0].Path;
return bestTargetFrameworkString?.Remove(bestTargetFrameworkString.Length - 1);
}
}
Expand Down
Loading