File: NuGetAssetResolver.cs
Web Access
Project: 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.Client;
using NuGet.ContentModel;
using NuGet.Frameworks;
using NuGet.RuntimeModel;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;

namespace Microsoft.DotNet.Build.Tasks.Packaging;

public class NuGetAssetResolver
{
    private ManagedCodeConventions _conventions;
    private ContentItemCollection _sourceItems;


    public const string PlaceHolderFile = "_._";

    public NuGetAssetResolver(string runtimeFile, IEnumerable<string> packageItems)
    {
        RuntimeGraph runtimeGraph = null;

        if (!string.IsNullOrEmpty(runtimeFile))
        {
            runtimeGraph = JsonRuntimeFormat.ReadRuntimeGraph(runtimeFile);
        }
        _conventions = new ManagedCodeConventions(runtimeGraph);

        _sourceItems = new ContentItemCollection();
        _sourceItems.Load(packageItems);
    }

    public ContentItemGroup GetCompileItems(NuGetFramework framework)
    {
        // don't use the RID for compile assets.
        var managedCriteria = _conventions.Criteria.ForFramework(framework);

        FixCriteria(managedCriteria);

        // compile falls back to runtime pattern in the case of lib with no ref, this won't fallback
        // to a runtime asset since we have no RID passed in.
        return _sourceItems.FindBestItemGroup(managedCriteria,
            _conventions.Patterns.CompileRefAssemblies,
            _conventions.Patterns.RuntimeAssemblies);
    }

    public IEnumerable<string> ResolveCompileAssets(NuGetFramework framework)
    {
        // we only care about compile items from this package, runtime packages will
        // never contribute compile items since the runtime graph is only used for runtime assets.
        var compileItems = GetCompileItems(framework);

        return (compileItems != null) ?
            compileItems.Items.Select(ci => ci.Path) :
            Enumerable.Empty<string>();
    }

    public ContentItemGroup GetRuntimeItems(NuGetFramework framework, string runtimeIdentifier)
    {
        var managedCriteria = _conventions.Criteria.ForFrameworkAndRuntime(framework, runtimeIdentifier);

        FixCriteria(managedCriteria);

        return _sourceItems.FindBestItemGroup(managedCriteria,
            _conventions.Patterns.RuntimeAssemblies);
    }

    public IEnumerable<string> ResolveRuntimeAssets(NuGetFramework framework, string runtimeId)
    {
        var runtimeItems = GetRuntimeItems(framework, runtimeId);

        return (runtimeItems != null) ?
            runtimeItems.Items.Select(ci => ci.Path) :
            Enumerable.Empty<string>();
    }

    public static void FixCriteria(SelectionCriteria criteria)
    {
        // workaround https://github.com/NuGet/Home/issues/1457
        foreach (var criterium in criteria.Entries)
        {
            if (criterium.Properties.ContainsKey(ManagedCodeConventions.PropertyNames.TargetFrameworkMoniker) &&
                !criterium.Properties.ContainsKey(ManagedCodeConventions.PropertyNames.RuntimeIdentifier))
            {
                criterium.Properties.Add(ManagedCodeConventions.PropertyNames.RuntimeIdentifier, null);
            }
        }
    }

    public static IEnumerable<string> GetPackageTargetDirectories(ContentItemGroup contentGroup)
    {
        if (contentGroup == null)
        {
            yield break;
        }

        foreach (var contentItem in contentGroup.Items)
        {
            // package paths are standardized to '/'
            int dirLength = contentItem.Path.LastIndexOf(Path.AltDirectorySeparatorChar);

            if (dirLength == -1)
            {
                yield return "";
            }
            else
            {
                yield return contentItem.Path.Substring(0, dirLength);
            }
        }
    }

    public static void ExamineAssets(TaskLoggingHelper logger, string assetType, string package, string target, IEnumerable<string> items, out bool hasRealAsset, out bool hasPlaceHolder)
    {
        hasPlaceHolder = false;
        hasRealAsset = false;
        StringBuilder assetLog = new StringBuilder($"{assetType} assets for {package} on {target}: ");
        if (items != null && items.Any())
        {
            foreach (var runtimeItem in items)
            {
                assetLog.AppendLine();
                assetLog.Append($"  {runtimeItem}");

                if (!hasRealAsset && NuGetAssetResolver.IsPlaceholder(runtimeItem))
                {
                    hasPlaceHolder = true;
                }
                else
                {
                    hasRealAsset = true;
                    hasPlaceHolder = false;
                }
            }
        }
        else
        {
            assetLog.AppendLine();
            assetLog.Append("  <none>");
        }
        logger.LogMessage(MessageImportance.Low, assetLog.ToString());
    }

    public static bool IsPlaceholder(string path)
    {
        return Path.GetFileName(path) == PlaceHolderFile;
    }
}

public class AggregateNuGetAssetResolver
{
    private ManagedCodeConventions _conventions;
    private Dictionary<string, ContentItemCollection> _packages;


    public const string PlaceHolderFile = "_._";

    public AggregateNuGetAssetResolver(string runtimeFile)
    {
        RuntimeGraph runtimeGraph = null;

        if (!String.IsNullOrEmpty(runtimeFile))
        {
            runtimeGraph = JsonRuntimeFormat.ReadRuntimeGraph(runtimeFile);
        }
        _conventions = new ManagedCodeConventions(runtimeGraph);
        _packages = new Dictionary<string, ContentItemCollection>();
    }

    public void AddPackageItems(string packageId, IEnumerable<string> packageItems)
    {
        if (!_packages.ContainsKey(packageId))
        {
            _packages[packageId] = new ContentItemCollection();
        }

        _packages[packageId].Load(packageItems);
    }

    public IReadOnlyDictionary<string, ContentItemGroup> GetCompileItems(NuGetFramework framework)
    {
        // don't use the RID for compile assets.
        var managedCriteria = _conventions.Criteria.ForFramework(framework);

        NuGetAssetResolver.FixCriteria(managedCriteria);

        Dictionary<string, ContentItemGroup> resolvedAssets = new Dictionary<string, ContentItemGroup>();
        // compile falls back to runtime pattern in the case of lib with no ref, this won't fallback
        // to a runtime asset since we have no RID passed in.
        foreach (var package in _packages.Keys)
        {
            resolvedAssets.Add(package,
                _packages[package].FindBestItemGroup(managedCriteria,
                    _conventions.Patterns.CompileRefAssemblies,
                    _conventions.Patterns.RuntimeAssemblies));
        }

        return resolvedAssets;
    }

    public IEnumerable<string> ResolveCompileAssets(NuGetFramework framework, string referencePackageId)
    {
        // we only care about compile items from this package, runtime packages will
        // never contribute compile items since the runtime graph is only used for runtime assets.
        var allCompileItems = GetCompileItems(framework);
        ContentItemGroup thisPackageCompileItems = null;
        allCompileItems.TryGetValue(referencePackageId, out thisPackageCompileItems);

        return (thisPackageCompileItems != null) ?
            thisPackageCompileItems.Items.Select(ci => AsPackageSpecificTargetPath(referencePackageId, ci.Path)) :
            Enumerable.Empty<string>();
    }

    public IEnumerable<string> ResolveCompileAssets(NuGetFramework framework)
    {
        var allCompileItems = GetCompileItems(framework);
        foreach (var packageId in allCompileItems.Keys)
        {
            var packageAssets = allCompileItems[packageId];
            if (packageAssets == null)
            {
                continue;
            }

            foreach (var packageAsset in packageAssets.Items)
            {
                yield return AsPackageSpecificTargetPath(packageId, packageAsset.Path);
            }
        }
    }

    public IReadOnlyDictionary<string, ContentItemGroup> GetRuntimeItems(NuGetFramework framework, string runtimeIdentifier)
    {
        var managedCriteria = _conventions.Criteria.ForFrameworkAndRuntime(framework, runtimeIdentifier);

        NuGetAssetResolver.FixCriteria(managedCriteria);

        Dictionary<string, ContentItemGroup> resolvedAssets = new Dictionary<string, ContentItemGroup>();

        foreach (var package in _packages.Keys)
        {
            resolvedAssets.Add(package,
                _packages[package].FindBestItemGroup(managedCriteria,
                    _conventions.Patterns.RuntimeAssemblies));
        }

        return resolvedAssets;
    }

    public IReadOnlyDictionary<string, ContentItemGroup> GetNativeItems(NuGetFramework framework, string runtimeIdentifier)
    {
        var managedCriteria = _conventions.Criteria.ForFrameworkAndRuntime(framework, runtimeIdentifier);

        NuGetAssetResolver.FixCriteria(managedCriteria);

        Dictionary<string, ContentItemGroup> resolvedAssets = new Dictionary<string, ContentItemGroup>();

        foreach (var package in _packages.Keys)
        {
            resolvedAssets.Add(package,
                _packages[package].FindBestItemGroup(managedCriteria,
                    _conventions.Patterns.NativeLibraries));
        }

        return resolvedAssets;
    }

    [Obsolete]
    public IReadOnlyDictionary<string, IEnumerable<ContentItemGroup>> GetAllRuntimeItems()
    {
        Dictionary<string, IEnumerable<ContentItemGroup>> resolvedAssets = new Dictionary<string, IEnumerable<ContentItemGroup>>();

        foreach (var package in _packages.Keys)
        {
            var contentItemGroups = new List<ContentItemGroup>();
            _packages[package].PopulateItemGroups(_conventions.Patterns.RuntimeAssemblies, contentItemGroups);
            resolvedAssets.Add(package, contentItemGroups);
        }

        return resolvedAssets;
    }

    public IEnumerable<string> ResolveRuntimeAssets(NuGetFramework framework, string runtimeId)
    {
        var allRuntimeItems = GetRuntimeItems(framework, runtimeId);
        foreach (var packageId in allRuntimeItems.Keys)
        {
            var packageAssets = allRuntimeItems[packageId];
            if (packageAssets == null)
            {
                continue;
            }

            foreach (var packageAsset in packageAssets.Items)
            {
                yield return AsPackageSpecificTargetPath(packageId, packageAsset.Path);
            }
        }
    }
    public IEnumerable<string> ResolveNativeAssets(NuGetFramework framework, string runtimeId)
    {
        var allNativeItems = GetNativeItems(framework, runtimeId);
        foreach (var packageId in allNativeItems.Keys)
        {
            var packageAssets = allNativeItems[packageId];
            if (packageAssets == null)
            {
                continue;
            }

            foreach (var packageAsset in packageAssets.Items)
            {
                yield return AsPackageSpecificTargetPath(packageId, packageAsset.Path);
            }
        }
    }

    public static string AsPackageSpecificTargetPath(string packageId, string targetPath)
    {
        return $"{packageId}/{targetPath}";
    }
}