-
Notifications
You must be signed in to change notification settings - Fork 353
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Align TargetFramework pkg with NuGet Task (#12041)
- Align the ChooseBestP2PTargetFrameworkTask more closely with the NuGet task that is used in the SDK to resolve best matching project references. - Enable nullable reference type warnings - Remove logic from BinPlace.targets that isn't needed anymore - Make the TargetFramework msbuild files pick up the SDK's runtime graph instead of passing it in explicitly.
- Loading branch information
1 parent
ea2db8a
commit e82404f
Showing
10 changed files
with
230 additions
and
178 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
159 changes: 103 additions & 56 deletions
159
src/Microsoft.DotNet.Build.Tasks.TargetFramework/src/ChooseBestP2PTargetFrameworkTask.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.