|
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
#nullable disable
using System;
using System.Collections.Generic;
using System.Globalization;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using NuGet.Common;
using NuGet.Frameworks;
#if NETFRAMEWORK
using System.Linq;
#endif
using NuGet.ProjectModel;
namespace NuGet.Build.Tasks
{
public class GetReferenceNearestTargetFrameworkTask : Task
{
private const string NEAREST_TARGET_FRAMEWORK = "NearestTargetFramework";
private const string TARGET_FRAMEWORKS = "TargetFrameworks";
private const string TARGET_FRAMEWORK_MONIKERS = "TargetFrameworkMonikers";
private const string TARGET_PLATFORM_MONIKERS = "TargetPlatformMonikers";
private const string MSBUILD_SOURCE_PROJECT_FILE = "MSBuildSourceProjectFile";
/// <summary>
/// The current project's name.
/// </summary>
public string CurrentProjectName { get; set; }
/// <summary>
/// The current project's target framework.
/// </summary>
[Required]
public string CurrentProjectTargetFramework { get; set; }
/// <summary>
/// Optional TargetPlatformMoniker
/// </summary>
public string CurrentProjectTargetPlatform { get; set; }
/// <summary>
/// The value of the current project's TargetFramework property.
/// </summary>
public string CurrentProjectTargetFrameworkProperty { get; set; }
/// <summary>
/// Optional list of target frameworks to be used as Fallback target frameworks.
/// </summary>
public string[] FallbackTargetFrameworks { 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[] AssignedProjects { get; set; }
public override bool Execute()
{
var logger = new MSBuildLogger(Log);
if (AnnotatedProjectReferences == null)
{
return !Log.HasLoggedErrors;
}
var fallbackNuGetFrameworks = new List<NuGetFramework>();
// validate current project framework
var errorMessage = string.Format(CultureInfo.CurrentCulture, Strings.UnsupportedTargetFramework, $"TargetFrameworkMoniker: {CurrentProjectTargetFramework}, TargetPlatformMoniker:{CurrentProjectTargetPlatform}");
if (!TryParseFramework(CurrentProjectTargetFramework, CurrentProjectTargetPlatform, errorMessage, logger, out var projectNuGetFramework))
{
return false;
}
if (FallbackTargetFrameworks != null &&
FallbackTargetFrameworks.Length > 0)
{
foreach (var fallbackFramework in FallbackTargetFrameworks)
{
// validate ATF project frameworks
errorMessage = string.Format(CultureInfo.CurrentCulture, Strings.UnsupportedFallbackFramework, fallbackFramework);
if (!TryParseFramework(fallbackFramework, errorMessage, logger, out var nugetFramework))
{
return false;
}
else
{
fallbackNuGetFrameworks.Add(nugetFramework);
}
}
}
AssignedProjects = new ITaskItem[AnnotatedProjectReferences.Length];
for (var index = 0; index < AnnotatedProjectReferences.Length; index++)
{
AssignedProjects[index] = AssignNearestFrameworkForSingleReference(AnnotatedProjectReferences[index], projectNuGetFramework, fallbackNuGetFrameworks, logger);
}
return !Log.HasLoggedErrors;
}
private ITaskItem AssignNearestFrameworkForSingleReference(
ITaskItem project,
NuGetFramework projectNuGetFramework,
IList<NuGetFramework> fallbackNuGetFrameworks,
MSBuildLogger logger)
{
var itemWithProperties = new TaskItem(project);
var referencedProjectFrameworkString = project.GetMetadata(TARGET_FRAMEWORKS);
var referenceTargetFrameworkMonikers = project.GetMetadata(TARGET_FRAMEWORK_MONIKERS);
var referencedProjectPlatformString = project.GetMetadata(TARGET_PLATFORM_MONIKERS);
var referencedProjectFile = project.GetMetadata(MSBUILD_SOURCE_PROJECT_FILE);
if (string.IsNullOrEmpty(referencedProjectFrameworkString))
{
// No target frameworks set, nothing to do.
return itemWithProperties;
}
var referencedProjectFrameworks = MSBuildStringUtility.Split(referencedProjectFrameworkString);
var referencedProjectTargetFrameworkMonikers = MSBuildStringUtility.Split(referenceTargetFrameworkMonikers);
var referencedProjectTargetPlatformMonikers = MSBuildStringUtility.Split(referencedProjectPlatformString);
if (referencedProjectTargetFrameworkMonikers.Length > 0 &&
(referencedProjectTargetFrameworkMonikers.Length != referencedProjectTargetPlatformMonikers.Length ||
referencedProjectTargetFrameworkMonikers.Length != referencedProjectFrameworks.Length))
{
logger.LogError($"Internal error for {CurrentProjectName}." +
$" Expected {TARGET_FRAMEWORKS}:{referencedProjectFrameworks}, " +
$"{TARGET_FRAMEWORK_MONIKERS}:{referenceTargetFrameworkMonikers}, " +
$"{TARGET_PLATFORM_MONIKERS}:{referencedProjectPlatformString} to have the same number of elements.");
return itemWithProperties;
}
// TargetFrameworks, TargetFrameworkMoniker, TargetPlatforMoniker
var targetFrameworkInformations = new List<TargetFrameworkInformation>();
var useTargetMonikers = referencedProjectTargetFrameworkMonikers.Length > 0;
for (int i = 0; i < referencedProjectFrameworks.Length; i++)
{
targetFrameworkInformations.Add(new TargetFrameworkInformation()
{
TargetAlias = referencedProjectFrameworks[i],
FrameworkName = GetNuGetFramework(
referencedProjectFrameworks[i],
useTargetMonikers ? referencedProjectTargetFrameworkMonikers[i] : null,
useTargetMonikers ? referencedProjectTargetPlatformMonikers[i] : null)
});
}
var packageSpec = new PackageSpec(targetFrameworkInformations);
var nearestNuGetFramework = packageSpec.GetNearestTargetFramework(projectNuGetFramework, CurrentProjectTargetFrameworkProperty);
if (nearestNuGetFramework.FrameworkName != null)
{
itemWithProperties.SetMetadata(NEAREST_TARGET_FRAMEWORK, nearestNuGetFramework.TargetAlias);
return itemWithProperties;
}
// try project fallback frameworks
foreach (var currentProjectTargetFramework in fallbackNuGetFrameworks)
{
nearestNuGetFramework = packageSpec.GetNearestTargetFramework(currentProjectTargetFramework, targetAlias: CurrentProjectTargetFrameworkProperty);
if (nearestNuGetFramework.FrameworkName != null)
{
var message = string.Format(CultureInfo.CurrentCulture,
Strings.ImportsFallbackWarning,
referencedProjectFile,
currentProjectTargetFramework.DotNetFrameworkName,
projectNuGetFramework.DotNetFrameworkName);
var warning = RestoreLogMessage.CreateWarning(NuGetLogCode.NU1702, message);
warning.LibraryId = referencedProjectFile;
warning.ProjectPath = CurrentProjectName;
// log NU1702 for ATF on project reference
logger.Log(warning);
itemWithProperties.SetMetadata(NEAREST_TARGET_FRAMEWORK, nearestNuGetFramework.TargetAlias);
return itemWithProperties;
}
}
// no match found
logger.LogError(string.Format(CultureInfo.CurrentCulture, Strings.NoCompatibleTargetFramework, project.ItemSpec, projectNuGetFramework.DotNetFrameworkName, referencedProjectFrameworkString));
return itemWithProperties;
}
private static bool TryParseFramework(string framework, string errorMessage, MSBuildLogger logger, out NuGetFramework nugetFramework)
{
nugetFramework = NuGetFramework.Parse(framework);
// validate framework
if (nugetFramework.IsUnsupported)
{
logger.LogError(errorMessage);
return false;
}
return true;
}
private static bool TryParseFramework(string targetFrameworkMoniker, string targetPlatformMoniker, string errorMessage, MSBuildLogger 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(',', StringComparison.Ordinal)
? NuGetFramework.ParseComponents(targetFrameworkMoniker, targetPlatformMoniker)
: NuGetFramework.Parse(targetFrameworkMoniker);
#endif
// validate framework
if (nugetFramework.IsUnsupported)
{
logger.LogError(errorMessage);
return false;
}
return true;
}
private static NuGetFramework GetNuGetFramework(string targetAlias, string targetFrameworkMoniker, string targetPlatformMoniker)
{
// Legacy path, process targetFrameworks if empty
if (string.IsNullOrEmpty(targetFrameworkMoniker))
{
return NuGetFramework.Parse(targetAlias);
}
// TargetFrameworkMoniker is always expected to be set. TargetPlatformMoniker will have a `None` value when empty, for frameworks like net5.0.
return NuGetFramework.ParseComponents(targetFrameworkMoniker,
targetPlatformMoniker.Equals("None", StringComparison.OrdinalIgnoreCase) ?
string.Empty :
targetPlatformMoniker);
}
}
}
|