Add support for loading and merging runtime deps json

This commit is contained in:
Pavel Krymets 2016-03-10 10:12:43 -08:00
parent d95a4f0a5a
commit c10df6b6a5
14 changed files with 411 additions and 54 deletions

View file

@ -16,5 +16,10 @@ namespace Microsoft.Extensions.EnvironmentAbstractions
{
return File.ReadAllText(path);
}
public Stream OpenRead(string path)
{
return File.OpenRead(path);
}
}
}

View file

@ -1,6 +1,8 @@
// 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.IO;
namespace Microsoft.Extensions.EnvironmentAbstractions
{
internal interface IFile
@ -8,5 +10,7 @@ namespace Microsoft.Extensions.EnvironmentAbstractions
bool Exists(string path);
string ReadAllText(string path);
Stream OpenRead(string path);
}
}

View file

@ -55,7 +55,7 @@ namespace Microsoft.Extensions.DependencyModel
compilationOptions,
GetLibraries(compilationExports, dependencyLookup, runtime: false).Cast<CompilationLibrary>(),
GetLibraries(runtimeExports, dependencyLookup, runtime: true).Cast<RuntimeLibrary>(),
new KeyValuePair<string, string[]>[0]);
new RuntimeFallbacks[] {});
}
private static CompilationOptions GetCompilationOptions(CommonCompilerOptions compilerOptions)

View file

@ -11,9 +11,6 @@ namespace Microsoft.Extensions.DependencyModel
{
public class DependencyContext
{
private const string DepsJsonExtension = ".deps.json";
private const string DepsFileExtension = ".deps";
private static readonly Lazy<DependencyContext> _defaultContext = new Lazy<DependencyContext>(LoadDefault);
public DependencyContext(string targetFramework,
@ -22,7 +19,7 @@ namespace Microsoft.Extensions.DependencyModel
CompilationOptions compilationOptions,
IEnumerable<CompilationLibrary> compileLibraries,
IEnumerable<RuntimeLibrary> runtimeLibraries,
IEnumerable<KeyValuePair<string, string[]>> runtimeGraph)
IEnumerable<RuntimeFallbacks> runtimeGraph)
{
if (targetFramework == null)
{
@ -32,6 +29,10 @@ namespace Microsoft.Extensions.DependencyModel
{
throw new ArgumentNullException(nameof(runtime));
}
if (compilationOptions == null)
{
throw new ArgumentNullException(nameof(compilationOptions));
}
if (compileLibraries == null)
{
throw new ArgumentNullException(nameof(compileLibraries));
@ -68,48 +69,47 @@ namespace Microsoft.Extensions.DependencyModel
public IReadOnlyList<RuntimeLibrary> RuntimeLibraries { get; }
public IReadOnlyList<KeyValuePair<string, string[]>> RuntimeGraph { get; }
public IReadOnlyList<RuntimeFallbacks> RuntimeGraph { get; }
public DependencyContext Merge(DependencyContext other)
{
if (other == null)
{
throw new ArgumentNullException(nameof(other));
}
return new DependencyContext(
TargetFramework,
Runtime,
IsPortable,
CompilationOptions,
CompileLibraries.Union(other.CompileLibraries, new LibraryMergeEqualityComparer<CompilationLibrary>()),
RuntimeLibraries.Union(other.RuntimeLibraries, new LibraryMergeEqualityComparer<RuntimeLibrary>()),
RuntimeGraph.Union(other.RuntimeGraph)
);
}
private static DependencyContext LoadDefault()
{
var entryAssembly = Assembly.GetEntryAssembly();
return Load(entryAssembly);
return DependencyContextLoader.Default.Load(Assembly.GetEntryAssembly());
}
public static DependencyContext Load(Assembly assembly)
{
if (assembly == null)
return DependencyContextLoader.Default.Load(assembly);
}
private class LibraryMergeEqualityComparer<T>: IEqualityComparer<T> where T:Library
{
public bool Equals(T x, T y)
{
throw new ArgumentNullException(nameof(assembly));
return string.Equals(x.Name, y.Name, StringComparison.Ordinal);
}
using (var stream = assembly.GetManifestResourceStream(assembly.GetName().Name + DepsJsonExtension))
public int GetHashCode(T obj)
{
if (stream != null)
{
return new DependencyContextJsonReader().Read(stream);
}
return obj.Name.GetHashCode();
}
var depsJsonFile = Path.ChangeExtension(assembly.Location, DepsJsonExtension);
if (File.Exists(depsJsonFile))
{
using (var stream = File.OpenRead(depsJsonFile))
{
return new DependencyContextJsonReader().Read(stream);
}
}
var depsFile = Path.ChangeExtension(assembly.Location, DepsFileExtension);
if (File.Exists(depsFile))
{
using (var stream = File.OpenRead(depsFile))
{
return new DependencyContextCsvReader().Read(stream);
}
}
return null;
}
}
}

View file

@ -9,7 +9,7 @@ using System.Text;
namespace Microsoft.Extensions.DependencyModel
{
public class DependencyContextCsvReader
public class DependencyContextCsvReader: IDependencyContextReader
{
public DependencyContext Read(Stream stream)
{
@ -62,7 +62,7 @@ namespace Microsoft.Extensions.DependencyModel
compilationOptions: CompilationOptions.Default,
compileLibraries: Enumerable.Empty<CompilationLibrary>(),
runtimeLibraries: runtimeLibraries.ToArray(),
runtimeGraph: Enumerable.Empty<KeyValuePair<string, string[]>>());
runtimeGraph: Enumerable.Empty<RuntimeFallbacks>());
}
private Tuple<string, string, string, string> PackageIdentity(DepsFileLine line)

View file

@ -10,7 +10,7 @@ using Newtonsoft.Json.Linq;
namespace Microsoft.Extensions.DependencyModel
{
public class DependencyContextJsonReader
public class DependencyContextJsonReader: IDependencyContextReader
{
public DependencyContext Read(Stream stream)
{
@ -79,7 +79,7 @@ namespace Microsoft.Extensions.DependencyModel
);
}
private IEnumerable<KeyValuePair<string, string[]>> ReadRuntimeGraph(JObject runtimes)
private IEnumerable<RuntimeFallbacks> ReadRuntimeGraph(JObject runtimes)
{
if (runtimes == null)
{
@ -90,7 +90,7 @@ namespace Microsoft.Extensions.DependencyModel
var runtime = (JProperty)targets.Single();
foreach (var pair in (JObject)runtime.Value)
{
yield return new KeyValuePair<string, string[]>(pair.Key, pair.Value.Values<string>().ToArray());
yield return new RuntimeFallbacks(pair.Key, pair.Value.Values<string>().ToArray());
}
}

View file

@ -0,0 +1,178 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.Extensions.EnvironmentAbstractions;
namespace Microsoft.Extensions.DependencyModel
{
public class DependencyContextLoader
{
private static Lazy<string[]> _depsFiles = new Lazy<string[]>(GetHostDepsList);
private const string DepsJsonExtension = ".deps.json";
private const string DepsExtension = ".deps";
private readonly string _entryPointDepsLocation;
private readonly string _runtimeDepsLocation;
private readonly IFileSystem _fileSystem;
private readonly IDependencyContextReader _jsonReader;
private readonly IDependencyContextReader _csvReader;
public DependencyContextLoader() : this(
GetDefaultEntrypointDepsLocation(),
GetDefaultRuntimeDepsLocation(),
FileSystemWrapper.Default,
new DependencyContextJsonReader(),
new DependencyContextCsvReader())
{
}
internal DependencyContextLoader(
string entryPointDepsLocation,
string runtimeDepsLocation,
IFileSystem fileSystem,
IDependencyContextReader jsonReader,
IDependencyContextReader csvReader)
{
_entryPointDepsLocation = entryPointDepsLocation;
_runtimeDepsLocation = runtimeDepsLocation;
_fileSystem = fileSystem;
_jsonReader = jsonReader;
_csvReader = csvReader;
}
public static DependencyContextLoader Default { get; } = new DependencyContextLoader();
internal virtual bool IsEntryAssembly(Assembly assembly)
{
return assembly.GetName() == Assembly.GetEntryAssembly().GetName();
}
internal virtual Stream GetResourceStream(Assembly assembly, string name)
{
return assembly.GetManifestResourceStream(name);
}
public DependencyContext Load(Assembly assembly)
{
if (assembly == null)
{
throw new ArgumentNullException(nameof(assembly));
}
DependencyContext context = null;
if (IsEntryAssembly(assembly))
{
context = LoadEntryAssemblyContext();
}
if (context == null)
{
context = LoadAssemblyContext(assembly);
}
if (context?.IsPortable == true)
{
var runtimeContext = LoadRuntimeContext();
if (runtimeContext != null)
{
context = context.Merge(runtimeContext);
}
}
return context;
}
private DependencyContext LoadEntryAssemblyContext()
{
if (!string.IsNullOrEmpty(_entryPointDepsLocation))
{
Debug.Assert(File.Exists(_entryPointDepsLocation));
using (var stream = _fileSystem.File.OpenRead(_entryPointDepsLocation))
{
return _jsonReader.Read(stream);
}
}
return null;
}
private DependencyContext LoadRuntimeContext()
{
if (!string.IsNullOrEmpty(_runtimeDepsLocation))
{
Debug.Assert(File.Exists(_runtimeDepsLocation));
using (var stream = _fileSystem.File.OpenRead(_runtimeDepsLocation))
{
return _jsonReader.Read(stream);
}
}
return null;
}
private DependencyContext LoadAssemblyContext(Assembly assembly)
{
using (var stream = GetResourceStream(assembly, assembly.GetName().Name + DepsJsonExtension))
{
if (stream != null)
{
return _jsonReader.Read(stream);
}
}
var depsJsonFile = Path.ChangeExtension(assembly.Location, DepsJsonExtension);
if (_fileSystem.File.Exists(depsJsonFile))
{
using (var stream = _fileSystem.File.OpenRead(depsJsonFile))
{
return _jsonReader.Read(stream);
}
}
var depsFile = Path.ChangeExtension(assembly.Location, DepsExtension);
if (_fileSystem.File.Exists(depsFile))
{
using (var stream = _fileSystem.File.OpenRead(depsFile))
{
return _csvReader.Read(stream);
}
}
return null;
}
private static string GetDefaultRuntimeDepsLocation()
{
var deps = _depsFiles.Value;
if (deps != null && deps.Length > 1)
{
return deps[1];
}
return null;
}
private static string GetDefaultEntrypointDepsLocation()
{
var deps = _depsFiles.Value;
if (deps != null && deps.Length > 0)
{
return deps[0];
}
return null;
}
private static string[] GetHostDepsList()
{
// TODO: Were going to replace this with AppContext.GetData
var appDomainType = typeof(object).GetTypeInfo().Assembly?.GetType("System.AppDomain");
var currentDomain = appDomainType?.GetProperty("CurrentDomain")?.GetValue(null);
var deps = appDomainType?.GetMethod("GetData")?.Invoke(currentDomain, new[] { "APP_CONTEXT_DEPS_FILES" });
return (deps as string)?.Split(new [] { ';' }, StringSplitOptions.RemoveEmptyEntries);
}
}
}

View file

@ -50,7 +50,7 @@ namespace Microsoft.Extensions.DependencyModel
private JObject WriteRuntimeGraph(DependencyContext context)
{
return new JObject(
context.RuntimeGraph.Select(g => new JProperty(g.Key, new JArray(g.Value)))
context.RuntimeGraph.Select(g => new JProperty(g.Runtime, new JArray(g.Fallbacks)))
);
}
@ -79,7 +79,7 @@ namespace Microsoft.Extensions.DependencyModel
{
if (value != null)
{
o[name] = value.ToString();
o.Add(new JProperty(name, value));
}
}
@ -168,7 +168,7 @@ namespace Microsoft.Extensions.DependencyModel
{
return;
}
libraryObject.Add(
libraryObject.AddFirst(
new JProperty(DependencyContextStrings.DependenciesPropertyName,
new JObject(
dependencies.Select(dependency => new JProperty(dependency.Name, dependency.Version))))
@ -232,8 +232,8 @@ namespace Microsoft.Extensions.DependencyModel
new JObject(runtimeLibrary.RuntimeTargets.SelectMany(WriteRuntimeTarget)))
);
}
AddResourceAssemblies(libraryObject, runtimeLibrary.ResourceAssemblies);
AddRuntimeAssemblies(libraryObject, runtimeLibrary.Assemblies);
AddResourceAssemblies(libraryObject, runtimeLibrary.ResourceAssemblies);
libraryObject.Add(DependencyContextStrings.NativeLibrariesKey, WriteAssetList(runtimeLibrary.NativeLibraries));
dependencies.UnionWith(runtimeLibrary.Dependencies);

View file

@ -0,0 +1,9 @@
using System.IO;
namespace Microsoft.Extensions.DependencyModel
{
public interface IDependencyContextReader
{
DependencyContext Read(Stream stream);
}
}

View file

@ -0,0 +1,19 @@
// 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.Collections.Generic;
namespace Microsoft.Extensions.DependencyModel
{
public class RuntimeFallbacks
{
public string Runtime { get; set; }
public IEnumerable<string> Fallbacks { get; set; }
public RuntimeFallbacks(string runtime, IEnumerable<string> fallbacks)
{
Runtime = runtime;
Fallbacks = fallbacks;
}
}
}

View file

@ -4,6 +4,7 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Microsoft.Extensions.EnvironmentAbstractions;
namespace Microsoft.Extensions.DependencyModel.Tests
@ -74,6 +75,11 @@ namespace Microsoft.Extensions.DependencyModel.Tests
}
return text;
}
public Stream OpenRead(string path)
{
return new MemoryStream(Encoding.UTF8.GetBytes(ReadAllText(path)));
}
}
private class DirectoryMock : IDirectory

View file

@ -96,14 +96,14 @@ namespace Microsoft.Extensions.DependencyModel.Tests
}
}
}");
context.RuntimeGraph.Should().Contain(p => p.Key == "osx.10.10-x64").Which
.Value.Should().BeEquivalentTo();
context.RuntimeGraph.Should().Contain(p => p.Runtime == "osx.10.10-x64").Which
.Fallbacks.Should().BeEquivalentTo();
context.RuntimeGraph.Should().Contain(p => p.Key == "osx.10.11-x64").Which
.Value.Should().BeEquivalentTo("osx");
context.RuntimeGraph.Should().Contain(p => p.Runtime == "osx.10.11-x64").Which
.Fallbacks.Should().BeEquivalentTo("osx");
context.RuntimeGraph.Should().Contain(p => p.Key == "rhel.7-x64").Which
.Value.Should().BeEquivalentTo("linux-x64", "unix");
context.RuntimeGraph.Should().Contain(p => p.Runtime == "rhel.7-x64").Which
.Fallbacks.Should().BeEquivalentTo("linux-x64", "unix");
}
[Fact]

View file

@ -37,7 +37,7 @@ namespace Microsoft.Extensions.DependencyModel.Tests
CompilationOptions compilationOptions = null,
CompilationLibrary[] compileLibraries = null,
RuntimeLibrary[] runtimeLibraries = null,
IReadOnlyList<KeyValuePair<string, string[]>> runtimeGraph = null)
IReadOnlyList<RuntimeFallbacks> runtimeGraph = null)
{
return new DependencyContext(
target ?? string.Empty,
@ -46,7 +46,7 @@ namespace Microsoft.Extensions.DependencyModel.Tests
compilationOptions ?? CompilationOptions.Default,
compileLibraries ?? new CompilationLibrary[0],
runtimeLibraries ?? new RuntimeLibrary[0],
runtimeGraph ?? new KeyValuePair<string, string[]>[0]
runtimeGraph ?? new RuntimeFallbacks[0]
);
}
@ -58,8 +58,8 @@ namespace Microsoft.Extensions.DependencyModel.Tests
"Target/runtime",
runtimeGraph: new[]
{
new KeyValuePair<string, string[]>("win7-x64", new [] { "win6", "win5"}),
new KeyValuePair<string, string[]>("win8-x64", new [] { "win7-x64"}),
new RuntimeFallbacks("win7-x64", new [] { "win6", "win5"}),
new RuntimeFallbacks("win8-x64", new [] { "win7-x64"}),
}));
var rids = result.Should().HaveProperty("runtimes")

View file

@ -0,0 +1,136 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyModel;
using FluentAssertions;
using Xunit;
namespace Microsoft.Extensions.DependencyModel.Tests
{
public class DependencyContextTests
{
[Fact]
public void MergeMergesLibraries()
{
var compilationLibraries = new[]
{
CreateCompilation("PackageA"),
CreateCompilation("PackageB"),
};
var runtimeLibraries = new[]
{
CreateRuntime("PackageA"),
CreateRuntime("PackageB"),
};
var compilationLibrariesRedist = new[]
{
CreateCompilation("PackageB"),
CreateCompilation("PackageC"),
};
var runtimeLibrariesRedist = new[]
{
CreateRuntime("PackageB"),
CreateRuntime("PackageC"),
};
var context = new DependencyContext(
"Framework",
"runtime",
true,
CompilationOptions.Default,
compilationLibraries,
runtimeLibraries,
new RuntimeFallbacks[] { });
var contextRedist = new DependencyContext(
"Framework",
"runtime",
true,
CompilationOptions.Default,
compilationLibrariesRedist,
runtimeLibrariesRedist,
new RuntimeFallbacks[] { });
var result = context.Merge(contextRedist);
result.CompileLibraries.Should().BeEquivalentTo(new[]
{
compilationLibraries[0],
compilationLibraries[1],
compilationLibrariesRedist[1],
});
result.RuntimeLibraries.Should().BeEquivalentTo(new[]
{
runtimeLibraries[0],
runtimeLibraries[1],
runtimeLibrariesRedist[1],
});
}
public void MergeMergesRuntimeGraph()
{
var context = new DependencyContext(
"Framework",
"runtime",
true,
CompilationOptions.Default,
Enumerable.Empty<CompilationLibrary>(),
Enumerable.Empty<RuntimeLibrary>(),
new RuntimeFallbacks[]
{
new RuntimeFallbacks("win8-x64", new [] { "win8" }),
});
var contextRedist = new DependencyContext(
"Framework",
"runtime",
true,
CompilationOptions.Default,
Enumerable.Empty<CompilationLibrary>(),
Enumerable.Empty<RuntimeLibrary>(),
new RuntimeFallbacks[]
{
new RuntimeFallbacks("win8", new [] { "win7-x64", "win7-x86" }),
});
var result = context.Merge(contextRedist);
result.RuntimeGraph.Should().Contain(g => g.Runtime == "win8-x64").
Subject.Fallbacks.Should().BeEquivalentTo("win8");
result.RuntimeGraph.Should().Contain(g => g.Runtime == "win8").
Subject.Fallbacks.Should().BeEquivalentTo("win7-x64", "win7-x86");
}
private CompilationLibrary CreateCompilation(string name)
{
return new CompilationLibrary(
"project",
name,
"1.1.1",
"HASH",
new string[] { },
new Dependency[] { },
false);
}
private RuntimeLibrary CreateRuntime(string name)
{
return new RuntimeLibrary(
"project",
name,
"1.1.1",
"HASH",
new RuntimeAssembly[] { },
new string[] { },
new ResourceAssembly[] { },
new RuntimeTarget[] { },
new Dependency[] {},
false);
}
}
}