|
// 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;
using Microsoft.Build.Utilities;
using NuGet.Frameworks;
using System.Collections.Generic;
using System.Linq;
namespace Microsoft.DotNet.Build.Tasks.Packaging
{
/// <summary>
/// Promotes dependencies from reference (ref) assembly TargetFramework to the implementation (lib) assembly
/// TargetFramework and vice versa.
/// NuGet only ever chooses a single dependencyGroup from a package. Often the TFM of the implementation and
/// reference differ so in order to ensure the correct dependencies are applied we have to promote dependencies
/// from a less specific ref to the more specific lib, and from a less specific lib to a more specific ref.
/// </summary>
public class PromoteDependencies : BuildTask
{
private const string TargetFrameworkMetadataName = "TargetFramework";
private PackageIndex index;
[Required]
public ITaskItem[] Dependencies { get; set; }
[Required]
public ITaskItem[] PackageIndexes { get; set; }
[Output]
public ITaskItem[] PromotedDependencies { get; set; }
public override bool Execute()
{
if (PackageIndexes == null && PackageIndexes.Length == 0)
{
Log.LogError("PackageIndexes argument must be specified");
return false;
}
index = PackageIndex.Load(PackageIndexes.Select(pi => pi.GetMetadata("FullPath")));
List<ITaskItem> promotedDependencies = new List<ITaskItem>();
var dependencies = Dependencies.Select(d => new Dependency(d)).ToArray();
var refSets = dependencies.Where(d => d.Id != "_._").Where(d => d.IsReference).GroupBy(d => NuGetFramework.Parse(d.TargetFramework)).ToDictionary(g => g.Key, g => g.ToArray());
var refFxs = refSets.Keys.ToArray();
Log.LogMessage(LogImportance.Low, $"Ref frameworks {string.Join(";", refFxs.Select(f => f.ToString()))}");
var libSets = dependencies.Where(d => !d.IsReference).GroupBy(d => NuGetFramework.Parse(d.TargetFramework)).ToDictionary(g => g.Key, g => g.ToArray());
var libFxs = libSets.Keys.ToArray();
Log.LogMessage(LogImportance.Low, $"Lib frameworks {string.Join(";", libFxs.Select(f => f.ToString()))}");
if (libFxs.Length > 0)
{
foreach (var refFx in refFxs)
{
// find best lib (if any)
var nearestLibFx = FrameworkUtilities.GetNearest(refFx, libFxs);
if (nearestLibFx != null && !nearestLibFx.Equals(refFx))
{
promotedDependencies.AddRange(CopyDependencies(libSets[nearestLibFx], refFx));
}
}
}
if (refFxs.Length > 0)
{
foreach (var libFx in libFxs)
{
// find best lib (if any)
var nearestRefFx = FrameworkUtilities.GetNearest(libFx, refFxs);
if (nearestRefFx != null && !nearestRefFx.Equals(libFx))
{
promotedDependencies.AddRange(CopyDependencies(refSets[nearestRefFx], libFx));
}
}
}
PromotedDependencies = promotedDependencies.ToArray();
return !Log.HasLoggedErrors;
}
private IEnumerable<ITaskItem> CopyDependencies(IEnumerable<Dependency> dependencies, NuGetFramework targetFramework)
{
foreach (var dependency in dependencies)
{
if (!index.IsInbox(dependency.Id, targetFramework, dependency.Version))
{
var copiedDepenedency = new TaskItem(dependency.OriginalItem);
copiedDepenedency.SetMetadata(TargetFrameworkMetadataName, targetFramework.GetShortFolderName());
yield return copiedDepenedency;
}
}
}
private class Dependency
{
public Dependency(ITaskItem item)
{
Id = item.ItemSpec;
Version = item.GetMetadata("Version");
IsReference = item.GetMetadata("TargetPath").StartsWith("ref/", System.StringComparison.OrdinalIgnoreCase);
TargetFramework = item.GetMetadata(TargetFrameworkMetadataName);
OriginalItem = item;
}
public string Id { get; }
public string Version { get; }
public bool IsReference { get; }
public string TargetFramework { get; }
public ITaskItem OriginalItem { get; }
}
}
}
|