File: ApplyMetaPackages.cs
Web Access
Project: src\src\Microsoft.DotNet.Build.Tasks.Packaging\src\Microsoft.DotNet.Build.Tasks.Packaging.csproj (Microsoft.DotNet.Build.Tasks.Packaging)
// 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>
    /// Replaces package dependencies with meta-package dependencies where appropriate.
    /// </summary>
    public class ApplyMetaPackages : BuildTask
    {
        /// <summary>
        /// Need the package id to ensure we don't add a meta-package reference for a package that depends on itself.
        /// </summary>
        [Required]
        public string PackageId { get; set; }
        /// <summary>
        /// Original dependencies
        /// </summary>
        [Required]
        public ITaskItem[] OriginalDependencies { get; set; }
 
        /// <summary>
        /// Package index files used to meta-package mappings
        /// </summary>
        public ITaskItem[] PackageIndexes { get; set; }
 
        /// <summary>
        /// List of package IDs which should be suppressed from remapping. You can pass in
        /// TargetFramework metadata on the item if we only need to supress the metapackage on
        /// specific TFMs.
        /// </summary>
        public ITaskItem[] SuppressMetaPackages { get; set; }
 
        /// <summary>
        /// Set to true to apply the meta-package remapping
        /// </summary>
        public bool Apply { get; set; }
        
        [Output]
        public ITaskItem[] UpdatedDependencies { get; set; }
        
        public override bool Execute()
        {
            if (!Apply)
            {
                UpdatedDependencies = OriginalDependencies;
                return true;
            }
 
            var index = PackageIndex.Load(PackageIndexes.Select(pi => pi.GetMetadata("FullPath")));
            List<ITaskItem> updatedDependencies = new List<ITaskItem>();
 
            var suppressMetaPackages = new Dictionary<string, HashSet<string>>();
 
            if (SuppressMetaPackages != null)
            {
                foreach (ITaskItem metapackage in SuppressMetaPackages)
                {
                    if (!suppressMetaPackages.TryGetValue(metapackage.ItemSpec, out var value))
                    {
                        value = new HashSet<string>();
                        suppressMetaPackages.Add(metapackage.ItemSpec, value);
                    }
                    var tfmSpecificSupression = metapackage.GetMetadata("TargetFramework");
                    if (string.IsNullOrEmpty(tfmSpecificSupression))
                    {
                        // If the supression doesn't specify a TargetFramework, then it applies to all.
                        value.Add("All");
                    }
                    else
                    {
                        var fx = NuGetFramework.Parse(tfmSpecificSupression);
                        value.Add(fx.DotNetFrameworkName);
                    }
                }
            }
 
            // We cannot add a dependency to a meta-package from a package that itself is part of the meta-package otherwise we create a cycle
            var metaPackageThisPackageIsIn = index.MetaPackages.GetMetaPackageId(PackageId);
            if (metaPackageThisPackageIsIn != null)
            {
                suppressMetaPackages.Add(metaPackageThisPackageIsIn, new HashSet<string> { "All" } );
            }
 
            // keep track of meta-package dependencies to add by framework so that we only add them once per framework.
            Dictionary<string, HashSet<NuGetFramework>> metaPackagesToAdd = new Dictionary<string, HashSet<NuGetFramework>>();
 
            foreach (var originalDependency in OriginalDependencies)
            {
                var metaPackage = index.MetaPackages.GetMetaPackageId(originalDependency.ItemSpec);
 
                // convert to meta-package dependency
                var tfm = originalDependency.GetMetadata("TargetFramework");
                var fx = NuGetFramework.Parse(tfm);
 
                if (metaPackage != null && !ShouldSuppressMetapackage(suppressMetaPackages, metaPackage, fx))
                {
                    HashSet<NuGetFramework> metaPackageFrameworks;
 
                    if (!metaPackagesToAdd.TryGetValue(metaPackage, out metaPackageFrameworks))
                    {
                        metaPackagesToAdd[metaPackage] = metaPackageFrameworks = new HashSet<NuGetFramework>();
                    }
 
                    metaPackageFrameworks.Add(fx);
                }
                else
                {
                    updatedDependencies.Add(originalDependency);
                }
            }
 
            updatedDependencies.AddRange(metaPackagesToAdd.SelectMany(pair => pair.Value.Select(tfm => CreateDependency(pair.Key, tfm))));
 
            UpdatedDependencies = updatedDependencies.ToArray();
 
            return !Log.HasLoggedErrors;
        }
 
        private bool ShouldSuppressMetapackage(Dictionary<string, HashSet<string>> suppressedMetaPackages, string metaPackage, NuGetFramework tfm) =>
            suppressedMetaPackages.TryGetValue(metaPackage, out var value) &&
                (value.Contains("All") || value.Contains(tfm.DotNetFrameworkName));
 
        private ITaskItem CreateDependency(string id, NuGetFramework targetFramework)
        {
            var item = new TaskItem(id);
            item.SetMetadata("TargetFramework", targetFramework.GetShortFolderName());
            return item;
        }
        
    }
}