File: ResolvePackageAssets.cs
Web Access
Project: ..\..\..\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 System.Diagnostics;
using System.Security.Cryptography;
using Microsoft.Build.Evaluation;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using NuGet.Common;
using NuGet.Frameworks;
using NuGet.ProjectModel;
using NuGet.Versioning;
 
namespace Microsoft.NET.Build.Tasks
{
    /// <summary>
    /// Resolve package assets from projects.assets.json into MSBuild items.
    ///
    /// Optimized for fast incrementality using an intermediate, binary assets.cache
    /// file that contains only the data that is actually returned for the current
    /// TFM/RID/etc. and written in a format that is easily decoded to ITaskItem
    /// arrays without undue allocation.
    /// </summary>
    public sealed class ResolvePackageAssets : TaskBase
    {
        #region Input Items
 
        /// <summary>
        /// Path to assets.json.
        /// </summary>
        public string ProjectAssetsFile { get; set; }
 
        /// <summary>
        /// Path to assets.cache file.
        /// </summary>
        [Required]
        public string ProjectAssetsCacheFile { get; set; }
 
        /// <summary>
        /// Path to project file (.csproj|.vbproj|.fsproj)
        /// </summary>
        [Required]
        public string ProjectPath { get; set; }
 
        /// <summary>
        /// TargetFramework to use for compile-time assets.
        /// </summary>
        [Required]
        public string TargetFramework { get; set; }
 
        /// <summary>
        /// RID to use for runtime assets (may be empty)
        /// </summary>
        public string RuntimeIdentifier { get; set; }
 
        /// <summary>
        /// The `any` RID can be passed to indicate that the assets should be resolved as a RID-agnostic application.
        /// We use this field to detect that case and ensure that we treat `any` the same as no RID at all.
        /// Essentially, if you see use of `RuntimeIdentifier` directly, you should ask "why?".
        /// </summary>
        private string EffectiveRuntimeIdentifier => !string.IsNullOrEmpty(RuntimeIdentifier) && RuntimeIdentifier != "any" ? RuntimeIdentifier : null;
 
        /// <summary>
        /// The platform library name for resolving copy local assets.
        /// </summary>
        public string PlatformLibraryName { get; set; }
 
        /// <summary>
        /// The runtime frameworks for resolving copy local assets.
        /// </summary>
        public ITaskItem[] RuntimeFrameworks { get; set; }
 
        /// <summary>
        /// Whether or not the the copy local is for a self-contained application.
        /// </summary>
        public bool IsSelfContained { get; set; }
 
        /// <summary>
        /// The languages to filter the resource assmblies for.
        /// </summary>
        public ITaskItem[] SatelliteResourceLanguages { get; set; }
 
        /// <summary>
        /// Do not write package assets cache to disk nor attempt to read previous cache from disk.
        /// </summary>
        public bool DisablePackageAssetsCache { get; set; }
 
        /// <summary>
        /// Do not generate transitive project references.
        /// </summary>
        public bool DisableTransitiveProjectReferences { get; set; }
 
        /// <summary>
        /// Disables FrameworkReferences from referenced projects or packages
        /// </summary>
        public bool DisableTransitiveFrameworkReferences { get; set; }
 
        /// <summary>
        /// Do not add references to framework assemblies as specified by packages.
        /// </summary>
        public bool DisableFrameworkAssemblies { get; set; }
 
        /// <summary>
        /// Whether or not resolved runtime target assets should be copied locally.
        /// </summary>
        public bool CopyLocalRuntimeTargetAssets { get; set; }
 
        /// <summary>
        /// Log messages from assets log to build error/warning/message.
        /// </summary>
        public bool EmitAssetsLogMessages { get; set; }
 
        /// <summary>
        /// Set ExternallyResolved=true metadata on reference items to indicate to MSBuild ResolveAssemblyReferences
        /// that these are resolved by an external system (in this case nuget) and therefore several steps can be
        /// skipped as an optimization.
        /// </summary>
        public bool MarkPackageReferencesAsExternallyResolved { get; set; }
 
        /// <summary>
        /// Project language ($(ProjectLanguage) in common targets -"VB" or "C#" or "F#" ).
        /// Impacts applicability of analyzer assets.
        /// </summary>
        public string ProjectLanguage { get; set; }
 
        /// <summary>
        /// Optional version of the compiler API (E.g. 'roslyn3.9', 'roslyn4.0')
        /// Impacts applicability of analyzer assets.
        /// </summary>
        public string CompilerApiVersion { get; set; }
 
        /// <summary>
        /// Check that there is at least one package dependency in the RID graph that is not in the RID-agnostic graph.
        /// Used as a heuristic to detect invalid RIDs.
        /// </summary>
        public bool EnsureRuntimePackageDependencies { get; set; }
 
        /// <summary>
        /// Specifies whether to validate that the version of the implicit platform packages in the assets
        /// file matches the version specified by <see cref="ExpectedPlatformPackages"/>
        /// </summary>
        public bool VerifyMatchingImplicitPackageVersion { get; set; }
 
        /// <summary>
        /// Implicitly referenced platform packages.  If set, then an error will be generated if the
        /// version of the specified packages from the assets file does not match the expected versions.
        /// </summary>
        public ITaskItem[] ExpectedPlatformPackages { get; set; }
 
        /// <summary>
        /// The RuntimeIdentifiers that shims will be generated for.
        /// </summary>
        public ITaskItem[] ShimRuntimeIdentifiers { get; set; }
 
        public ITaskItem[] PackageReferences { get; set; }
 
        /// <summary>
        /// The file name of Apphost asset.
        /// </summary>
        [Required]
        public string DotNetAppHostExecutableNameWithoutExtension { get; set; }
 
        /// <summary>
        /// True indicates we are doing a design-time build. Otherwise we are in a build.
        /// </summary>
        public bool DesignTimeBuild { get; set; }
 
        /// <summary>
        /// Eg: "Microsoft.NETCore.App;NETStandard.Library"
        /// </summary>
        [Required]
        public string DefaultImplicitPackages { get; set; }
 
        #endregion
 
        #region Output Items
        /// <summary>
        /// Full paths to assemblies from packages to pass to compiler as analyzers.
        /// </summary>
        [Output]
        public ITaskItem[] Analyzers { get; private set; }
 
        /// <summary>
        /// Full paths to assemblies from packages to compiler as references.
        /// </summary>
        [Output]
        public ITaskItem[] CompileTimeAssemblies { get; private set; }
 
        /// <summary>
        /// Content files from package that require preprocessing.
        /// Content files that do not require preprocessing are written directly to .g.props by nuget restore.
        /// </summary>
        [Output]
        public ITaskItem[] ContentFilesToPreprocess { get; private set; }
 
        /// <summary>
        /// Simple names of framework assemblies that packages request to be added as framework references.
        /// </summary>
        [Output]
        public ITaskItem[] FrameworkAssemblies { get; private set; }
 
        [Output]
        public ITaskItem[] FrameworkReferences { get; private set; }
 
        /// <summary>
        /// Full paths to native libraries from packages to run against.
        /// </summary>
        [Output]
        public ITaskItem[] NativeLibraries { get; private set; }
 
        /// <summary>
        /// The package folders from the assets file (ie the paths under which package assets may be found)
        /// </summary>
        [Output]
        public ITaskItem[] PackageFolders { get; set; }
 
        /// <summary>
        /// Full paths to satellite assemblies from packages.
        /// </summary>
        [Output]
        public ITaskItem[] ResourceAssemblies { get; private set; }
 
        /// <summary>
        /// Full paths to managed assemblies from packages to run against.
        /// </summary>
        [Output]
        public ITaskItem[] RuntimeAssemblies { get; private set; }
 
        /// <summary>
        /// Full paths to RID-specific assets that go in runtimes/ folder on publish.
        /// </summary>
        [Output]
        public ITaskItem[] RuntimeTargets { get; private set; }
 
        /// <summary>
        /// Relative paths to project files that are referenced transitively (but not directly).
        /// </summary>
        [Output]
        public ITaskItem[] TransitiveProjectReferences { get; private set; }
 
        /// <summary>
        /// Relative paths for Apphost for different ShimRuntimeIdentifiers with RuntimeIdentifier as meta data
        /// </summary>
        [Output]
        public ITaskItem[] ApphostsForShimRuntimeIdentifiers { get; private set; }
 
        [Output]
        public ITaskItem[] PackageDependencies { get; private set; }
 
        /// <summary>
        /// Filters and projects items produced by <see cref="ResolvePackageDependencies"/> for consumption by
        /// the dependencies tree, via design-time builds.
        /// </summary>
        /// <remarks>
        /// Changes to the implementation of output must be coordinated with <c>PackageRuleHandler</c>
        /// in the dotnet/project-system repo.
        /// </remarks>
        [Output]
        public ITaskItem[] PackageDependenciesDesignTime { get; private set; }
 
        /// <summary>
        /// List of symbol files (.pdb) related to NuGet packages.
        /// </summary>
        /// <remarks>
        /// Pdb files to be copied to the output directory
        /// </remarks>
        [Output]
        public ITaskItem[] DebugSymbolsFiles { get; private set; }
 
        /// <summary>
        /// List of xml files related to NuGet packages.
        /// </summary>
        /// <remarks>
        ///  The XML files should only be included in the publish output if PublishReferencesDocumentationFiles is true
        /// </remarks>
        [Output]
        public ITaskItem[] ReferenceDocumentationFiles { get; private set; }
 
        #endregion
 
        /// <summary>
        /// Messages from the assets file.
        /// These are logged directly and therefore not returned to the targets (note private here).
        /// However,they are still stored as ITaskItem[] so that the same cache reader/writer code
        /// can be used for message items and asset items.
        /// </summary>
        private ITaskItem[] _logMessages;
 
        ////////////////////////////////////////////////////////////////////////////////////////////////////
        // Package Asset Cache File Format Details
        //
        // Encodings of Int32, Byte[], String as defined by System.IO.BinaryReader/Writer.
        //
        // There are 3 sections, written in the following order:
        //
        // 1. Header
        // ---------
        // Encodes format and enough information to quickly decide if cache is still valid.
        //
        // Header:
        //   Int32 Signature: Spells PKGA ("package assets") when 4 little-endian bytes are interpreted as ASCII chars.
        //   Int32 Version: Increased whenever format changes to prevent issues when building incrementally with a different SDK.
        //   Byte[] SettingsHash: SHA-256 of settings that require the cache to be invalidated when changed.
        //   Int32 MetadataStringTableOffset: Byte offset in file to start of the metadata string table.
        //
        // 2. ItemGroup[] ItemGroups
        // --------------
        // There is one ItemGroup for each ITaskItem[] output (Analyzers, CompileTimeAssemblies, etc.)
        // Count and order of item groups is constant and therefore not encoded in to the file.
        //
        // ItemGroup:
        //   Int32   ItemCount
        //   Item[]  Items
        //
        // Item:
        //    String      ItemSpec (not index to string table because it generally unique)
        //    Int32       MetadataCount
        //    Metadata[]  Metadata
        //
        // Metadata:
        //    Int32 Key: Index in to MetadataStringTable for metadata key
        //    Int32 Value: Index in to MetadataStringTable for metadata value
        //
        // 3. MetadataStringTable
        // ----------------------
        // Indexes keys and values of item metadata to compress the cache file
        //
        // MetadataStringTable:
        //    Int32 MetadataStringCount
        //    String[] MetadataStrings
        ////////////////////////////////////////////////////////////////////////////////////////////////////
 
        private const int CacheFormatSignature = ('P' << 0) | ('K' << 8) | ('G' << 16) | ('A' << 24);
        private const int CacheFormatVersion = 12;
        private static readonly Encoding TextEncoding = Encoding.UTF8;
        private const int SettingsHashLength = 256 / 8;
        private HashAlgorithm CreateSettingsHash() => SHA256.Create();
 
        protected override void ExecuteCore()
        {
            if (string.IsNullOrEmpty(ProjectAssetsFile))
            {
                throw new BuildErrorException(Strings.AssetsFileNotSet);
            }
 
            ReadItemGroups();
            SetImplicitMetadataForCompileTimeAssemblies();
            SetImplicitMetadataForFrameworkAssemblies();
            LogMessagesToMSBuild();
        }
 
        private void ReadItemGroups()
        {
            using (var reader = new CacheReader(this))
            {
                // NOTE: Order (alphabetical by group name followed by log messages) must match writer.
                Analyzers = reader.ReadItemGroup();
                ApphostsForShimRuntimeIdentifiers = reader.ReadItemGroup();
                CompileTimeAssemblies = reader.ReadItemGroup();
                ContentFilesToPreprocess = reader.ReadItemGroup();
                DebugSymbolsFiles = reader.ReadItemGroup();
                FrameworkAssemblies = reader.ReadItemGroup();
                FrameworkReferences = reader.ReadItemGroup();
                NativeLibraries = reader.ReadItemGroup();
                PackageDependencies = reader.ReadItemGroup();
                PackageDependenciesDesignTime = reader.ReadItemGroup();
                PackageFolders = reader.ReadItemGroup();
                ReferenceDocumentationFiles = reader.ReadItemGroup();
                ResourceAssemblies = reader.ReadItemGroup();
                RuntimeAssemblies = reader.ReadItemGroup();
                RuntimeTargets = reader.ReadItemGroup();
                TransitiveProjectReferences = reader.ReadItemGroup();
 
                _logMessages = reader.ReadItemGroup();
            }
        }
 
        private void SetImplicitMetadataForCompileTimeAssemblies()
        {
            string externallyResolved = MarkPackageReferencesAsExternallyResolved ? "true" : "";
 
            foreach (var item in CompileTimeAssemblies)
            {
                item.SetMetadata(MetadataKeys.NuGetSourceType, "Package");
                item.SetMetadata(MetadataKeys.Private, "false");
                item.SetMetadata(MetadataKeys.HintPath, item.ItemSpec);
                item.SetMetadata(MetadataKeys.ExternallyResolved, externallyResolved);
            }
        }
 
        private void SetImplicitMetadataForFrameworkAssemblies()
        {
            foreach (var item in FrameworkAssemblies)
            {
                item.SetMetadata(MetadataKeys.NuGetIsFrameworkReference, "true");
                item.SetMetadata(MetadataKeys.NuGetSourceType, "Package");
                item.SetMetadata(MetadataKeys.Pack, "false");
                item.SetMetadata(MetadataKeys.Private, "false");
            }
        }
 
        private void LogMessagesToMSBuild()
        {
            if (!EmitAssetsLogMessages)
            {
                return;
            }
 
            foreach (var item in _logMessages)
            {
                Log.Log(
                    new Message(
                        text: item.ItemSpec,
                        level: GetMessageLevel(item.GetMetadata(MetadataKeys.Severity)),
                        code: item.GetMetadata(MetadataKeys.DiagnosticCode),
                        file: ProjectPath));
            }
        }
 
        private static MessageLevel GetMessageLevel(string severity)
        {
            switch (severity)
            {
                case nameof(LogLevel.Error):
                    return MessageLevel.Error;
                case nameof(LogLevel.Warning):
                    return MessageLevel.Warning;
                default:
                    return MessageLevel.NormalImportance;
            }
        }
 
        internal byte[] HashSettings()
        {
            using (var stream = new MemoryStream())
            {
                using (var writer = new BinaryWriter(stream, TextEncoding, leaveOpen: true))
                {
                    writer.Write(DisablePackageAssetsCache);
                    writer.Write(DisableFrameworkAssemblies);
                    writer.Write(CopyLocalRuntimeTargetAssets);
                    writer.Write(DisableTransitiveProjectReferences);
                    writer.Write(DisableTransitiveFrameworkReferences);
                    writer.Write(DotNetAppHostExecutableNameWithoutExtension);
                    writer.Write(EmitAssetsLogMessages);
                    writer.Write(EnsureRuntimePackageDependencies);
                    writer.Write(MarkPackageReferencesAsExternallyResolved);
                    if (PackageReferences != null)
                    {
                        foreach (var packageReference in PackageReferences)
                        {
                            writer.Write(packageReference.ItemSpec ?? "");
                            writer.Write(packageReference.GetMetadata(MetadataKeys.Version));
                            writer.Write(packageReference.GetMetadata(MetadataKeys.Publish));
                        }
                    }
                    if (ExpectedPlatformPackages != null)
                    {
                        foreach (var implicitPackage in ExpectedPlatformPackages)
                        {
                            writer.Write(implicitPackage.ItemSpec ?? "");
                            writer.Write(implicitPackage.GetMetadata(MetadataKeys.Version) ?? "");
                        }
                    }
                    writer.Write(ProjectAssetsCacheFile);
                    writer.Write(ProjectAssetsFile ?? "");
                    writer.Write(PlatformLibraryName ?? "");
                    if (RuntimeFrameworks != null)
                    {
                        foreach (var framework in RuntimeFrameworks)
                        {
                            writer.Write(framework.ItemSpec ?? "");
                        }
                    }
                    writer.Write(IsSelfContained);
                    if (SatelliteResourceLanguages != null)
                    {
                        foreach (var language in SatelliteResourceLanguages)
                        {
                            writer.Write(language.ItemSpec ?? "");
                        }
                    }
                    writer.Write(ProjectLanguage ?? "");
                    writer.Write(CompilerApiVersion ?? "");
                    writer.Write(ProjectPath);
                    // we want to ensure uniqueness of results, so even though `any` is No RID for purposes of Task logic,
                    // we continue to treat it distinctly for hashing
                    writer.Write(RuntimeIdentifier ?? "");
                    if (ShimRuntimeIdentifiers != null)
                    {
                        foreach (var r in ShimRuntimeIdentifiers)
                        {
                            writer.Write(r.ItemSpec ?? "");
                        }
                    }
                    writer.Write(TargetFramework);
                    writer.Write(VerifyMatchingImplicitPackageVersion);
                    writer.Write(DefaultImplicitPackages ?? "");
                }
 
                stream.Position = 0;
 
                using (var hash = CreateSettingsHash())
                {
                    return hash.ComputeHash(stream);
                }
            }
        }
 
        private sealed class CacheReader : IDisposable
        {
            private BinaryReader _reader;
            private string[] _metadataStringTable;
 
            public CacheReader(ResolvePackageAssets task)
            {
                byte[] settingsHash = task.HashSettings();
 
                if (!task.DisablePackageAssetsCache)
                {
                    // I/O errors can occur here if there are parallel calls to resolve package assets
                    // for the same project configured with the same intermediate directory. This can
                    // (for example) happen when design-time builds and real builds overlap.
                    //
                    // If there is an I/O error, then we fall back to the same in-memory approach below
                    // as when DisablePackageAssetsCache is set to true.
                    try
                    {
                        _reader = CreateReaderFromDisk(task, settingsHash);
                    }
                    catch (IOException) { }
                    catch (UnauthorizedAccessException) { }
                }
 
                if (_reader == null)
                {
                    _reader = CreateReaderFromMemory(task, settingsHash);
                }
 
                ReadMetadataStringTable();
            }
 
            private static BinaryReader CreateReaderFromMemory(ResolvePackageAssets task, byte[] settingsHash)
            {
                if (!task.DisablePackageAssetsCache)
                {
                    task.Log.LogMessage(MessageImportance.High, Strings.UnableToUsePackageAssetsCache_Info);
                }
 
                Stream stream;
                using (var writer = new CacheWriter(task))
                {
                    stream = writer.WriteToMemoryStream();
                }
 
                return OpenCacheStream(stream, settingsHash);
            }
 
            private static BinaryReader CreateReaderFromDisk(ResolvePackageAssets task, byte[] settingsHash)
            {
                Debug.Assert(!task.DisablePackageAssetsCache);
 
                BinaryReader reader = null;
                try
                {
                    if (IsCacheFileUpToDate())
                    {
                        reader = OpenCacheFile(task.ProjectAssetsCacheFile, settingsHash);
                    }
                }
                catch (IOException) { }
                catch (InvalidDataException) { }
                catch (UnauthorizedAccessException) { }
 
                if (reader == null)
                {
                    using (var writer = new CacheWriter(task))
                    {
                        if (writer.CanWriteToCacheFile)
                        {
                            writer.WriteToCacheFile();
                            reader = OpenCacheFile(task.ProjectAssetsCacheFile, settingsHash);
                        }
                        else
                        {
                            var stream = writer.WriteToMemoryStream();
                            reader = OpenCacheStream(stream, settingsHash);
                        }
                    }
                }
 
                return reader;
 
                bool IsCacheFileUpToDate() => File.GetLastWriteTimeUtc(task.ProjectAssetsCacheFile) > File.GetLastWriteTimeUtc(task.ProjectAssetsFile);
            }
 
            private static BinaryReader OpenCacheStream(Stream stream, byte[] settingsHash)
            {
                var reader = new BinaryReader(stream, TextEncoding, leaveOpen: false);
 
                try
                {
                    ValidateHeader(reader, settingsHash);
                }
                catch
                {
                    reader.Dispose();
                    throw;
                }
 
                return reader;
            }
 
            private static BinaryReader OpenCacheFile(string path, byte[] settingsHash)
            {
                var stream = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.Read);
                return OpenCacheStream(stream, settingsHash);
            }
 
            private static void ValidateHeader(BinaryReader reader, byte[] settingsHash)
            {
                if (reader.ReadInt32() != CacheFormatSignature
                    || reader.ReadInt32() != CacheFormatVersion
                    || !reader.ReadBytes(SettingsHashLength).SequenceEqual(settingsHash))
                {
                    throw new InvalidDataException();
                }
            }
 
            private void ReadMetadataStringTable()
            {
                int stringTablePosition = _reader.ReadInt32();
                int savedPosition = Position;
                Position = stringTablePosition;
 
                _metadataStringTable = new string[_reader.ReadInt32()];
                for (int i = 0; i < _metadataStringTable.Length; i++)
                {
                    _metadataStringTable[i] = _reader.ReadString();
                }
 
                Position = savedPosition;
            }
 
            private int Position
            {
                get => checked((int)_reader.BaseStream.Position);
                set => _reader.BaseStream.Position = value;
            }
 
            public void Dispose()
            {
                _reader.Dispose();
            }
 
            internal ITaskItem[] ReadItemGroup()
            {
                var items = new ITaskItem[_reader.ReadInt32()];
 
                for (int i = 0; i < items.Length; i++)
                {
                    items[i] = ReadItem();
                }
 
                return items;
            }
 
            private ITaskItem ReadItem()
            {
                var item = new TaskItem(_reader.ReadString());
                int metadataCount = _reader.ReadInt32();
 
                for (int i = 0; i < metadataCount; i++)
                {
                    string key = _metadataStringTable[_reader.ReadInt32()];
                    string value = _metadataStringTable[_reader.ReadInt32()];
                    item.SetMetadata(key, value);
                }
 
                return item;
            }
        }
 
        internal sealed class CacheWriter : IDisposable
        {
            private const int InitialStringTableCapacity = 32;
 
            private ResolvePackageAssets _task;
            private BinaryWriter _writer;
            private LockFile _lockFile;
            private LockFileTarget _compileTimeTarget;
            private IPackageResolver _packageResolver;
            private LockFileTarget _runtimeTarget;
            private Dictionary<string, int> _stringTable;
            private List<string> _metadataStrings;
            private List<int> _bufferedMetadata;
            private HashSet<string> _copyLocalPackageExclusions;
            private HashSet<string> _publishPackageExclusions;
            private Placeholder _metadataStringTablePosition;
            private string _targetFramework;
            private int _itemCount;
 
            public bool CanWriteToCacheFile { get; set; }
 
            private bool MismatchedAssetsFile => !CanWriteToCacheFile;
 
            private const string NetCorePlatformLibrary = "Microsoft.NETCore.App";
 
            private const char RelatedPropertySeparator = ';';
 
            /// <summary>
            /// This constructor should only be used for testing - IPackgeResolver carries a lot of
            /// state so using mocks really help with testing this component.
            /// </summary>
            public CacheWriter(ResolvePackageAssets task, IPackageResolver resolver) : this(task)
            {
                _packageResolver = resolver;
            }
 
            public CacheWriter(ResolvePackageAssets task)
            {
                _targetFramework = task.TargetFramework;
 
                _task = task;
                _lockFile = new LockFileCache(task).GetLockFile(task.ProjectAssetsFile);
                _packageResolver = NuGetPackageResolver.CreateResolver(_lockFile);
 
                //  If we are doing a design-time build, we do not want to fail the build if we can't find the
                //  target framework and/or runtime identifier in the assets file.  This is because the design-time
                //  build needs to succeed in order to get the right information in order to run a restore in order
                //  to write the assets file with the correct information.
 
                //  So if we can't find the right target in the lock file and are doing a design-time build, we use
                //  an empty lock file target instead of throwing an error, and we don't save the results to the
                //  cache file.
                CanWriteToCacheFile = true;
                if (task.DesignTimeBuild)
                {
                    _compileTimeTarget = _lockFile.GetTargetAndReturnNullIfNotFound(_targetFramework, runtimeIdentifier: null);
                    _runtimeTarget = _lockFile.GetTargetAndReturnNullIfNotFound(_targetFramework, runtimeIdentifier: _task.EffectiveRuntimeIdentifier);
                    if (_compileTimeTarget == null)
                    {
                        _compileTimeTarget = new LockFileTarget();
                        CanWriteToCacheFile = false;
                    }
                    if (_runtimeTarget == null)
                    {
                        _runtimeTarget = new LockFileTarget();
                        CanWriteToCacheFile = false;
                    }
                }
                else
                {
                    _compileTimeTarget = _lockFile.GetTargetAndThrowIfNotFound(_targetFramework, runtimeIdentifier: null);
                    _runtimeTarget = _lockFile.GetTargetAndThrowIfNotFound(_targetFramework, runtimeIdentifier: _task.EffectiveRuntimeIdentifier);
                }
 
 
                _stringTable = new Dictionary<string, int>(InitialStringTableCapacity, StringComparer.Ordinal);
                _metadataStrings = new List<string>(InitialStringTableCapacity);
                _bufferedMetadata = new List<int>();
 
                //  If the assets file doesn't match the inputs, don't bother trying to compute package exclusions
                if (!MismatchedAssetsFile)
                {
                    ComputePackageExclusions();
                }
            }
 
            public void WriteToCacheFile()
            {
                Directory.CreateDirectory(Path.GetDirectoryName(_task.ProjectAssetsCacheFile));
                var stream = File.Open(_task.ProjectAssetsCacheFile, FileMode.Create, FileAccess.ReadWrite, FileShare.None);
                using (_writer = new BinaryWriter(stream, TextEncoding, leaveOpen: false))
                {
                    Write();
                }
            }
 
            public Stream WriteToMemoryStream()
            {
                var stream = new MemoryStream();
                _writer = new BinaryWriter(stream, TextEncoding, leaveOpen: true);
                Write();
                stream.Position = 0;
                return stream;
            }
 
            public void Dispose()
            {
                _writer?.Dispose();
                _writer = null;
            }
 
            private void FlushMetadata()
            {
                if (_itemCount == 0)
                {
                    return;
                }
 
                Debug.Assert((_bufferedMetadata.Count % 2) == 0);
 
                _writer.Write(_bufferedMetadata.Count / 2);
 
                foreach (int m in _bufferedMetadata)
                {
                    _writer.Write(m);
                }
 
                _bufferedMetadata.Clear();
            }
 
            private void Write()
            {
                WriteHeader();
                WriteItemGroups();
                WriteMetadataStringTable();
 
                // Write signature last so that we will not attempt to use an incomplete cache file and instead
                // regenerate it.
                WriteToPlaceholder(new Placeholder(0), CacheFormatSignature);
            }
 
            private void WriteHeader()
            {
                // Leave room for signature, which we only write at the very end so that we will
                // not attempt to use a cache file corrupted by a prior crash.
                WritePlaceholder();
 
                _writer.Write(CacheFormatVersion);
 
                _writer.Write(_task.HashSettings());
                _metadataStringTablePosition = WritePlaceholder();
            }
 
            private void WriteItemGroups()
            {
                // NOTE: Order (alphabetical by group name followed by log messages) must match reader.
                WriteItemGroup(WriteAnalyzers);
                WriteItemGroup(WriteApphostsForShimRuntimeIdentifiers);
                WriteItemGroup(WriteCompileTimeAssemblies);
                WriteItemGroup(WriteContentFilesToPreprocess);
                WriteItemGroup(WriteDebugSymbolsFiles);
                WriteItemGroup(WriteFrameworkAssemblies);
                WriteItemGroup(WriteFrameworkReferences);
                WriteItemGroup(WriteNativeLibraries);
                WriteItemGroup(WritePackageDependencies);
                WriteItemGroup(WritePackageDependenciesDesignTime);
                WriteItemGroup(WritePackageFolders);
                WriteItemGroup(WriteReferenceDocumentationFiles);
                WriteItemGroup(WriteResourceAssemblies);
                WriteItemGroup(WriteRuntimeAssemblies);
                WriteItemGroup(WriteRuntimeTargets);
                WriteItemGroup(WriteTransitiveProjectReferences);
 
                WriteItemGroup(WriteLogMessages);
            }
 
            private void WriteMetadataStringTable()
            {
                int savedPosition = Position;
 
                _writer.Write(_metadataStrings.Count);
 
                foreach (var s in _metadataStrings)
                {
                    _writer.Write(s);
                }
 
                WriteToPlaceholder(_metadataStringTablePosition, savedPosition);
            }
 
            private int Position
            {
                get => checked((int)_writer.BaseStream.Position);
                set => _writer.BaseStream.Position = value;
            }
 
            private struct Placeholder
            {
                public readonly int Position;
                public Placeholder(int position) { Position = position; }
            }
 
            private Placeholder WritePlaceholder()
            {
                var placeholder = new Placeholder(Position);
                _writer.Write(int.MinValue);
                return placeholder;
            }
 
            private void WriteToPlaceholder(Placeholder placeholder, int value)
            {
                int savedPosition = Position;
                Position = placeholder.Position;
                _writer.Write(value);
                Position = savedPosition;
            }
 
            class LibraryComparer : IEqualityComparer<(string, NuGetVersion)>
            {
                public bool Equals((string, NuGetVersion) l1, (string, NuGetVersion) l2)
                {
                    return StringComparer.OrdinalIgnoreCase.Equals(l1.Item1, l2.Item1)
                        && l1.Item2.Equals(l2.Item2);
 
                }
                public int GetHashCode((string, NuGetVersion) library)
                {
#if NET
                    return HashCode.Combine(
                        StringComparer.OrdinalIgnoreCase.GetHashCode(library.Item1),
                        library.Item2.GetHashCode());
#else
                    int hashCode = -1507694697;
                    hashCode = hashCode * -1521134295 + StringComparer.OrdinalIgnoreCase.GetHashCode(library.Item1);
                    hashCode = hashCode * -1521134295 + library.Item2.GetHashCode();
                    return hashCode;
#endif
                }
            }
 
            private void WriteAnalyzers()
            {
                AnalyzerResolver resolver = new(this);
 
                foreach (var library in _lockFile.Libraries)
                {
                    if (!library.IsPackage())
                    {
                        continue;
                    }
 
                    foreach (var file in library.Files)
                    {
                        resolver.AddFile(file, library);
                    }
 
                    resolver.CompleteLibraryAnalyzers();
                }
            }
 
            /// <summary>
            /// Resolves the correct analyzer assets from a NuGet package.
            /// </summary>
            /// <remarks>
            /// This allows packages to ship multiple analyzers that target different versions
            /// of the compiler. For example, a package may include:
            ///
            /// "analyzers/dotnet/roslyn3.7/analyzer.dll"
            /// "analyzers/dotnet/roslyn3.8/analyzer.dll"
            /// "analyzers/dotnet/roslyn4.0/analyzer.dll"
            ///
            /// When the <paramref name="compilerApiVersion"/> is 'roslyn3.9', only the assets
            /// in the folder with the highest applicable compiler version are picked.
            /// In this case,
            ///
            /// "analyzers/dotnet/roslyn3.8/analyzer.dll"
            ///
            /// will be picked, and the other analyzer assets will be excluded.
            /// </remarks>
            private class AnalyzerResolver
            {
                private readonly CacheWriter _cacheWriter;
                private readonly string _compilerNameSearchString;
                private readonly Version _compilerVersion;
                private Dictionary<(string, NuGetVersion), LockFileTargetLibrary> _targetLibraries;
                private List<(string, LockFileLibrary, Version)> _potentialAnalyzers;
                private Version _maxApplicableVersion;
 
                private Dictionary<(string, NuGetVersion), LockFileTargetLibrary> TargetLibraries =>
                    _targetLibraries ??=
                        _cacheWriter._compileTimeTarget.Libraries.ToDictionary(l => (l.Name, l.Version), new LibraryComparer());
 
                public AnalyzerResolver(CacheWriter cacheWriter)
                {
                    _cacheWriter = cacheWriter;
 
                    if (ParseCompilerApiVersion(_cacheWriter._task.CompilerApiVersion, out ReadOnlyMemory<char> compilerName, out Version compilerVersion))
                    {
#if NET
                        _compilerNameSearchString = string.Concat("/".AsSpan(), compilerName.Span);
#else
                        _compilerNameSearchString = "/" + compilerName;
#endif
                        _compilerVersion = compilerVersion;
                    }
                }
 
                public void AddFile(string file, LockFileLibrary library)
                {
                    if (NuGetUtils.IsApplicableAnalyzer(file, _cacheWriter._task.ProjectLanguage))
                    {
                        if (IsFileCompilerVersionSpecific(file, out Version fileCompilerVersion))
                        {
                            if (fileCompilerVersion > _compilerVersion)
                            {
                                // version is too high - skip this file
                                return;
                            }
 
                            _potentialAnalyzers ??= new List<(string, LockFileLibrary, Version)>();
                            _potentialAnalyzers.Add((file, library, fileCompilerVersion));
 
                            if (_maxApplicableVersion == null || fileCompilerVersion > _maxApplicableVersion)
                            {
                                _maxApplicableVersion = fileCompilerVersion;
                            }
                        }
                        else
                        {
                            // if this file isn't specific to a compiler version, just write it directly
                            WriteAnalyzer(file, library);
                        }
                    }
                }
 
                private bool IsFileCompilerVersionSpecific(string file, out Version fileCompilerVersion)
                {
                    fileCompilerVersion = null;
 
                    if (_compilerNameSearchString == null)
                    {
                        // unable to tell if this file is specific to a compiler version
                        return false;
                    }
 
                    int compilerNameStart = file.IndexOf(_compilerNameSearchString);
                    if (compilerNameStart == -1)
                    {
                        return false;
                    }
 
                    int compilerVersionStart = compilerNameStart + _compilerNameSearchString.Length;
                    int compilerVersionStop = file.IndexOf('/', compilerVersionStart);
                    if (compilerVersionStop == -1)
                    {
                        return false;
                    }
 
                    return TryParseVersion(file, compilerVersionStart, compilerVersionStop - compilerVersionStart, out fileCompilerVersion);
                }
 
                public void CompleteLibraryAnalyzers()
                {
                    if (_maxApplicableVersion != null && _potentialAnalyzers?.Count > 0)
                    {
                        foreach (var (file, library, version) in _potentialAnalyzers)
                        {
                            if (version == _maxApplicableVersion)
                            {
                                WriteAnalyzer(file, library);
                            }
                        }
                    }
 
                    // clear the variables that are scoped per library
                    _maxApplicableVersion = null;
                    _potentialAnalyzers?.Clear();
                }
 
                private void WriteAnalyzer(string file, LockFileLibrary library)
                {
                    if (TargetLibraries.TryGetValue((library.Name, library.Version), out var targetLibrary))
                    {
                        _cacheWriter.WriteItem(_cacheWriter._packageResolver.ResolvePackageAssetPath(targetLibrary, file), targetLibrary);
                    }
                }
 
                /// <summary>
                /// Parses the <paramref name="compilerApiVersion"/> string into its component parts:
                /// compilerName:, e.g. "roslyn"
                /// compilerVersion: e.g. 3.9
                /// </summary>
                private static bool ParseCompilerApiVersion(string compilerApiVersion, out ReadOnlyMemory<char> compilerName, out Version compilerVersion)
                {
                    compilerName = default;
                    compilerVersion = default;
 
                    if (string.IsNullOrEmpty(compilerApiVersion))
                    {
                        return false;
                    }
 
                    int compilerVersionStart = -1;
                    for (int i = 0; i < compilerApiVersion.Length; i++)
                    {
                        if (char.IsDigit(compilerApiVersion[i]))
                        {
                            compilerVersionStart = i;
                            break;
                        }
                    }
 
                    if (compilerVersionStart > 0)
                    {
                        if (TryParseVersion(compilerApiVersion, compilerVersionStart, out compilerVersion))
                        {
                            compilerName = compilerApiVersion.AsMemory(0, compilerVersionStart);
                            return true;
                        }
                    }
 
                    // didn't find a compiler name or version
                    return false;
                }
 
                private static bool TryParseVersion(string value, int startIndex, out Version version) =>
                    TryParseVersion(value, startIndex, value.Length - startIndex, out version);
 
                private static bool TryParseVersion(string value, int startIndex, int length, out Version version)
                {
#if NET
                    return Version.TryParse(value.AsSpan(startIndex, length), out version);
#else
                    return Version.TryParse(value.Substring(startIndex, length), out version);
#endif
                }
            }
 
            private void WriteItemGroup(Action writeItems)
            {
                var placeholder = WritePlaceholder();
                _itemCount = 0;
                writeItems();
                FlushMetadata();
                WriteToPlaceholder(placeholder, _itemCount);
            }
 
            private void WriteCompileTimeAssemblies()
            {
                WriteItems(
                    _compileTimeTarget,
                    package => package.CompileTimeAssemblies,
                    filter: null,
                    writeMetadata: (package, asset) =>
                    {
                        if (asset.Properties.TryGetValue(LockFileItem.AliasesProperty, out var aliases))
                        {
                            WriteMetadata(MetadataKeys.Aliases, aliases);
                        }
                    });
            }
 
            private void WriteContentFilesToPreprocess()
            {
                WriteItems(
                    _runtimeTarget,
                    p => p.ContentFiles,
                    filter: asset => !string.IsNullOrEmpty(asset.PPOutputPath),
                    writeMetadata: (package, asset) =>
                    {
                        WriteMetadata(MetadataKeys.BuildAction, asset.BuildAction.ToString());
                        WriteMetadata(MetadataKeys.CopyToOutput, asset.CopyToOutput.ToString());
                        WriteMetadata(MetadataKeys.PPOutputPath, asset.PPOutputPath);
                        WriteMetadata(MetadataKeys.OutputPath, asset.OutputPath);
                        WriteMetadata(MetadataKeys.CodeLanguage, asset.CodeLanguage);
                    });
            }
 
            private void WriteDebugSymbolsFiles()
            {
                WriteDebugItems(
                    p => p.RuntimeAssemblies,
                    MetadataKeys.PdbExtension);
            }
 
            private void WriteReferenceDocumentationFiles()
            {
                WriteDebugItems(
                    p => p.CompileTimeAssemblies,
                    MetadataKeys.XmlExtension);
            }
 
            private void WriteDebugItems(
                Func<LockFileTargetLibrary, IEnumerable<LockFileItem>> getAssets,
                string extension)
            {
                foreach (var library in _runtimeTarget.Libraries)
                {
                    if (!library.IsPackage())
                    {
                        continue;
                    }
 
                    foreach (LockFileItem asset in getAssets(library))
                    {
                        if (asset.IsPlaceholderFile() || !asset.Properties.ContainsKey(MetadataKeys.RelatedProperty))
                        {
                            continue;
                        }
 
                        string itemSpec = _packageResolver.ResolvePackageAssetPath(library, asset.Path);
 
                        string relatedExtensions = asset.Properties[MetadataKeys.RelatedProperty];
 
                        foreach (string fileExtension in relatedExtensions.Split(RelatedPropertySeparator))
                        {
                            if (StringComparer.InvariantCulture.Equals(fileExtension, extension))
                            {
                                string xmlFilePath = Path.ChangeExtension(itemSpec, fileExtension);
                                if (File.Exists(xmlFilePath))
                                {
                                    WriteItem(xmlFilePath, library);
                                }
                            }
                        }
                    }
                }
            }
 
            private void WriteFrameworkAssemblies()
            {
                if (_task.DisableFrameworkAssemblies)
                {
                    return;
                }
 
                //  Keep track of Framework assemblies that we've already written items for,
                //  in order to only create one item for each Framework assembly.
                //  This means that if multiple packages have a dependency on the same
                //  Framework assembly, we will no longer emit separate items for each one.
                //  This should make the logs a lot cleaner and easier to understand,
                //  and may improve perf.  If you really want to know all the packages
                //  that brought in a framework assembly, you can look in the assets
                //  file.
                var writtenFrameworkAssemblies = new HashSet<string>(StringComparer.Ordinal);
 
                foreach (var library in _compileTimeTarget.Libraries)
                {
                    if (!library.IsPackage())
                    {
                        continue;
                    }
 
                    foreach (string frameworkAssembly in library.FrameworkAssemblies)
                    {
                        if (writtenFrameworkAssemblies.Add(frameworkAssembly))
                        {
                            WriteItem(frameworkAssembly, library);
                        }
                    }
                }
            }
 
            private void WriteLogMessages()
            {
                static string GetSeverity(LogLevel level)
                {
                    switch (level)
                    {
                        case LogLevel.Warning: return nameof(LogLevel.Warning);
                        case LogLevel.Error: return nameof(LogLevel.Error);
                        default: return ""; // treated as info
                    }
                }
 
                foreach (var message in _lockFile.LogMessages)
                {
                    WriteItem(message.Message);
                    WriteMetadata(MetadataKeys.DiagnosticCode, message.Code.ToString());
                    WriteMetadata(MetadataKeys.Severity, GetSeverity(message.Level));
                }
 
                WriteAdditionalLogMessages();
            }
 
            /// <summary>
            /// Writes log messages which are not directly in the assets file, but are based on conditions
            /// this task evaluates
            /// </summary>
            private void WriteAdditionalLogMessages()
            {
                WriteUnsupportedRuntimeIdentifierMessageIfNecessary();
                WriteMismatchedPlatformPackageVersionMessageIfNecessary();
            }
 
            private void WriteUnsupportedRuntimeIdentifierMessageIfNecessary()
            {
                if (_task.EnsureRuntimePackageDependencies && !string.IsNullOrEmpty(_task.EffectiveRuntimeIdentifier))
                {
                    if (_compileTimeTarget.Libraries.Count >= _runtimeTarget.Libraries.Count)
                    {
                        WriteItem(string.Format(Strings.UnsupportedRuntimeIdentifier, _task.EffectiveRuntimeIdentifier));
                        WriteMetadata(MetadataKeys.Severity, nameof(LogLevel.Error));
                    }
                }
            }
 
            private static readonly char[] _specialNuGetVersionChars = new char[]
                {
                    '*',
                    '(', ')',
                    '[', ']'
                };
 
            private void WriteMismatchedPlatformPackageVersionMessageIfNecessary()
            {
                static bool hasTwoPeriods(string s)
                {
                    int firstPeriodIndex = s.IndexOf('.');
                    if (firstPeriodIndex < 0)
                    {
                        return false;
                    }
                    int secondPeriodIndex = s.IndexOf('.', firstPeriodIndex + 1);
                    return secondPeriodIndex >= 0;
                }
 
                if (_task.VerifyMatchingImplicitPackageVersion &&
                    _task.ExpectedPlatformPackages != null)
                {
                    foreach (var implicitPackage in _task.ExpectedPlatformPackages)
                    {
                        var packageName = implicitPackage.ItemSpec;
                        var expectedVersion = implicitPackage.GetMetadata(MetadataKeys.Version);
 
                        if (string.IsNullOrEmpty(packageName) ||
                            string.IsNullOrEmpty(expectedVersion) ||
                            //  If RuntimeFrameworkVersion was specified as a version range or a floating version,
                            //  then we can't compare the versions directly, so just skip the check
                            expectedVersion.IndexOfAny(_specialNuGetVersionChars) >= 0)
                        {
                            continue;
                        }
 
                        var restoredPackage = _runtimeTarget.GetLibrary(packageName);
                        if (restoredPackage != null)
                        {
                            var restoredVersion = restoredPackage.Version.ToNormalizedString();
 
                            //  Normalize expected version.  For example, converts "2.0" to "2.0.0"
                            if (!hasTwoPeriods(expectedVersion))
                            {
                                expectedVersion += ".0";
                            }
 
                            if (restoredVersion != expectedVersion)
                            {
                                WriteItem(string.Format(Strings.MismatchedPlatformPackageVersion,
                                                        packageName,
                                                        restoredVersion,
                                                        expectedVersion));
                                WriteMetadata(MetadataKeys.Severity, nameof(LogLevel.Error));
                            }
                        }
                    }
                }
            }
 
            private void WriteNativeLibraries()
            {
                WriteItems(
                    _runtimeTarget,
                    package => package.NativeLibraries,
                    writeMetadata: (package, asset) =>
                    {
                        WriteMetadata(MetadataKeys.AssetType, "native");
                        WriteCopyLocalMetadataIfNeeded(package, Path.GetFileName(asset.Path));
                    });
            }
 
            private void WriteApphostsForShimRuntimeIdentifiers()
            {
                if (!CanResolveApphostFromFrameworkReference())
                {
                    return;
                }
 
                if (_task.ShimRuntimeIdentifiers == null || _task.ShimRuntimeIdentifiers.Length == 0)
                {
                    return;
                }
 
                foreach (var runtimeIdentifier in _task.ShimRuntimeIdentifiers.Select(r => r.ItemSpec))
                {
                    bool throwIfAssetsFileTargetNotFound = !_task.DesignTimeBuild;
 
                    LockFileTarget runtimeTarget;
                    if (_task.DesignTimeBuild)
                    {
                        runtimeTarget = _lockFile.GetTargetAndReturnNullIfNotFound(_targetFramework, runtimeIdentifier) ?? new LockFileTarget();
                    }
                    else
                    {
                        runtimeTarget = _lockFile.GetTargetAndThrowIfNotFound(_targetFramework, runtimeIdentifier);
                    }
 
                    var apphostName = _task.DotNetAppHostExecutableNameWithoutExtension + ExecutableExtension.ForRuntimeIdentifier(runtimeIdentifier);
 
                    Tuple<string, LockFileTargetLibrary> resolvedPackageAssetPathAndLibrary = FindApphostInRuntimeTarget(apphostName, runtimeTarget);
 
                    WriteItem(resolvedPackageAssetPathAndLibrary.Item1, resolvedPackageAssetPathAndLibrary.Item2);
                    WriteMetadata(MetadataKeys.RuntimeIdentifier, runtimeIdentifier);
                }
            }
 
            /// <summary>
            /// After netcoreapp3.0 apphost is resolved during ProcessFrameworkReferences. It should return nothing here
            /// </summary>
            private bool CanResolveApphostFromFrameworkReference()
            {
                if (!CanWriteToCacheFile)
                {
                    //  If we can't write to the cache file, it's because this is a design-time build where the
                    //  TargetFramework doesn't match what's in the assets file.  So don't try looking up the
                    //  TargetFramework in the assets file.
                    return false;
                }
                else
                {
                    var targetFramework = _lockFile.GetTargetAndThrowIfNotFound(_targetFramework, null).TargetFramework;
 
                    if (targetFramework.Version.Major >= 3
                        && targetFramework.Framework.Equals(".NETCoreApp", StringComparison.OrdinalIgnoreCase))
                    {
                        return false;
                    }
 
                    return true;
                }
            }
 
            private void WritePackageFolders()
            {
                foreach (var packageFolder in _lockFile.PackageFolders)
                {
                    WriteItem(packageFolder.Path);
                }
            }
 
            private void WritePackageDependencies()
            {
                foreach (var library in _runtimeTarget.Libraries)
                {
                    if (library.IsPackage())
                    {
                        WriteItem(library.Name);
                    }
                }
            }
 
            private void WritePackageDependenciesDesignTime()
            {
                var implicitPackageReferences = CollectSDKReferencesDesignTime.GetImplicitPackageReferences(_task.DefaultImplicitPackages);
 
                // Scan PackageDependencies to build the set of packages in our target.
                var allowItemSpecs = GetPackageDependencies();
                var diagnosticLevels = GetPackageDiagnosticLevels();
 
                foreach (var package in _lockFile.Libraries)
                {
                    var packageVersion = package.Version.ToNormalizedString();
                    string packageId = $"{package.Name}/{packageVersion}";
 
                    // Find PackageDefinitions that match our allowed item specs
                    if (string.IsNullOrEmpty(package.Name) || !allowItemSpecs.Contains(packageId))
                    {
                        // Only include packages from the allow list.
                        // This excludes transitive packages and those from other targets.
                        continue;
                    }
 
                    var dependencyType = GetDependencyType(package.Type);
 
                    if (dependencyType == DependencyType.Package ||
                        dependencyType == DependencyType.Unresolved)
                    {
                        WriteItem(packageId);
                        WriteMetadata(MetadataKeys.Name, package.Name);
 
                        var version = packageVersion ?? string.Empty;
                        WriteMetadata(MetadataKeys.Version, version);
 
                        var isImplicitlyDefined = implicitPackageReferences.Contains(package.Name);
                        WriteMetadata(MetadataKeys.IsImplicitlyDefined, isImplicitlyDefined.ToString());
 
                        string resolvedPackagePath = _packageResolver.GetPackageDirectory(package.Name, package.Version);
                        var resolvedPath = resolvedPackagePath ?? string.Empty;
                        var resolved = !string.IsNullOrEmpty(resolvedPath);
                        WriteMetadata(MetadataKeys.Resolved, resolved.ToString());
 
                        string itemPath = package.Path ?? string.Empty;
                        var path = (resolved
                            ? resolvedPath
                            : itemPath) ?? string.Empty;
                        WriteMetadata(MetadataKeys.Path, path);
 
                        string diagnosticLevel = string.Empty;
                        if (diagnosticLevels?.TryGetValue(package.Name, out LogLevel level) ?? false)
                        {
                            diagnosticLevel = level.ToString();
                        }
                        WriteMetadata(MetadataKeys.DiagnosticLevel, diagnosticLevel);
                    }
                }
 
                HashSet<string> GetPackageDependencies()
                {
                    HashSet<string> projectFileDependencies = _lockFile.GetProjectFileDependencySet(_compileTimeTarget.Name);
 
                    HashSet<string> results = new(StringComparer.OrdinalIgnoreCase);
 
                    foreach (var package in _compileTimeTarget.Libraries)
                    {
                        if (projectFileDependencies.Contains(package.Name))
                        {
                            string itemSpec = GetPackageId(package);
 
                            bool added = results.Add(itemSpec);
 
                            Debug.Assert(added);
                        }
                    }
 
                    return results;
                }
 
                static string GetPackageId(LockFileTargetLibrary package) => $"{package.Name}/{package.Version.ToNormalizedString()}";
 
                Dictionary<string, LogLevel> GetPackageDiagnosticLevels()
                {
                    if (_lockFile.LogMessages.Count == 0)
                    {
                        return null;
                    }
 
                    var packageReverseDependencies = GetReverseDependencies();
                    var result = new Dictionary<string, LogLevel>();
                    for (int i = 0; i < _lockFile.LogMessages.Count; i++)
                    {
                        var message = _lockFile.LogMessages[i];
                        if (string.IsNullOrEmpty(message.LibraryId)) continue;
 
                        if (message.TargetGraphs is null || message.TargetGraphs.Count == 0 || message.TargetGraphs.Any(ForCurrentTargetFramework))
                        {
                            ApplyDiagnosticLevel(message.LibraryId, message.Level, result, packageReverseDependencies);
                        }
                    }
 
                    return result;
 
                    Dictionary<string, HashSet<string>> GetReverseDependencies()
                    {
                        var packageReverseDependencies = new Dictionary<string, HashSet<string>>(_compileTimeTarget.Libraries.Count, StringComparer.OrdinalIgnoreCase);
                        for (int i = 0; i < _compileTimeTarget.Libraries.Count; i++)
                        {
                            var parentPackage = _compileTimeTarget.Libraries[i];
                            if (string.IsNullOrEmpty(parentPackage.Name)) continue;
 
                            if (!packageReverseDependencies.ContainsKey(parentPackage.Name))
                            {
                                packageReverseDependencies[parentPackage.Name] = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
                            }
 
                            for (int j = 0; j < parentPackage.Dependencies.Count; j++)
                            {
                                var dependency = parentPackage.Dependencies[j].Id;
 
                                if (!packageReverseDependencies.TryGetValue(dependency, out HashSet<string> parentPackages))
                                {
                                    parentPackages = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
                                    packageReverseDependencies[dependency] = parentPackages;
                                }
 
                                parentPackages.Add(parentPackage.Name);
                            }
                        }
                        return packageReverseDependencies;
                    }
 
                    bool ForCurrentTargetFramework(string targetFramework)
                    {
                        var parts = targetFramework.Split(LockFile.DirectorySeparatorChar);
                        string alias = parts[0];
                        if (alias == _task.TargetFramework)
                        {
                            return true;
                        }
                        else
                        {
                            var parsedTargetGraph = NuGetFramework.Parse(alias);
                            alias = _lockFile.PackageSpec.TargetFrameworks
                                .FirstOrDefault(tf => tf.FrameworkName == parsedTargetGraph)
                                ?.TargetAlias ?? targetFramework;
                        }
 
                        return alias == _task.TargetFramework;
                    }
 
                    void ApplyDiagnosticLevel(string package, LogLevel messageLevel, Dictionary<string, LogLevel> diagnosticLevels, Dictionary<string, HashSet<string>> reverseDependencies)
                    {
                        if (!reverseDependencies.TryGetValue(package, out HashSet<string> parentPackages))
                        {
                            // The package is not used in the current TargetFramework
                            return;
                        }
 
                        if (diagnosticLevels.TryGetValue(package, out LogLevel cachedLevel))
                        {
                            // Only continue if we need to increase the level
                            if (cachedLevel >= messageLevel)
                            {
                                return;
                            }
                        }
 
                        diagnosticLevels[package] = messageLevel;
 
                        // Flow changes upwards, towards the direct PackageReference
                        foreach (var parentPackage in parentPackages)
                        {
                            ApplyDiagnosticLevel(parentPackage, messageLevel, diagnosticLevels, reverseDependencies);
                        }
                    }
                }
 
                static DependencyType GetDependencyType(string dependencyTypeString)
                {
                    Enum.TryParse(dependencyTypeString, ignoreCase: true, out DependencyType dependencyType);
                    return dependencyType;
                }
            }
 
            private void WriteResourceAssemblies()
            {
                WriteItems(
                    _runtimeTarget,
                    package => package.ResourceAssemblies.Where(asset =>
                        _task.SatelliteResourceLanguages == null ||
                        _task.SatelliteResourceLanguages.Any(lang =>
                            string.Equals(asset.Properties["locale"], lang.ItemSpec, StringComparison.OrdinalIgnoreCase))),
                    writeMetadata: (package, asset) =>
                    {
                        WriteMetadata(MetadataKeys.AssetType, "resources");
                        string locale = asset.Properties["locale"];
                        // Locales from packages can be free-form, so we normalize them to the standard
                        // forms here. If the locale is mixed-case, that can cause issues on case-sensitive
                        // file systems when the locale-specific assets are copied.
                        try
                        {
                            var normalizedLocale = System.Globalization.CultureInfo.GetCultureInfo(locale).Name;
                            if (normalizedLocale != locale)
                            {
                                var tfm = _lockFile.GetTargetAndThrowIfNotFound(_targetFramework, null).TargetFramework;
                                if (tfm.Version.Major >= 7)
                                {
                                    _task.Log.LogWarning(Strings.PackageContainsIncorrectlyCasedLocale, package.Name, package.Version.ToNormalizedString(), locale, normalizedLocale);
                                }
                                else
                                {
                                    // We emit low-priority messages here because some clients may interpret normal or higher messages
                                    // as warnings when they have codes, locations, etc.
                                    // Roslyn does similar for IDE-only analysis messages.
                                    _task.Log.LogMessage(MessageImportance.Low, Strings.PackageContainsIncorrectlyCasedLocale, package.Name, package.Version.ToNormalizedString(), locale, normalizedLocale);
                                }
                            }
                            locale = normalizedLocale;
                        }
                        catch (System.Globalization.CultureNotFoundException cnf)
                        {
                            var tfm = _lockFile.GetTargetAndThrowIfNotFound(_targetFramework, null).TargetFramework;
                            if (tfm.Version.Major >= 7)
                            {
                                _task.Log.LogWarning(Strings.PackageContainsUnknownLocale, package.Name, package.Version.ToNormalizedString(), cnf.InvalidCultureName);
                            }
                            else
                            {
                                // We emit low-priority messages here because some clients may interpret normal or higher messages
                                // as warnings when they have codes, locations, etc.
                                // Roslyn does similar for IDE-only analysis messages.
                                _task.Log.LogMessage(MessageImportance.Low, Strings.PackageContainsUnknownLocale, package.Name, package.Version.ToNormalizedString(), cnf.InvalidCultureName);
                            }
 
                            // We could potentially strip this unknown locale at this point, but we do not.
                            // Locale data can change over time (it's typically an OS database that's kept updated),
                            // and the data on the system running the build may not be the same data as
                            // the system executing the built code. So we should be permissive for this case.
                        }
                        bool wroteCopyLocalMetadata = WriteCopyLocalMetadataIfNeeded(
                                package,
                                Path.GetFileName(asset.Path),
                                destinationSubDirectory: locale + Path.DirectorySeparatorChar);
                        if (!wroteCopyLocalMetadata)
                        {
                            WriteMetadata(MetadataKeys.DestinationSubDirectory, locale + Path.DirectorySeparatorChar);
                        }
                        WriteMetadata(MetadataKeys.Culture, locale);
                    });
            }
 
            private void WriteRuntimeAssemblies()
            {
                WriteItems(
                    _runtimeTarget,
                    package => package.RuntimeAssemblies,
                    writeMetadata: (package, asset) =>
                    {
                        WriteMetadata(MetadataKeys.AssetType, "runtime");
                        WriteCopyLocalMetadataIfNeeded(package, Path.GetFileName(asset.Path));
                    });
            }
 
            private void WriteRuntimeTargets()
            {
                WriteItems(
                    _runtimeTarget,
                    package => package.RuntimeTargets,
                    writeMetadata: (package, asset) =>
                    {
                        WriteMetadata(MetadataKeys.AssetType, asset.AssetType.ToLowerInvariant());
                        bool wroteCopyLocalMetadata = false;
                        if (_task.CopyLocalRuntimeTargetAssets)
                        {
                            wroteCopyLocalMetadata = WriteCopyLocalMetadataIfNeeded(
                                package,
                                Path.GetFileName(asset.Path),
                                destinationSubDirectory: Path.GetDirectoryName(asset.Path) + Path.DirectorySeparatorChar);
                        }
                        if (!wroteCopyLocalMetadata)
                        {
                            WriteMetadata(MetadataKeys.DestinationSubDirectory, Path.GetDirectoryName(asset.Path) + Path.DirectorySeparatorChar);
                        }
                        WriteMetadata(MetadataKeys.RuntimeIdentifier, asset.Runtime);
                    });
            }
 
            private void WriteTransitiveProjectReferences()
            {
                if (_task.DisableTransitiveProjectReferences)
                {
                    return;
                }
 
                Dictionary<string, string> projectReferencePaths = null;
                HashSet<string> directProjectDependencies = null;
 
                foreach (var library in _runtimeTarget.Libraries)
                {
                    if (!library.IsTransitiveProjectReference(_lockFile, ref directProjectDependencies,
                        _lockFile.GetLockFileTargetAlias(_lockFile.GetTargetAndReturnNullIfNotFound(_targetFramework, null))))
                    {
                        continue;
                    }
 
                    if (projectReferencePaths == null)
                    {
                        projectReferencePaths = GetProjectReferencePaths(_lockFile);
                    }
 
                    if (!directProjectDependencies.Contains(library.Name))
                    {
                        WriteItem(projectReferencePaths[library.Name], library);
                    }
                }
            }
 
            private void WriteFrameworkReferences()
            {
                if (_task.DisableTransitiveFrameworkReferences)
                {
                    return;
                }
 
                HashSet<string> writtenFrameworkReferences = null;
 
                foreach (var library in _runtimeTarget.Libraries)
                {
                    foreach (var frameworkReference in library.FrameworkReferences)
                    {
                        if (writtenFrameworkReferences == null)
                        {
                            writtenFrameworkReferences = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
                        }
                        if (writtenFrameworkReferences.Add(frameworkReference))
                        {
                            WriteItem(frameworkReference);
                        }
                    }
                }
            }
 
            private void WriteItems<T>(
                LockFileTarget target,
                Func<LockFileTargetLibrary, IEnumerable<T>> getAssets,
                Func<T, bool> filter = null,
                Action<LockFileTargetLibrary, T> writeMetadata = null)
                where T : LockFileItem
            {
                foreach (var library in target.Libraries)
                {
                    if (!library.IsPackage())
                    {
                        continue;
                    }
 
                    foreach (T asset in getAssets(library))
                    {
                        if (asset.IsPlaceholderFile() || (filter != null && !filter.Invoke(asset)))
                        {
                            continue;
                        }
 
                        string itemSpec = _packageResolver.ResolvePackageAssetPath(library, asset.Path);
                        WriteItem(itemSpec, library);
                        WriteMetadata(MetadataKeys.PathInPackage, asset.Path);
 
                        writeMetadata?.Invoke(library, asset);
                    }
                }
            }
 
            private void WriteItem(string itemSpec)
            {
                FlushMetadata();
                _itemCount++;
                _writer.Write(ProjectCollection.Escape(itemSpec));
            }
 
            private void WriteItem(string itemSpec, LockFileTargetLibrary package)
            {
                WriteItem(itemSpec);
                WriteMetadata(MetadataKeys.NuGetPackageId, package.Name);
                WriteMetadata(MetadataKeys.NuGetPackageVersion, package.Version.ToNormalizedString());
            }
 
            private void WriteMetadata(string key, string value)
            {
                if (!string.IsNullOrEmpty(value))
                {
                    _bufferedMetadata.Add(GetMetadataIndex(key));
                    _bufferedMetadata.Add(GetMetadataIndex(value));
                }
            }
 
            private bool WriteCopyLocalMetadataIfNeeded(LockFileTargetLibrary package, string assetsFileName, string destinationSubDirectory = null)
            {
                bool shouldCopyLocal = true;
                if (_copyLocalPackageExclusions != null && _copyLocalPackageExclusions.Contains(package.Name))
                {
                    shouldCopyLocal = false;
                }
                bool shouldIncludeInPublish = shouldCopyLocal;
                if (shouldIncludeInPublish && _publishPackageExclusions != null && _publishPackageExclusions.Contains(package.Name))
                {
                    shouldIncludeInPublish = false;
                }
 
                if (!shouldCopyLocal && !shouldIncludeInPublish)
                {
                    return false;
                }
 
                if (shouldCopyLocal)
                {
                    WriteMetadata(MetadataKeys.CopyLocal, "true");
                    if (!shouldIncludeInPublish)
                    {
                        WriteMetadata(MetadataKeys.CopyToPublishDirectory, "false");
                    }
                }
                WriteMetadata(
                    MetadataKeys.DestinationSubPath,
                    string.IsNullOrEmpty(destinationSubDirectory) ?
                        assetsFileName :
                        Path.Combine(destinationSubDirectory, assetsFileName));
                if (!string.IsNullOrEmpty(destinationSubDirectory))
                {
                    WriteMetadata(MetadataKeys.DestinationSubDirectory, destinationSubDirectory);
                }
 
                return true;
            }
 
            private int GetMetadataIndex(string value)
            {
                if (!_stringTable.TryGetValue(value, out int index))
                {
                    index = _metadataStrings.Count;
                    _stringTable.Add(value, index);
                    _metadataStrings.Add(value);
                }
 
                return index;
            }
 
            private void ComputePackageExclusions()
            {
                var copyLocalPackageExclusions = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
                var libraryLookup = _runtimeTarget.Libraries
                    .GroupBy(p => p.Name)
                    .Select(g => g.First())
                    .ToDictionary(p => p.Name, StringComparer.OrdinalIgnoreCase);
 
                // Only exclude platform packages for framework-dependent applications
                if ((!_task.IsSelfContained || string.IsNullOrEmpty(_runtimeTarget.RuntimeIdentifier)) &&
                    _task.PlatformLibraryName != null)
                {
                    // Exclude the platform library
                    var platformLibrary = _runtimeTarget.GetLibrary(_task.PlatformLibraryName);
                    if (platformLibrary != null)
                    {
                        copyLocalPackageExclusions.UnionWith(_runtimeTarget.GetPlatformExclusionList(platformLibrary, libraryLookup));
 
                        // If the platform library is not Microsoft.NETCore.App, treat it as an implicit dependency.
                        // This makes it so Microsoft.AspNet.* 2.x platforms also exclude Microsoft.NETCore.App files.
                        if (!string.Equals(platformLibrary.Name, NetCorePlatformLibrary, StringComparison.OrdinalIgnoreCase))
                        {
                            var library = _runtimeTarget.GetLibrary(NetCorePlatformLibrary);
                            if (library != null)
                            {
                                copyLocalPackageExclusions.UnionWith(_runtimeTarget.GetPlatformExclusionList(library, libraryLookup));
                            }
                        }
                    }
                }
 
                if (_task.PackageReferences != null)
                {
                    var excludeFromPublishPackageReferences = _task.PackageReferences
                        .Where(pr => pr.GetBooleanMetadata(MetadataKeys.Publish) == false)
                        .ToList();
 
                    if (excludeFromPublishPackageReferences.Any())
                    {
 
                        var topLevelDependencies = ProjectContext.GetTopLevelDependencies(_lockFile, _runtimeTarget);
 
                        //  Exclude transitive dependencies of excluded packages unless they are also dependencies
                        //  of non-excluded packages
 
                        HashSet<string> includedDependencies = new(StringComparer.OrdinalIgnoreCase);
                        HashSet<string> excludeFromPublishPackageIds = new(
                            excludeFromPublishPackageReferences.Select(pr => pr.ItemSpec),
                            StringComparer.OrdinalIgnoreCase);
 
                        Stack<string> dependenciesToWalk = new(
                            topLevelDependencies.Except(excludeFromPublishPackageIds, StringComparer.OrdinalIgnoreCase));
 
                        while (dependenciesToWalk.Any())
                        {
                            var dependencyName = dependenciesToWalk.Pop();
                            if (!includedDependencies.Contains(dependencyName))
                            {
                                //  There may not be a library in the assets file if a referenced project has
                                //  PrivateAssets="all" for a package reference, and there is a package in the graph
                                //  that depends on the same packge.
                                if (libraryLookup.TryGetValue(dependencyName, out var library))
                                {
                                    includedDependencies.Add(dependencyName);
                                    foreach (var newDependency in library.Dependencies)
                                    {
                                        dependenciesToWalk.Push(newDependency.Id);
                                    }
                                }
                            }
                        }
 
                        var publishPackageExclusions = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
 
                        foreach (var library in _runtimeTarget.Libraries)
                        {
                            //  Libraries explicitly marked as exclude from publish should be excluded from
                            //  publish even if there are other transitive dependencies to them
                            if (excludeFromPublishPackageIds.Contains(library.Name))
                            {
                                publishPackageExclusions.Add(library.Name);
                            }
 
                            if (!includedDependencies.Contains(library.Name))
                            {
                                publishPackageExclusions.Add(library.Name);
                            }
                        }
 
                        if (publishPackageExclusions.Any())
                        {
                            _publishPackageExclusions = publishPackageExclusions;
                        }
                    }
                }
 
                if (copyLocalPackageExclusions.Any())
                {
                    _copyLocalPackageExclusions = copyLocalPackageExclusions;
                }
            }
 
            private static Dictionary<string, string> GetProjectReferencePaths(LockFile lockFile)
            {
                Dictionary<string, string> paths = new();
 
                foreach (var library in lockFile.Libraries)
                {
                    if (library.IsProject())
                    {
                        paths[library.Name] = NuGetPackageResolver.NormalizeRelativePath(library.MSBuildProject);
                    }
                }
 
                return paths;
            }
 
            private Tuple<string, LockFileTargetLibrary> FindApphostInRuntimeTarget(string apphostName, LockFileTarget runtimeTarget)
            {
                foreach (LockFileTargetLibrary library in runtimeTarget.Libraries)
                {
                    if (!library.IsPackage())
                    {
                        continue;
                    }
 
                    foreach (LockFileItem asset in library.NativeLibraries)
                    {
                        if (asset.IsPlaceholderFile())
                        {
                            continue;
                        }
 
                        var resolvedPackageAssetPath = _packageResolver.ResolvePackageAssetPath(library, asset.Path);
 
                        if (Path.GetFileName(resolvedPackageAssetPath) == apphostName)
                        {
                            return new Tuple<string, LockFileTargetLibrary>(resolvedPackageAssetPath, library);
                        }
                    }
                }
 
                throw new BuildErrorException(Strings.CannotFindApphostForRid, runtimeTarget.RuntimeIdentifier);
            }
        }
    }
}