File: ResolveTargetingPackAssets.cs
Web Access
Project: src\src\sdk\src\Tasks\Microsoft.NET.Build.Tasks\Microsoft.NET.Build.Tasks.csproj (Microsoft.NET.Build.Tasks)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#nullable disable

using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;

using static Microsoft.DotNet.Cli.EnvironmentVariableNames;

namespace Microsoft.NET.Build.Tasks
{
    public class ResolveTargetingPackAssets : TaskBase
    {
        public ITaskItem[] FrameworkReferences { get; set; } = Array.Empty<ITaskItem>();

        public ITaskItem[] ResolvedTargetingPacks { get; set; } = Array.Empty<ITaskItem>();

        public ITaskItem[] RuntimeFrameworks { get; set; } = Array.Empty<ITaskItem>();

        public bool GenerateErrorForMissingTargetingPacks { get; set; }

        public bool NuGetRestoreSupported { get; set; } = true;

        public bool DisableTransitiveFrameworkReferenceDownloads { get; set; }

        public string NetCoreTargetingPackRoot { get; set; }

        public string ProjectLanguage { get; set; }

        [Output]
        public ITaskItem[] ReferencesToAdd { get; set; }

        [Output]
        public ITaskItem[] AnalyzersToAdd { get; set; }

        [Output]
        public ITaskItem[] PlatformManifests { get; set; }

        [Output]
        public string PackageConflictPreferredPackages { get; set; }

        [Output]
        public ITaskItem[] PackageConflictOverrides { get; set; }

        [Output]
        public ITaskItem[] UsedRuntimeFrameworks { get; set; }

        private static readonly bool s_allowCacheLookup = Environment.GetEnvironmentVariable(ALLOW_TARGETING_PACK_CACHING) != "0";

        public ResolveTargetingPackAssets()
        {
        }

        protected override void ExecuteCore()
        {
            StronglyTypedInputs inputs = GetInputs();

            string cacheKey = inputs.CacheKey();

            ResolvedAssetsCacheEntry results;

            if (s_allowCacheLookup &&
                BuildEngine4?.GetRegisteredTaskObject(
                    cacheKey,
                    RegisteredTaskObjectLifetime.AppDomain /* really "until process exit" */)
                  is ResolvedAssetsCacheEntry cacheEntry)
            {
                // NOTE: It's conceivably possible that the user modified the targeting
                //       packs between builds. Since that is extremely rare and the standard
                //       user scenario reads the targeting pack contents over and over without
                //       modification, we're electing not to check for file modification and
                //       returning any cached results that we have.

                results = cacheEntry;
            }
            else
            {
                results = Resolve(inputs, BuildEngine4);

                if (s_allowCacheLookup)
                {
                    BuildEngine4?.RegisterTaskObject(cacheKey, results, RegisteredTaskObjectLifetime.AppDomain, allowEarlyCollection: true);
                }
            }

            foreach (var error in results.Errors)
            {
                Log.LogError(error);
            }

            ReferencesToAdd = results.ReferencesToAdd;
            AnalyzersToAdd = results.AnalyzersToAdd;
            PlatformManifests = results.PlatformManifests;
            PackageConflictOverrides = results.PackageConflictOverrides;
            PackageConflictPreferredPackages = results.PackageConflictPreferredPackages;
            UsedRuntimeFrameworks = results.UsedRuntimeFrameworks;
        }

        internal StronglyTypedInputs GetInputs() => new(
                        FrameworkReferences,
                        ResolvedTargetingPacks,
                        RuntimeFrameworks,
                        GenerateErrorForMissingTargetingPacks,
                        NuGetRestoreSupported,
                        DisableTransitiveFrameworkReferenceDownloads,
                        NetCoreTargetingPackRoot,
                        ProjectLanguage);

        private static ResolvedAssetsCacheEntry Resolve(StronglyTypedInputs inputs, IBuildEngine4 buildEngine)
        {
            List<TaskItem> referencesToAdd = new();
            List<TaskItem> analyzersToAdd = new();
            List<TaskItem> platformManifests = new();
            List<TaskItem> packageConflictOverrides = new();
            List<string> preferredPackages = new();
            List<string> errors = new();

            var resolvedTargetingPacks = inputs.ResolvedTargetingPacks
                .ToDictionary(
                    tp => tp.Name,
                    StringComparer.OrdinalIgnoreCase);

            FrameworkReference[] frameworkReferences = inputs.FrameworkReferences;

            foreach (var frameworkReference in frameworkReferences)
            {
                bool foundTargetingPack = resolvedTargetingPacks.TryGetValue(frameworkReference.Name, out TargetingPack targetingPack);
                string targetingPackRoot = targetingPack?.Path;

                if (string.IsNullOrEmpty(targetingPackRoot) || !Directory.Exists(targetingPackRoot))
                {
                    if (inputs.GenerateErrorForMissingTargetingPacks)
                    {
                        if (!foundTargetingPack)
                        {
                            if (frameworkReference.Name.Equals("Microsoft.Maui.Essentials", StringComparison.OrdinalIgnoreCase))
                            {
                                errors.Add(Strings.UnknownFrameworkReference_MauiEssentials);
                            }
                            else
                            {
                                errors.Add(string.Format(Strings.UnknownFrameworkReference, frameworkReference.Name));
                            }

                        }
                        else
                        {
                            if (inputs.DisableTransitiveFrameworkReferences)
                            {
                                errors.Add(string.Format(Strings.TargetingPackNotRestored_TransitiveDisabled, frameworkReference.Name));
                            }
                            else if (inputs.NuGetRestoreSupported)
                            {
                                errors.Add(string.Format(Strings.TargetingPackNeedsRestore, frameworkReference.Name));
                            }
                            else
                            {
                                errors.Add(string.Format(
                                    Strings.TargetingApphostPackMissingCannotRestore,
                                    "Targeting",
                                    $"{inputs.NetCoreTargetingPackRoot}\\{targetingPack.NuGetPackageId ?? ""}",
                                    targetingPack.TargetFramework ?? "",
                                    targetingPack.NuGetPackageId ?? "",
                                    targetingPack.NuGetPackageVersion ?? ""
                                    ));
                            }
                        }
                    }
                }
                else
                {
                    string targetingPackFormat = targetingPack.Format;

                    if (targetingPackFormat.Equals("NETStandardLegacy", StringComparison.OrdinalIgnoreCase))
                    {
                        AddNetStandardTargetingPackAssets(targetingPack, targetingPackRoot, referencesToAdd);
                    }
                    else
                    {
                        string targetingPackTargetFramework = targetingPack.TargetFramework;
                        if (string.IsNullOrEmpty(targetingPackTargetFramework))
                        {
                            targetingPackTargetFramework = "netcoreapp3.0";
                        }

                        string targetingPackDataPath = Path.Combine(targetingPackRoot, "data");

                        string targetingPackDllFolder = Path.Combine(targetingPackRoot, "ref", targetingPackTargetFramework);

                        //  Fall back to netcoreapp5.0 folder if looking for net5.0 and it's not found
                        if (!Directory.Exists(targetingPackDllFolder) &&
                            targetingPackTargetFramework.Equals("net5.0", StringComparison.OrdinalIgnoreCase))
                        {
                            targetingPackTargetFramework = "netcoreapp5.0";
                            targetingPackDllFolder = Path.Combine(targetingPackRoot, "ref", targetingPackTargetFramework);
                        }

                        string platformManifestPath = Path.Combine(targetingPackDataPath, "PlatformManifest.txt");

                        string packageOverridesPath = Path.Combine(targetingPackDataPath, "PackageOverrides.txt");

                        string frameworkListPath = Path.Combine(targetingPackDataPath, "FrameworkList.xml");

                        FrameworkListDefinition definition = new(
                            frameworkListPath,
                            targetingPackRoot,
                            targetingPackDllFolder,
                            targetingPack.Name,
                            targetingPack.Profile,
                            targetingPack.NuGetPackageId,
                            targetingPack.NuGetPackageVersion,
                            inputs.ProjectLanguage);

                        AddItemsFromFrameworkList(definition, buildEngine, referencesToAdd, analyzersToAdd);

                        if (File.Exists(platformManifestPath))
                        {
                            platformManifests.Add(new TaskItem(platformManifestPath));
                        }

                        if (File.Exists(packageOverridesPath))
                        {
                            packageConflictOverrides.Add(CreatePackageOverride(targetingPack.NuGetPackageId, packageOverridesPath));
                        }

                        preferredPackages.AddRange(targetingPack.PackageConflictPreferredPackages.Split(';'));
                    }
                }
            }

            //  Calculate which RuntimeFramework items should actually be used based on framework references
            HashSet<string> frameworkReferenceNames = new(frameworkReferences.Select(fr => fr.Name), StringComparer.OrdinalIgnoreCase);

            //  Filter out duplicate references (which can happen when referencing two different profiles that overlap)
            List<TaskItem> deduplicatedReferences = DeduplicateItems(referencesToAdd);
            List<TaskItem> deduplicatedAnalyzers = DeduplicateItems(analyzersToAdd);

            ResolvedAssetsCacheEntry newCacheEntry = new()
            {
                ReferencesToAdd = deduplicatedReferences.Distinct().ToArray(),
                AnalyzersToAdd = deduplicatedAnalyzers.Distinct().ToArray(),
                PlatformManifests = platformManifests.ToArray(),
                PackageConflictOverrides = packageConflictOverrides.ToArray(),
                PackageConflictPreferredPackages = string.Join(";", preferredPackages),
                UsedRuntimeFrameworks = inputs.RuntimeFrameworks.Where(rf => frameworkReferenceNames.Contains(rf.FrameworkName))
                    .Select(rf => rf.Item)
                    .ToArray(),
                Errors = errors.ToArray(),
            };
            return newCacheEntry;
        }

        //  Get distinct items based on case-insensitive ItemSpec comparison
        private static List<TaskItem> DeduplicateItems(List<TaskItem> items)
        {
            HashSet<string> seenItemSpecs = new(StringComparer.OrdinalIgnoreCase);
            List<TaskItem> deduplicatedItems = new(items.Count);
            foreach (var item in items)
            {
                if (seenItemSpecs.Add(item.ItemSpec))
                {
                    deduplicatedItems.Add(item);
                }
            }
            return deduplicatedItems;
        }

        private static TaskItem CreatePackageOverride(string runtimeFrameworkName, string packageOverridesPath)
        {
            TaskItem packageOverride = new(runtimeFrameworkName);
            packageOverride.SetMetadata("OverriddenPackages", File.ReadAllText(packageOverridesPath));
            return packageOverride;
        }

        private static void AddNetStandardTargetingPackAssets(TargetingPack targetingPack, string targetingPackRoot, List<TaskItem> referencesToAdd)
        {
            string targetingPackTargetFramework = targetingPack.TargetFramework;
            string targetingPackAssetPath = Path.Combine(targetingPackRoot, "build", targetingPackTargetFramework, "ref");

            foreach (var dll in Directory.GetFiles(targetingPackAssetPath, "*.dll"))
            {
                var reference = CreateItem(
                    dll,
                    targetingPack.Name,
                    targetingPack.NuGetPackageId,
                    targetingPack.NuGetPackageVersion);

                if (!Path.GetFileName(dll).Equals("netstandard.dll", StringComparison.OrdinalIgnoreCase))
                {
                    reference.SetMetadata("Facade", "true");
                }

                referencesToAdd.Add(reference);
            }
        }

        private static void AddItemsFromFrameworkList(FrameworkListDefinition definition, IBuildEngine4 buildEngine4, List<TaskItem> referenceItems, List<TaskItem> analyzerItems)
        {
            string frameworkListKey = definition.CacheKey();

            if (s_allowCacheLookup &&
                buildEngine4?.GetRegisteredTaskObject(
                  frameworkListKey,
                  RegisteredTaskObjectLifetime.AppDomain)
                is FrameworkList cacheEntry)
            {
                // As above, we are not even checking timestamps here
                // and instead assuming that the targeting pack folder
                // is fully immutable.

                analyzerItems.AddRange(cacheEntry.Analyzers);
                referenceItems.AddRange(cacheEntry.References);
            }

            XDocument frameworkListDoc = XDocument.Load(definition.FrameworkListPath);

            string profile = definition.Profile;

            bool usePathElementsInFrameworkListAsFallBack =
                TestFirstFileInFrameworkListUsingAssemblyNameConvention(definition.TargetingPackDllFolder, frameworkListDoc);

            List<TaskItem> referenceItemsFromThisFramework = new();
            List<TaskItem> analyzerItemsFromThisFramework = new();

            foreach (var fileElement in frameworkListDoc.Root.Elements("File"))
            {
                if (!string.IsNullOrEmpty(profile))
                {
                    var profileAttributeValue = fileElement.Attribute("Profile")?.Value;

                    if (profileAttributeValue == null)
                    {
                        //  If profile was specified but this assembly doesn't belong to any profiles, don't reference it
                        continue;
                    }

                    var assemblyProfiles = profileAttributeValue.Split(';');
                    if (!assemblyProfiles.Contains(profile, StringComparer.OrdinalIgnoreCase))
                    {
                        //  Assembly wasn't in profile specified, so don't reference it
                        continue;
                    }
                }

                string referencedByDefaultAttributeValue = fileElement.Attribute("ReferencedByDefault")?.Value;
                if (referencedByDefaultAttributeValue != null &&
                    referencedByDefaultAttributeValue.Equals("false", StringComparison.OrdinalIgnoreCase))
                {
                    //  Don't automatically reference this assembly if it has ReferencedByDefault="false"
                    continue;
                }

                string itemType = fileElement.Attribute("Type")?.Value;
                bool isAnalyzer = itemType?.Equals("Analyzer", StringComparison.OrdinalIgnoreCase) ?? false;

                string assemblyName = fileElement.Attribute("AssemblyName").Value;

                string dllPath = usePathElementsInFrameworkListAsFallBack || isAnalyzer ?
                    Path.Combine(definition.TargetingPackRoot, fileElement.Attribute("Path").Value) :
                    GetDllPathViaAssemblyName(definition.TargetingPackDllFolder, assemblyName);

                var item = CreateItem(dllPath, definition.FrameworkReferenceName, definition.NuGetPackageId, definition.NuGetPackageVersion);

                item.SetMetadata("AssemblyName", assemblyName);
                item.SetMetadata("AssemblyVersion", fileElement.Attribute("AssemblyVersion").Value);
                item.SetMetadata("FileVersion", fileElement.Attribute("FileVersion").Value);
                item.SetMetadata("PublicKeyToken", fileElement.Attribute("PublicKeyToken").Value);

                if (isAnalyzer)
                {
                    string itemLanguage = fileElement.Attribute("Language")?.Value;

                    if (itemLanguage != null)
                    {
                        // expect cs instead of C#, fs rather than F# per NuGet conventions
                        string projectLanguage = definition.ProjectLanguage?.Replace('#', 's');

                        if (projectLanguage == null || !projectLanguage.Equals(itemLanguage, StringComparison.OrdinalIgnoreCase))
                        {
                            continue;
                        }
                    }

                    analyzerItemsFromThisFramework.Add(item);
                }
                else
                {
                    referenceItemsFromThisFramework.Add(item);
                }
            }

            if (s_allowCacheLookup)
            {
                FrameworkList list = new()
                {
                    Analyzers = analyzerItemsFromThisFramework.ToArray(),
                    References = referenceItemsFromThisFramework.ToArray(),
                };

                buildEngine4?.RegisterTaskObject(frameworkListKey, list, RegisteredTaskObjectLifetime.AppDomain, allowEarlyCollection: true);
            }

            analyzerItems.AddRange(analyzerItemsFromThisFramework);
            referenceItems.AddRange(referenceItemsFromThisFramework);
        }

        /// <summary>
        /// Due to https://github.com/dotnet/sdk/issues/12098 we fall back to use "Path" when "AssemblyName" will
        /// not resolve the actual dll.
        /// </summary>
        /// <returns>if use we should use "Path" element in frameworkList as a fallback</returns>
        private static bool TestFirstFileInFrameworkListUsingAssemblyNameConvention(string targetingPackDllFolder,
            XDocument frameworkListDoc)
        {
            bool usePathElementsInFrameworkListPathAsFallBack;
            var firstFileElement = frameworkListDoc.Root.Elements("File").FirstOrDefault();
            if (firstFileElement == null)
            {
                usePathElementsInFrameworkListPathAsFallBack = false;
            }
            else
            {
                string dllPath = GetDllPathViaAssemblyName(targetingPackDllFolder, firstFileElement);

                usePathElementsInFrameworkListPathAsFallBack = !File.Exists(dllPath);
            }

            return usePathElementsInFrameworkListPathAsFallBack;
        }

        private static string GetDllPathViaAssemblyName(string targetingPackDllFolder, XElement fileElement)
        {
            string assemblyName = fileElement.Attribute("AssemblyName").Value;
            return GetDllPathViaAssemblyName(targetingPackDllFolder, assemblyName);
        }

        private static string GetDllPathViaAssemblyName(string targetingPackDllFolder, string assemblyName)
        {
            var dllPath = Path.Combine(targetingPackDllFolder, assemblyName + ".dll");
            return dllPath;
        }

        private static TaskItem CreateItem(string dll, ITaskItem targetingPack)
        {
            return CreateItem(
                dll,
                targetingPack.ItemSpec,
                targetingPack.GetMetadata(MetadataKeys.NuGetPackageId),
                targetingPack.GetMetadata(MetadataKeys.NuGetPackageVersion));
        }

        private static TaskItem CreateItem(string dll, string frameworkReferenceName, string nuGetPackageId, string nuGetPackageVersion)
        {
            var reference = new TaskItem(dll);

            reference.SetMetadata(MetadataKeys.ExternallyResolved, "true");
            reference.SetMetadata(MetadataKeys.Private, "false");
            reference.SetMetadata(MetadataKeys.NuGetPackageId, nuGetPackageId);
            reference.SetMetadata(MetadataKeys.NuGetPackageVersion, nuGetPackageVersion);

            reference.SetMetadata("FrameworkReferenceName", frameworkReferenceName);
            reference.SetMetadata("FrameworkReferenceVersion", nuGetPackageVersion);

            return reference;
        }

        internal class StronglyTypedInputs
        {
            public FrameworkReference[] FrameworkReferences { get; private set; }
            public TargetingPack[] ResolvedTargetingPacks { get; private set; }
            public RuntimeFramework[] RuntimeFrameworks { get; private set; }
            public bool GenerateErrorForMissingTargetingPacks { get; private set; }
            public bool NuGetRestoreSupported { get; private set; }
            public bool DisableTransitiveFrameworkReferences { get; private set; }
            public string NetCoreTargetingPackRoot { get; private set; }
            public string ProjectLanguage { get; private set; }

            public StronglyTypedInputs(
                ITaskItem[] frameworkReferences,
                ITaskItem[] resolvedTargetingPacks,
                ITaskItem[] runtimeFrameworks,
                bool generateErrorForMissingTargetingPacks,
                bool nuGetRestoreSupported,
                bool disableTransitiveFrameworkReferences,
                string netCoreTargetingPackRoot,
                string projectLanguage)
            {
                FrameworkReferences = frameworkReferences.Select(fr => new FrameworkReference(fr.ItemSpec)).ToArray();
                ResolvedTargetingPacks = resolvedTargetingPacks.Select(
                    item => new TargetingPack(
                        item.ItemSpec,
                        item.GetMetadata(MetadataKeys.Path),
                        item.GetMetadata("TargetingPackFormat"),
                        item.GetMetadata("TargetFramework"),
                        item.GetMetadata("Profile"),
                        item.GetMetadata(MetadataKeys.NuGetPackageId),
                        item.GetMetadata(MetadataKeys.NuGetPackageVersion),
                        item.GetMetadata(MetadataKeys.PackageConflictPreferredPackages)))
                    .ToArray();
                RuntimeFrameworks = runtimeFrameworks.Select(item => new RuntimeFramework(item.ItemSpec, item.GetMetadata(MetadataKeys.FrameworkName), item)).ToArray();
                GenerateErrorForMissingTargetingPacks = generateErrorForMissingTargetingPacks;
                NuGetRestoreSupported = nuGetRestoreSupported;
                DisableTransitiveFrameworkReferences = disableTransitiveFrameworkReferences;
                NetCoreTargetingPackRoot = netCoreTargetingPackRoot;
                ProjectLanguage = projectLanguage;
            }

            public string CacheKey()
            {
                StringBuilder cacheKeyBuilder = new(nameof(ResolveTargetingPackAssets) + nameof(StronglyTypedInputs));
                cacheKeyBuilder.AppendLine();

                foreach (var frameworkReference in FrameworkReferences)
                {
                    cacheKeyBuilder.AppendLine(frameworkReference.CacheKey());
                }
                cacheKeyBuilder.AppendLine();
                foreach (var resolvedTargetingPack in ResolvedTargetingPacks)
                {
                    cacheKeyBuilder.AppendLine(resolvedTargetingPack.CacheKey());
                }

                cacheKeyBuilder.AppendLine(nameof(RuntimeFrameworks));
                foreach (var runtimeFramework in RuntimeFrameworks)
                {
                    cacheKeyBuilder.AppendLine(runtimeFramework.CacheKey());
                }

                cacheKeyBuilder.AppendLine($"{nameof(GenerateErrorForMissingTargetingPacks)}={GenerateErrorForMissingTargetingPacks}");
                cacheKeyBuilder.AppendLine($"{nameof(NuGetRestoreSupported)}={NuGetRestoreSupported}");
                cacheKeyBuilder.AppendLine($"{nameof(DisableTransitiveFrameworkReferences)}={DisableTransitiveFrameworkReferences}");

                cacheKeyBuilder.AppendLine($"{nameof(NetCoreTargetingPackRoot)}={NetCoreTargetingPackRoot}");

                cacheKeyBuilder.AppendLine($"{nameof(ProjectLanguage)}={ProjectLanguage}");

                return cacheKeyBuilder.ToString();

            }
        }

        private class FrameworkList
        {
            public IReadOnlyList<TaskItem> Analyzers;
            public IReadOnlyList<TaskItem> References;
        }

        internal class FrameworkReference
        {
            public string Name { get; private set; }

            public FrameworkReference(string name)
            {
                Name = name;
            }

            public string CacheKey()
            {
                return $"FrameworkReference: {Name}";
            }
        }

        internal class TargetingPack
        {
            public string Name { get; private set; }
            public string Path { get; private set; }
            public string Format { get; private set; }
            public string TargetFramework { get; private set; }
            public string Profile { get; private set; }
            public string NuGetPackageId { get; private set; }
            public string NuGetPackageVersion { get; private set; }
            public string PackageConflictPreferredPackages { get; private set; }

            public TargetingPack(
                string name,
                string path,
                string format,
                string targetFramework,
                string profile,
                string nuGetPackageId,
                string nuGetPackageVersion,
                string packageConflictPreferredPackages)
            {
                Name = name;
                Path = path;
                Format = format;
                TargetFramework = targetFramework;
                Profile = profile;
                NuGetPackageId = nuGetPackageId;
                NuGetPackageVersion = nuGetPackageVersion;
                PackageConflictPreferredPackages = packageConflictPreferredPackages;
            }

            public string CacheKey()
            {
                StringBuilder builder = new();
                builder.AppendLine(nameof(TargetingPack));

                builder.AppendLine(Name);
                builder.AppendLine(Path);
                builder.AppendLine(Format);
                builder.AppendLine(TargetFramework);
                builder.AppendLine(Profile);
                builder.AppendLine(NuGetPackageId);
                builder.AppendLine(NuGetPackageVersion);
                builder.AppendLine(PackageConflictPreferredPackages);

                return builder.ToString();
            }
        }

        internal class RuntimeFramework
        {
            public string Name { get; private set; }
            public string FrameworkName { get; private set; }
            public readonly ITaskItem Item;

            public RuntimeFramework(string name, string frameworkName, ITaskItem item)
            {
                Name = name;
                FrameworkName = frameworkName;
                Item = item;
            }

            public string CacheKey()
            {
                return $"{nameof(RuntimeFramework)}: {Name} ({FrameworkName} {Item?.GetMetadata(MetadataKeys.Version)})";
            }
        }

        internal readonly struct FrameworkListDefinition
        {
            public readonly string FrameworkListPath;
            public readonly string TargetingPackRoot;
            public readonly string TargetingPackDllFolder;
            public readonly string ProjectLanguage;

            public readonly string FrameworkReferenceName;
            public readonly string Profile;
            public readonly string NuGetPackageId;
            public readonly string NuGetPackageVersion;

            public FrameworkListDefinition(string frameworkListPath,
                                           string targetingPackRoot,
                                           string targetingPackDllFolder,
                                           string frameworkReferenceName,
                                           string profile,
                                           string nuGetPackageId,
                                           string nuGetPackageVersion,
                                           string projectLanguage)
            {
                FrameworkListPath = frameworkListPath;
                TargetingPackRoot = targetingPackRoot;
                TargetingPackDllFolder = targetingPackDllFolder;
                ProjectLanguage = projectLanguage;

                FrameworkReferenceName = frameworkReferenceName;
                Profile = profile;
                NuGetPackageId = nuGetPackageId;
                NuGetPackageVersion = nuGetPackageVersion;
            }

            /// <summary>
            /// Construct a key for the framework-specific cache lookup.
            /// </summary>
            public string CacheKey()
            {
                // IMPORTANT: any input changes that can affect the output should be included in this key.
                StringBuilder keyBuilder = new(nameof(FrameworkListDefinition));
                keyBuilder.AppendLine();
                keyBuilder.AppendLine(FrameworkListPath);
                keyBuilder.AppendLine(TargetingPackRoot);
                keyBuilder.AppendLine(TargetingPackDllFolder);
                keyBuilder.AppendLine(FrameworkReferenceName);
                keyBuilder.AppendLine(Profile);
                keyBuilder.AppendLine(NuGetPackageId);
                keyBuilder.AppendLine(NuGetPackageVersion);
                keyBuilder.AppendLine(ProjectLanguage);

                string frameworkListKey = keyBuilder.ToString();
                return frameworkListKey;
            }
        }

        private class ResolvedAssetsCacheEntry
        {
            public ITaskItem[] ReferencesToAdd;
            public ITaskItem[] AnalyzersToAdd;
            public ITaskItem[] PlatformManifests;
            public string PackageConflictPreferredPackages;
            public ITaskItem[] PackageConflictOverrides;
            public ITaskItem[] UsedRuntimeFrameworks;
            public string[] Errors;
        }
    }
}