// Copyright (c) .NET Foundation and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; using System.Collections.Generic; using System.IO; using System.Linq; using Newtonsoft.Json; using Newtonsoft.Json.Linq; namespace Microsoft.Extensions.DependencyModel { public class DependencyContextJsonReader { public DependencyContext Read(Stream stream) { using (var streamReader = new StreamReader(stream)) { using (var reader = new JsonTextReader(streamReader)) { var root = JObject.Load(reader); return Read(root); } } } private bool IsRuntimeTarget(string name) => name.Contains(DependencyContextStrings.VersionSeperator); private DependencyContext Read(JObject root) { var runtime = string.Empty; var target = string.Empty; var isPortable = true; var runtimeTargetName = root[DependencyContextStrings.RuntimeTargetPropertyName]?.Value(); var libraryStubs = ReadLibraryStubs((JObject) root[DependencyContextStrings.LibrariesPropertyName]); var targetsObject = (JObject) root[DependencyContextStrings.TargetsPropertyName]; JObject runtimeTarget = null; JObject compileTarget = null; if (targetsObject != null) { var compileTargetProperty = targetsObject.Properties() .FirstOrDefault(p => !IsRuntimeTarget(p.Name)); compileTarget = (JObject)compileTargetProperty.Value; target = compileTargetProperty.Name; if (!string.IsNullOrEmpty(runtimeTargetName)) { runtimeTarget = (JObject) targetsObject[runtimeTargetName]; if (runtimeTarget == null) { throw new FormatException($"Target with name {runtimeTargetName} not found"); } var seperatorIndex = runtimeTargetName.IndexOf(DependencyContextStrings.VersionSeperator); if (seperatorIndex > -1 && seperatorIndex < runtimeTargetName.Length) { runtime = runtimeTargetName.Substring(seperatorIndex + 1); isPortable = false; } } else { runtimeTarget = compileTarget; } } return new DependencyContext( target, runtime, isPortable, ReadCompilationOptions((JObject)root[DependencyContextStrings.CompilationOptionsPropertName]), ReadLibraries(compileTarget, false, libraryStubs).Cast().ToArray(), ReadLibraries(runtimeTarget, true, libraryStubs).Cast().ToArray(), ReadRuntimeGraph((JObject)root[DependencyContextStrings.RuntimesPropertyName]).ToArray() ); } private IEnumerable> ReadRuntimeGraph(JObject runtimes) { if (runtimes == null) { yield break; } var targets = runtimes.Children(); var runtime = (JProperty)targets.Single(); foreach (var pair in (JObject)runtime.Value) { yield return new KeyValuePair(pair.Key, pair.Value.Values().ToArray()); } } private CompilationOptions ReadCompilationOptions(JObject compilationOptionsObject) { if (compilationOptionsObject == null) { return CompilationOptions.Default; } return new CompilationOptions( compilationOptionsObject[DependencyContextStrings.DefinesPropertyName]?.Values(), compilationOptionsObject[DependencyContextStrings.LanguageVersionPropertyName]?.Value(), compilationOptionsObject[DependencyContextStrings.PlatformPropertyName]?.Value(), compilationOptionsObject[DependencyContextStrings.AllowUnsafePropertyName]?.Value(), compilationOptionsObject[DependencyContextStrings.WarningsAsErrorsPropertyName]?.Value(), compilationOptionsObject[DependencyContextStrings.OptimizePropertyName]?.Value(), compilationOptionsObject[DependencyContextStrings.KeyFilePropertyName]?.Value(), compilationOptionsObject[DependencyContextStrings.DelaySignPropertyName]?.Value(), compilationOptionsObject[DependencyContextStrings.PublicSignPropertyName]?.Value(), compilationOptionsObject[DependencyContextStrings.DebugTypePropertyName]?.Value(), compilationOptionsObject[DependencyContextStrings.EmitEntryPointPropertyName]?.Value(), compilationOptionsObject[DependencyContextStrings.GenerateXmlDocumentationPropertyName]?.Value() ); } private IEnumerable ReadLibraries(JObject librariesObject, bool runtime, Dictionary libraryStubs) { if (librariesObject == null) { return Enumerable.Empty(); } return librariesObject.Properties().Select(property => ReadLibrary(property, runtime, libraryStubs)); } private Library ReadLibrary(JProperty property, bool runtime, Dictionary libraryStubs) { var nameWithVersion = property.Name; LibraryStub stub; if (!libraryStubs.TryGetValue(nameWithVersion, out stub)) { throw new InvalidOperationException($"Cannot find library information for {nameWithVersion}"); } var seperatorPosition = nameWithVersion.IndexOf(DependencyContextStrings.VersionSeperator); var name = nameWithVersion.Substring(0, seperatorPosition); var version = nameWithVersion.Substring(seperatorPosition + 1); var libraryObject = (JObject) property.Value; var dependencies = ReadDependencies(libraryObject); if (runtime) { var runtimeTargets = new List(); var runtimeTargetsObject = (JObject)libraryObject[DependencyContextStrings.RuntimeTargetsPropertyName]; var entries = ReadRuntimeTargetEntries(runtimeTargetsObject).ToArray(); foreach (var ridGroup in entries.GroupBy(e => e.Rid)) { var runtimeAssets = entries.Where(e => e.Type == DependencyContextStrings.RuntimeAssetType) .Select(e => RuntimeAssembly.Create(e.Path)) .ToArray(); var nativeAssets = entries.Where(e => e.Type == DependencyContextStrings.NativeAssetType) .Select(e => e.Path) .ToArray(); runtimeTargets.Add(new RuntimeTarget( ridGroup.Key, runtimeAssets, nativeAssets )); } var assemblies = ReadAssemblies(libraryObject, DependencyContextStrings.RuntimeAssembliesKey) .Select(RuntimeAssembly.Create) .ToArray(); var resourceAssemblies = ReadResourceAssemblies((JObject)libraryObject[DependencyContextStrings.ResourceAssembliesPropertyName]); return new RuntimeLibrary( type: stub.Type, name: name, version: version, hash: stub.Hash, assemblies: assemblies, resourceAssemblies: resourceAssemblies, subTargets: runtimeTargets.ToArray(), dependencies: dependencies, serviceable: stub.Serviceable); } else { var assemblies = ReadAssemblies(libraryObject, DependencyContextStrings.CompileTimeAssembliesKey); return new CompilationLibrary(stub.Type, name, version, stub.Hash, assemblies, dependencies, stub.Serviceable); } } private IEnumerable ReadResourceAssemblies(JObject resourcesObject) { if (resourcesObject == null) { yield break; } foreach (var resourceProperty in resourcesObject) { yield return new ResourceAssembly( locale: resourceProperty.Value[DependencyContextStrings.LocalePropertyName]?.Value(), path: resourceProperty.Key ); } } private static IEnumerable ReadRuntimeTargetEntries(JObject runtimeTargetObject) { if (runtimeTargetObject == null) { yield break; } foreach (var libraryProperty in runtimeTargetObject) { var libraryObject = (JObject)libraryProperty.Value; yield return new RuntimeTargetEntryStub() { Path = libraryProperty.Key, Rid = libraryObject[DependencyContextStrings.RidPropertyName].Value(), Type = libraryObject[DependencyContextStrings.AssetTypePropertyName].Value() }; } } private static string[] ReadAssemblies(JObject libraryObject, string name) { var assembliesObject = (JObject) libraryObject[name]; if (assembliesObject == null) { return new string[] {}; } return assembliesObject.Properties().Select(property => property.Name).ToArray(); } private static Dependency[] ReadDependencies(JObject libraryObject) { var dependenciesObject = (JObject) libraryObject[DependencyContextStrings.DependenciesPropertyName]; if (dependenciesObject == null) { return new Dependency[]{ }; } return dependenciesObject.Properties() .Select(property => new Dependency(property.Name, (string) property.Value)).ToArray(); } private Dictionary ReadLibraryStubs(JObject librariesObject) { var libraries = new Dictionary(); if (librariesObject != null) { foreach (var libraryProperty in librariesObject) { var value = (JObject) libraryProperty.Value; var stub = new LibraryStub { Name = libraryProperty.Key, Hash = value[DependencyContextStrings.Sha512PropertyName]?.Value(), Type = value[DependencyContextStrings.TypePropertyName].Value(), Serviceable = value[DependencyContextStrings.ServiceablePropertyName]?.Value() == true }; libraries.Add(stub.Name, stub); } } return libraries; } private struct RuntimeTargetEntryStub { public string Type; public string Path; public string Rid; } private struct LibraryStub { public string Name; public string Hash; public string Type; public bool Serviceable; } } }