File: DependencyContextJsonReader.cs
Web Access
Project: src\src\libraries\Microsoft.Extensions.DependencyModel\src\Microsoft.Extensions.DependencyModel.csproj (Microsoft.Extensions.DependencyModel)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Text.Json;
 
namespace Microsoft.Extensions.DependencyModel
{
    public class DependencyContextJsonReader : IDependencyContextReader
    {
        private const int UnseekableStreamInitialRentSize = 4096;
        private static ReadOnlySpan<byte> Utf8Bom => [0xEF, 0xBB, 0xBF];
 
        private readonly Dictionary<string, string> _stringPool = new Dictionary<string, string>();
 
        public DependencyContext Read(Stream stream)
        {
            ThrowHelper.ThrowIfNull(stream);
 
            ArraySegment<byte> buffer = ReadToEnd(stream);
            try
            {
                return Read(new Utf8JsonReader(buffer, isFinalBlock: true, state: default));
            }
            finally
            {
                // Holds document content, clear it before returning it.
                buffer.AsSpan().Clear();
                ArrayPool<byte>.Shared.Return(buffer.Array!);
            }
        }
 
        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                _stringPool.Clear();
            }
        }
 
        public void Dispose()
        {
            Dispose(true);
        }
 
        // Borrowed from https://github.com/dotnet/corefx/blob/b8bc4ff80c5f7baa681e8a569d367356957ba78a/src/System.Text.Json/src/System/Text/Json/Document/JsonDocument.Parse.cs#L290-L362
        private static ArraySegment<byte> ReadToEnd(Stream stream)
        {
            int written = 0;
            byte[]? rented = null;
 
            ReadOnlySpan<byte> utf8Bom = Utf8Bom;
 
            try
            {
                if (stream.CanSeek)
                {
                    // Ask for 1 more than the length to avoid resizing later,
                    // which is unnecessary in the common case where the stream length doesn't change.
                    long expectedLength = Math.Max(utf8Bom.Length, stream.Length - stream.Position) + 1;
                    rented = ArrayPool<byte>.Shared.Rent(checked((int)expectedLength));
                }
                else
                {
                    rented = ArrayPool<byte>.Shared.Rent(UnseekableStreamInitialRentSize);
                }
 
                int lastRead;
 
                // Read up to 3 bytes to see if it's the UTF-8 BOM, for parity with the behavior
                // of StreamReader..ctor(Stream).
                do
                {
                    // No need for checking for growth, the minimal rent sizes both guarantee it'll fit.
                    Debug.Assert(rented.Length >= utf8Bom.Length);
 
                    lastRead = stream.Read(
                        rented,
                        written,
                        utf8Bom.Length - written);
 
                    written += lastRead;
                } while (lastRead > 0 && written < utf8Bom.Length);
 
                // If we have 3 bytes, and they're the BOM, reset the write position to 0.
                if (written == utf8Bom.Length &&
                    utf8Bom.SequenceEqual(rented.AsSpan(0, utf8Bom.Length)))
                {
                    written = 0;
                }
 
                do
                {
                    if (rented.Length == written)
                    {
                        byte[] toReturn = rented;
                        rented = ArrayPool<byte>.Shared.Rent(checked(toReturn.Length * 2));
                        Buffer.BlockCopy(toReturn, 0, rented, 0, toReturn.Length);
                        // Holds document content, clear it.
                        ArrayPool<byte>.Shared.Return(toReturn, clearArray: true);
                    }
 
                    lastRead = stream.Read(rented, written, rented.Length - written);
                    written += lastRead;
                } while (lastRead > 0);
 
                return new ArraySegment<byte>(rented, 0, written);
            }
            catch
            {
                if (rented != null)
                {
                    // Holds document content, clear it before returning it.
                    rented.AsSpan(0, written).Clear();
                    ArrayPool<byte>.Shared.Return(rented);
                }
 
                throw;
            }
        }
 
        private DependencyContext Read(Utf8JsonReader reader)
        {
            reader.ReadStartObject();
 
            string runtime = string.Empty;
            string framework = string.Empty;
            bool isPortable = true;
            string? runtimeTargetName = null;
            string? runtimeSignature = null;
 
            CompilationOptions? compilationOptions = null;
            List<Target>? targets = null;
            Dictionary<string, LibraryStub>? libraryStubs = null;
            List<RuntimeFallbacks>? runtimeFallbacks = null;
 
            while (reader.Read() && reader.IsTokenTypeProperty())
            {
                switch (reader.GetString())
                {
                    case DependencyContextStrings.RuntimeTargetPropertyName:
                        ReadRuntimeTarget(ref reader, out runtimeTargetName, out runtimeSignature);
                        break;
                    case DependencyContextStrings.CompilationOptionsPropertyName:
                        compilationOptions = ReadCompilationOptions(ref reader);
                        break;
                    case DependencyContextStrings.TargetsPropertyName:
                        targets = ReadTargets(ref reader);
                        break;
                    case DependencyContextStrings.LibrariesPropertyName:
                        libraryStubs = ReadLibraries(ref reader);
                        break;
                    case DependencyContextStrings.RuntimesPropertyName:
                        runtimeFallbacks = ReadRuntimes(ref reader);
                        break;
                    default:
                        reader.Skip();
                        break;
                }
            }
 
            compilationOptions ??= CompilationOptions.Default;
 
            Target? runtimeTarget = SelectRuntimeTarget(targets, runtimeTargetName);
            runtimeTargetName = runtimeTarget?.Name;
 
            if (runtimeTargetName != null)
            {
                int separatorIndex = runtimeTargetName.IndexOf(DependencyContextStrings.VersionSeparator);
                if (separatorIndex > -1 && separatorIndex < runtimeTargetName.Length)
                {
                    runtime = runtimeTargetName.Substring(separatorIndex + 1);
                    framework = runtimeTargetName.Substring(0, separatorIndex);
                    isPortable = false;
                }
                else
                {
                    framework = runtimeTargetName;
                }
            }
 
            Target? compileTarget = null;
 
            Target? ridlessTarget = targets.FirstOrDefault(t => !IsRuntimeTarget(t.Name));
            if (ridlessTarget != null)
            {
                compileTarget = ridlessTarget;
                if (runtimeTarget == null)
                {
                    runtimeTarget = compileTarget;
                    framework = ridlessTarget.Name;
                }
            }
 
            if (runtimeTarget == null)
            {
                throw new FormatException(SR.NoRuntimeTarget);
            }
 
            return new DependencyContext(
                new TargetInfo(framework, runtime, runtimeSignature, isPortable),
                compilationOptions,
                CreateLibraries(compileTarget?.Libraries, false, libraryStubs).Cast<CompilationLibrary>().ToArray(),
                CreateLibraries(runtimeTarget.Libraries, true, libraryStubs).Cast<RuntimeLibrary>().ToArray(),
                runtimeFallbacks ?? Enumerable.Empty<RuntimeFallbacks>());
        }
 
        private static Target? SelectRuntimeTarget([NotNull] List<Target>? targets, string? runtimeTargetName)
        {
            Target? target;
 
            if (targets == null || targets.Count == 0)
            {
                throw new FormatException(SR.NoTargetsSection);
            }
 
            if (!string.IsNullOrEmpty(runtimeTargetName))
            {
                target = targets.FirstOrDefault(t => t.Name == runtimeTargetName);
                if (target == null)
                {
                    throw new FormatException(SR.Format(SR.TargetNotFound, runtimeTargetName));
                }
            }
            else
            {
                target = targets.FirstOrDefault(t => IsRuntimeTarget(t.Name));
            }
 
            return target;
        }
 
        private static bool IsRuntimeTarget(string name)
        {
            return name.Contains(DependencyContextStrings.VersionSeparator);
        }
 
        private static void ReadRuntimeTarget(ref Utf8JsonReader reader, out string? runtimeTargetName, out string? runtimeSignature)
        {
            runtimeTargetName = null;
            runtimeSignature = null;
 
            reader.ReadStartObject();
 
            while (reader.TryReadStringProperty(out string? propertyName, out string? propertyValue))
            {
                switch (propertyName)
                {
                    case DependencyContextStrings.RuntimeTargetNamePropertyName:
                        runtimeTargetName = propertyValue;
                        break;
                    case DependencyContextStrings.RuntimeTargetSignaturePropertyName:
                        runtimeSignature = propertyValue;
                        break;
                }
            }
 
            reader.CheckEndObject();
        }
 
        private static CompilationOptions ReadCompilationOptions(ref Utf8JsonReader reader)
        {
            IEnumerable<string?>? defines = null;
            string? languageVersion = null;
            string? platform = null;
            bool? allowUnsafe = null;
            bool? warningsAsErrors = null;
            bool? optimize = null;
            string? keyFile = null;
            bool? delaySign = null;
            bool? publicSign = null;
            string? debugType = null;
            bool? emitEntryPoint = null;
            bool? generateXmlDocumentation = null;
 
            reader.ReadStartObject();
 
            while (reader.Read() && reader.IsTokenTypeProperty())
            {
                switch (reader.GetString())
                {
                    case DependencyContextStrings.DefinesPropertyName:
                        defines = reader.ReadStringArray();
                        break;
                    case DependencyContextStrings.LanguageVersionPropertyName:
                        languageVersion = reader.ReadAsString();
                        break;
                    case DependencyContextStrings.PlatformPropertyName:
                        platform = reader.ReadAsString();
                        break;
                    case DependencyContextStrings.AllowUnsafePropertyName:
                        allowUnsafe = reader.ReadAsNullableBoolean();
                        break;
                    case DependencyContextStrings.WarningsAsErrorsPropertyName:
                        warningsAsErrors = reader.ReadAsNullableBoolean();
                        break;
                    case DependencyContextStrings.OptimizePropertyName:
                        optimize = reader.ReadAsNullableBoolean();
                        break;
                    case DependencyContextStrings.KeyFilePropertyName:
                        keyFile = reader.ReadAsString();
                        break;
                    case DependencyContextStrings.DelaySignPropertyName:
                        delaySign = reader.ReadAsNullableBoolean();
                        break;
                    case DependencyContextStrings.PublicSignPropertyName:
                        publicSign = reader.ReadAsNullableBoolean();
                        break;
                    case DependencyContextStrings.DebugTypePropertyName:
                        debugType = reader.ReadAsString();
                        break;
                    case DependencyContextStrings.EmitEntryPointPropertyName:
                        emitEntryPoint = reader.ReadAsNullableBoolean();
                        break;
                    case DependencyContextStrings.GenerateXmlDocumentationPropertyName:
                        generateXmlDocumentation = reader.ReadAsNullableBoolean();
                        break;
                    default:
                        reader.Skip();
                        break;
                }
            }
 
            reader.CheckEndObject();
 
            return new CompilationOptions(
                defines ?? Enumerable.Empty<string?>(),
                languageVersion,
                platform,
                allowUnsafe,
                warningsAsErrors,
                optimize,
                keyFile,
                delaySign,
                publicSign,
                debugType,
                emitEntryPoint,
                generateXmlDocumentation);
        }
 
        private List<Target> ReadTargets(ref Utf8JsonReader reader)
        {
            reader.ReadStartObject();
 
            var targets = new List<Target>();
 
            while (reader.Read() && reader.IsTokenTypeProperty())
            {
                string? targetName = reader.GetString();
 
                if (string.IsNullOrEmpty(targetName))
                {
                    throw new FormatException(SR.Format(SR.RequiredFieldNotSpecified, nameof(targetName)));
                }
 
                targets.Add(ReadTarget(ref reader, targetName));
            }
 
            reader.CheckEndObject();
 
            return targets;
        }
 
        private Target ReadTarget(ref Utf8JsonReader reader, string targetName)
        {
            reader.ReadStartObject();
 
            var libraries = new List<TargetLibrary>();
 
            while (reader.Read() && reader.IsTokenTypeProperty())
            {
                string? targetLibraryName = reader.GetString();
 
                if (string.IsNullOrEmpty(targetLibraryName))
                {
                    throw new FormatException(SR.Format(SR.RequiredFieldNotSpecified, nameof(targetLibraryName)));
                }
 
                libraries.Add(ReadTargetLibrary(ref reader, targetLibraryName));
            }
 
            reader.CheckEndObject();
 
            return new Target(targetName, libraries);
        }
 
        private TargetLibrary ReadTargetLibrary(ref Utf8JsonReader reader, string targetLibraryName)
        {
            IEnumerable<Dependency>? dependencies = null;
            List<RuntimeFile>? runtimes = null;
            List<RuntimeFile>? natives = null;
            List<string>? compilations = null;
            List<RuntimeTargetEntryStub>? runtimeTargets = null;
            List<ResourceAssembly>? resources = null;
            bool? compileOnly = null;
 
            reader.ReadStartObject();
 
            while (reader.Read() && reader.IsTokenTypeProperty())
            {
                switch (reader.GetString())
                {
                    case DependencyContextStrings.DependenciesPropertyName:
                        dependencies = ReadTargetLibraryDependencies(ref reader);
                        break;
                    case DependencyContextStrings.RuntimeAssembliesKey:
                        runtimes = ReadRuntimeFiles(ref reader);
                        break;
                    case DependencyContextStrings.NativeLibrariesKey:
                        natives = ReadRuntimeFiles(ref reader);
                        break;
                    case DependencyContextStrings.CompileTimeAssembliesKey:
                        compilations = ReadPropertyNames(ref reader);
                        break;
                    case DependencyContextStrings.RuntimeTargetsPropertyName:
                        runtimeTargets = ReadTargetLibraryRuntimeTargets(ref reader);
                        break;
                    case DependencyContextStrings.ResourceAssembliesPropertyName:
                        resources = ReadTargetLibraryResources(ref reader);
                        break;
                    case DependencyContextStrings.CompilationOnlyPropertyName:
                        compileOnly = reader.ReadAsNullableBoolean();
                        break;
                    default:
                        reader.Skip();
                        break;
                }
            }
 
            reader.CheckEndObject();
 
            return new TargetLibrary()
            {
                Name = targetLibraryName,
                Dependencies = dependencies ?? Enumerable.Empty<Dependency>(),
                Runtimes = runtimes,
                Natives = natives,
                Compilations = compilations,
                RuntimeTargets = runtimeTargets,
                Resources = resources,
                CompileOnly = compileOnly
            };
        }
 
        private List<Dependency> ReadTargetLibraryDependencies(ref Utf8JsonReader reader)
        {
            var dependencies = new List<Dependency>();
 
            reader.ReadStartObject();
 
            while (reader.TryReadStringProperty(out string? name, out string? version))
            {
                if (string.IsNullOrEmpty(name))
                {
                    throw new FormatException(SR.Format(SR.RequiredFieldNotSpecified, nameof(name)));
                }
                if (string.IsNullOrEmpty(version))
                {
                    throw new FormatException(SR.Format(SR.RequiredFieldNotSpecified, nameof(version)));
                }
 
                dependencies.Add(new Dependency(Pool(name), Pool(version)));
            }
 
            reader.CheckEndObject();
 
            return dependencies;
        }
 
        private static List<string> ReadPropertyNames(ref Utf8JsonReader reader)
        {
            var runtimes = new List<string>();
 
            reader.ReadStartObject();
 
            while (reader.Read() && reader.IsTokenTypeProperty())
            {
                string? libraryName = reader.GetString();
 
                if (string.IsNullOrEmpty(libraryName))
                {
                    throw new FormatException(SR.Format(SR.RequiredFieldNotSpecified, nameof(libraryName)));
                }
                reader.Skip();
 
                runtimes.Add(libraryName);
            }
 
            reader.CheckEndObject();
 
            return runtimes;
        }
 
        private static List<RuntimeFile> ReadRuntimeFiles(ref Utf8JsonReader reader)
        {
            var runtimeFiles = new List<RuntimeFile>();
 
            reader.ReadStartObject();
 
            while (reader.Read() && reader.IsTokenTypeProperty())
            {
                string? assemblyVersion = null;
                string? fileVersion = null;
 
                string? path = reader.GetString();
 
                if (string.IsNullOrEmpty(path))
                {
                    throw new FormatException(SR.Format(SR.RequiredFieldNotSpecified, nameof(path)));
                }
 
                reader.ReadStartObject();
 
                while (reader.TryReadStringProperty(out string? propertyName, out string? propertyValue))
                {
                    switch (propertyName)
                    {
                        case DependencyContextStrings.AssemblyVersionPropertyName:
                            assemblyVersion = propertyValue;
                            break;
                        case DependencyContextStrings.FileVersionPropertyName:
                            fileVersion = propertyValue;
                            break;
                    }
                }
 
                reader.CheckEndObject();
 
                runtimeFiles.Add(new RuntimeFile(path, assemblyVersion, fileVersion));
            }
 
            reader.CheckEndObject();
 
            return runtimeFiles;
        }
 
        private List<RuntimeTargetEntryStub> ReadTargetLibraryRuntimeTargets(ref Utf8JsonReader reader)
        {
            var runtimeTargets = new List<RuntimeTargetEntryStub>();
 
            reader.ReadStartObject();
 
            while (reader.Read() && reader.IsTokenTypeProperty())
            {
                string? runtimePath = reader.GetString();
 
                if (string.IsNullOrEmpty(runtimePath))
                {
                    throw new FormatException(SR.Format(SR.RequiredFieldNotSpecified, nameof(runtimePath)));
                }
 
                var runtimeTarget = new RuntimeTargetEntryStub
                {
                    Path = runtimePath
                };
 
                reader.ReadStartObject();
 
                while (reader.TryReadStringProperty(out string? propertyName, out string? propertyValue))
                {
                    switch (propertyName)
                    {
                        case DependencyContextStrings.RidPropertyName:
                            runtimeTarget.Rid = Pool(propertyValue);
                            break;
                        case DependencyContextStrings.AssetTypePropertyName:
                            runtimeTarget.Type = Pool(propertyValue);
                            break;
                        case DependencyContextStrings.AssemblyVersionPropertyName:
                            runtimeTarget.AssemblyVersion = propertyValue;
                            break;
                        case DependencyContextStrings.FileVersionPropertyName:
                            runtimeTarget.FileVersion = propertyValue;
                            break;
                    }
                }
 
                reader.CheckEndObject();
 
                runtimeTargets.Add(runtimeTarget);
            }
 
            reader.CheckEndObject();
 
            return runtimeTargets;
        }
 
        private List<ResourceAssembly> ReadTargetLibraryResources(ref Utf8JsonReader reader)
        {
            var resources = new List<ResourceAssembly>();
 
            reader.ReadStartObject();
 
            while (reader.Read() && reader.IsTokenTypeProperty())
            {
                string? path = reader.GetString();
 
                if (string.IsNullOrEmpty(path))
                {
                    throw new FormatException(SR.Format(SR.RequiredFieldNotSpecified, nameof(path)));
                }
 
                string? locale = null;
 
                reader.ReadStartObject();
 
                while (reader.TryReadStringProperty(out string? propertyName, out string? propertyValue))
                {
                    if (propertyName == DependencyContextStrings.LocalePropertyName)
                    {
                        locale = propertyValue;
                    }
                }
 
                reader.CheckEndObject();
 
                if (locale != null)
                {
                    resources.Add(new ResourceAssembly(path, Pool(locale)));
                }
            }
 
            reader.CheckEndObject();
 
            return resources;
        }
 
        private Dictionary<string, LibraryStub> ReadLibraries(ref Utf8JsonReader reader)
        {
            var libraries = new Dictionary<string, LibraryStub>();
 
            reader.ReadStartObject();
 
            while (reader.Read() && reader.IsTokenTypeProperty())
            {
                string? libraryName = reader.GetString();
 
                if (string.IsNullOrEmpty(libraryName))
                {
                    throw new FormatException(SR.Format(SR.RequiredFieldNotSpecified, nameof(libraryName)));
                }
 
                libraries.Add(Pool(libraryName), ReadOneLibrary(ref reader));
            }
 
            reader.CheckEndObject();
 
            return libraries;
        }
 
        private LibraryStub ReadOneLibrary(ref Utf8JsonReader reader)
        {
            string? hash = null;
            string? type = null;
            bool serviceable = false;
            string? path = null;
            string? hashPath = null;
            string? runtimeStoreManifestName = null;
 
            reader.ReadStartObject();
 
            while (reader.Read() && reader.IsTokenTypeProperty())
            {
                switch (reader.GetString())
                {
                    case DependencyContextStrings.Sha512PropertyName:
                        hash = reader.ReadAsString();
                        break;
                    case DependencyContextStrings.TypePropertyName:
                        type = reader.ReadAsString();
                        break;
                    case DependencyContextStrings.ServiceablePropertyName:
                        serviceable = reader.ReadAsBoolean(defaultValue: false);
                        break;
                    case DependencyContextStrings.PathPropertyName:
                        path = reader.ReadAsString();
                        break;
                    case DependencyContextStrings.HashPathPropertyName:
                        hashPath = reader.ReadAsString();
                        break;
                    case DependencyContextStrings.RuntimeStoreManifestPropertyName:
                        runtimeStoreManifestName = reader.ReadAsString();
                        break;
                    default:
                        reader.Skip();
                        break;
                }
            }
 
            reader.CheckEndObject();
 
            if (string.IsNullOrEmpty(type))
            {
                throw new FormatException(SR.Format(SR.RequiredFieldNotSpecified, nameof(type)));
            }
 
            return new LibraryStub()
            {
                Hash = hash,
                Type = Pool(type),
                Serviceable = serviceable,
                Path = path,
                HashPath = hashPath,
                RuntimeStoreManifestName = runtimeStoreManifestName
            };
        }
 
        private static List<RuntimeFallbacks> ReadRuntimes(ref Utf8JsonReader reader)
        {
            var runtimeFallbacks = new List<RuntimeFallbacks>();
 
            reader.ReadStartObject();
 
            while (reader.Read() && reader.IsTokenTypeProperty())
            {
                string? runtime = reader.GetString();
                string?[] fallbacks = reader.ReadStringArray();
 
                if (string.IsNullOrEmpty(runtime))
                {
                    throw new FormatException(SR.Format(SR.RequiredFieldNotSpecified, nameof(runtime)));
                }
 
                runtimeFallbacks.Add(new RuntimeFallbacks(runtime, fallbacks));
            }
 
            reader.CheckEndObject();
 
            return runtimeFallbacks;
        }
 
        private IEnumerable<Library> CreateLibraries(IEnumerable<TargetLibrary>? libraries, bool runtime, Dictionary<string, LibraryStub>? libraryStubs)
        {
            if (libraries == null)
            {
                return Enumerable.Empty<Library>();
            }
 
            return CreateLibrariesNotNull(libraries, runtime, libraryStubs);
 
            IEnumerable<Library> CreateLibrariesNotNull(IEnumerable<TargetLibrary> libraries, bool runtime, Dictionary<string, LibraryStub>? libraryStubs)
            {
                foreach (TargetLibrary library in libraries)
                {
                    Library? createdLibrary = CreateLibrary(library, runtime, libraryStubs);
                    if (createdLibrary is not null)
                        yield return createdLibrary;
                }
            }
        }
 
        private Library? CreateLibrary(TargetLibrary targetLibrary, bool runtime, Dictionary<string, LibraryStub>? libraryStubs)
        {
            string nameWithVersion = targetLibrary.Name;
 
            if (libraryStubs == null || !libraryStubs.TryGetValue(nameWithVersion, out LibraryStub stub))
            {
                throw new InvalidOperationException(SR.Format(SR.LibraryInformationNotFound, nameWithVersion));
            }
 
            int separatorPosition = nameWithVersion.IndexOf(DependencyContextStrings.VersionSeparator);
 
            string name = Pool(nameWithVersion.Substring(0, separatorPosition));
            string version = Pool(nameWithVersion.Substring(separatorPosition + 1));
 
            if (runtime)
            {
                // Runtime section of this library was trimmed by type:platform
                bool? isCompilationOnly = targetLibrary.CompileOnly;
                if (isCompilationOnly == true)
                {
                    return null;
                }
 
                var runtimeAssemblyGroups = new List<RuntimeAssetGroup>();
                var nativeLibraryGroups = new List<RuntimeAssetGroup>();
                if (targetLibrary.RuntimeTargets != null)
                {
                    foreach (IGrouping<string?, RuntimeTargetEntryStub> ridGroup in targetLibrary.RuntimeTargets.GroupBy(e => e.Rid))
                    {
                        RuntimeFile[] groupRuntimeAssemblies = ridGroup
                            .Where(e => e.Type == DependencyContextStrings.RuntimeAssetType)
                            .Select(e => new RuntimeFile(e.Path, e.AssemblyVersion, e.FileVersion))
                            .ToArray();
 
                        if (groupRuntimeAssemblies.Length != 0)
                        {
                            runtimeAssemblyGroups.Add(new RuntimeAssetGroup(
                                ridGroup.Key,
                                groupRuntimeAssemblies.Where(a => Path.GetFileName(a.Path) != "_._")));
                        }
 
                        RuntimeFile[] groupNativeLibraries = ridGroup
                            .Where(e => e.Type == DependencyContextStrings.NativeAssetType)
                            .Select(e => new RuntimeFile(e.Path, e.AssemblyVersion, e.FileVersion))
                            .ToArray();
 
                        if (groupNativeLibraries.Length != 0)
                        {
                            nativeLibraryGroups.Add(new RuntimeAssetGroup(
                                ridGroup.Key,
                                groupNativeLibraries.Where(a => Path.GetFileName(a.Path) != "_._")));
                        }
                    }
                }
 
                if (targetLibrary.Runtimes != null && targetLibrary.Runtimes.Count > 0)
                {
                    runtimeAssemblyGroups.Add(new RuntimeAssetGroup(string.Empty, targetLibrary.Runtimes));
                }
 
                if (targetLibrary.Natives != null && targetLibrary.Natives.Count > 0)
                {
                    nativeLibraryGroups.Add(new RuntimeAssetGroup(string.Empty, targetLibrary.Natives));
                }
 
                return new RuntimeLibrary(
                    type: stub.Type,
                    name: name,
                    version: version,
                    hash: stub.Hash,
                    runtimeAssemblyGroups: runtimeAssemblyGroups,
                    nativeLibraryGroups: nativeLibraryGroups,
                    resourceAssemblies: targetLibrary.Resources ?? Enumerable.Empty<ResourceAssembly>(),
                    dependencies: targetLibrary.Dependencies,
                    serviceable: stub.Serviceable,
                    path: stub.Path,
                    hashPath: stub.HashPath,
                    runtimeStoreManifestName: stub.RuntimeStoreManifestName);
            }
            else
            {
                IEnumerable<string> assemblies = targetLibrary.Compilations ?? Enumerable.Empty<string>();
                return new CompilationLibrary(
                    stub.Type,
                    name,
                    version,
                    stub.Hash,
                    assemblies,
                    targetLibrary.Dependencies,
                    stub.Serviceable,
                    stub.Path,
                    stub.HashPath);
            }
        }
 
        [return: NotNullIfNotNull(nameof(s))]
        private string? Pool(string? s)
        {
            if (s == null)
            {
                return null;
            }
 
            if (!_stringPool.TryGetValue(s, out string? result))
            {
                _stringPool[s] = s;
                result = s;
            }
            return result;
        }
 
        private sealed class Target
        {
            public string Name;
 
            public IEnumerable<TargetLibrary> Libraries;
 
            public Target(string name, IEnumerable<TargetLibrary> libraries)
            {
                Name = name;
                Libraries = libraries;
            }
        }
 
        private struct TargetLibrary
        {
            public string Name;
 
            public IEnumerable<Dependency> Dependencies;
 
            public List<RuntimeFile>? Runtimes;
 
            public List<RuntimeFile>? Natives;
 
            public List<string>? Compilations;
 
            public List<RuntimeTargetEntryStub>? RuntimeTargets;
 
            public List<ResourceAssembly>? Resources;
 
            public bool? CompileOnly;
        }
 
        private struct RuntimeTargetEntryStub
        {
            public string? Type;
 
            public string Path;
 
            public string? Rid;
 
            public string? AssemblyVersion;
 
            public string? FileVersion;
        }
 
        private struct LibraryStub
        {
            public string? Hash;
 
            public string Type;
 
            public bool Serviceable;
 
            public string? Path;
 
            public string? HashPath;
 
            public string? RuntimeStoreManifestName;
        }
    }
}