diff --git a/src/Microsoft.AspNetCore.Mvc.Core/DependencyContext/CompilationLibrary.cs b/src/Microsoft.AspNetCore.Mvc.Core/DependencyContext/CompilationLibrary.cs new file mode 100644 index 0000000000..89be85b592 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/DependencyContext/CompilationLibrary.cs @@ -0,0 +1,45 @@ +// 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.Linq; +using Microsoft.Extensions.DependencyModel.Resolution; + +namespace Microsoft.Extensions.DependencyModel +{ + internal class CompilationLibrary : Library + { + public CompilationLibrary(string type, + string name, + string version, + string hash, + IEnumerable assemblies, + IEnumerable dependencies, + bool serviceable) + : base(type, name, version, hash, dependencies, serviceable) + { + Assemblies = assemblies.ToArray(); + } + + public IReadOnlyList Assemblies { get; } + + internal static ICompilationAssemblyResolver DefaultResolver { get; } = new CompositeCompilationAssemblyResolver(new ICompilationAssemblyResolver[] + { + new PackageCacheCompilationAssemblyResolver(), + new AppBaseCompilationAssemblyResolver(), + new ReferenceAssemblyPathResolver(), + new PackageCompilationAssemblyResolver() + }); + + public IEnumerable ResolveReferencePaths() + { + var assemblies = new List(); + if (!DefaultResolver.TryResolveAssemblyPaths(this, assemblies)) + { + throw new InvalidOperationException($"Can not find compilation library location for package '{Name}'"); + } + return assemblies; + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Core/DependencyContext/CompilationOptions.cs b/src/Microsoft.AspNetCore.Mvc.Core/DependencyContext/CompilationOptions.cs new file mode 100644 index 0000000000..16d6d024e5 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/DependencyContext/CompilationOptions.cs @@ -0,0 +1,76 @@ +// 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; +using System.Linq; + +namespace Microsoft.Extensions.DependencyModel +{ + internal class CompilationOptions + { + public IEnumerable Defines { get; } + + public string LanguageVersion { get; } + + public string Platform { get; } + + public bool? AllowUnsafe { get; } + + public bool? WarningsAsErrors { get; } + + public bool? Optimize { get; } + + public string KeyFile { get; } + + public bool? DelaySign { get; } + + public bool? PublicSign { get; } + + public string DebugType { get; } + + public bool? EmitEntryPoint { get; } + + public bool? GenerateXmlDocumentation { get; } + + public static CompilationOptions Default { get; } = new CompilationOptions( + defines: Enumerable.Empty(), + languageVersion: null, + platform: null, + allowUnsafe: null, + warningsAsErrors: null, + optimize: null, + keyFile: null, + delaySign: null, + publicSign: null, + debugType: null, + emitEntryPoint: null, + generateXmlDocumentation: null); + + public CompilationOptions(IEnumerable defines, + string languageVersion, + string platform, + bool? allowUnsafe, + bool? warningsAsErrors, + bool? optimize, + string keyFile, + bool? delaySign, + bool? publicSign, + string debugType, + bool? emitEntryPoint, + bool? generateXmlDocumentation) + { + Defines = defines; + LanguageVersion = languageVersion; + Platform = platform; + AllowUnsafe = allowUnsafe; + WarningsAsErrors = warningsAsErrors; + Optimize = optimize; + KeyFile = keyFile; + DelaySign = delaySign; + PublicSign = publicSign; + DebugType = debugType; + EmitEntryPoint = emitEntryPoint; + GenerateXmlDocumentation = generateXmlDocumentation; + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Core/DependencyContext/Dependency.cs b/src/Microsoft.AspNetCore.Mvc.Core/DependencyContext/Dependency.cs new file mode 100644 index 0000000000..ff302ff515 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/DependencyContext/Dependency.cs @@ -0,0 +1,38 @@ +// 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 Microsoft.Extensions.Internal; + +namespace Microsoft.Extensions.DependencyModel +{ + internal struct Dependency + { + public Dependency(string name, string version) + { + Name = name; + Version = version; + } + + public string Name { get; } + public string Version { get; } + + public bool Equals(Dependency other) + { + return string.Equals(Name, other.Name) && string.Equals(Version, other.Version); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + return obj is Dependency && Equals((Dependency) obj); + } + + public override int GetHashCode() + { + var combiner = HashCodeCombiner.Start(); + combiner.Add(Name); + combiner.Add(Version); + return combiner.CombinedHash; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Core/DependencyContext/DependencyContext.cs b/src/Microsoft.AspNetCore.Mvc.Core/DependencyContext/DependencyContext.cs new file mode 100644 index 0000000000..3a6c966e78 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/DependencyContext/DependencyContext.cs @@ -0,0 +1,115 @@ +// 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.IO; +using System.Reflection; +using System.Collections.Generic; +using System.Linq; + +namespace Microsoft.Extensions.DependencyModel +{ + internal class DependencyContext + { + private static readonly Lazy _defaultContext = new Lazy(LoadDefault); + + public DependencyContext(string targetFramework, + string runtime, + bool isPortable, + CompilationOptions compilationOptions, + IEnumerable compileLibraries, + IEnumerable runtimeLibraries, + IEnumerable runtimeGraph) + { + if (targetFramework == null) + { + throw new ArgumentNullException(nameof(targetFramework)); + } + if (runtime == null) + { + throw new ArgumentNullException(nameof(runtime)); + } + if (compilationOptions == null) + { + throw new ArgumentNullException(nameof(compilationOptions)); + } + if (compileLibraries == null) + { + throw new ArgumentNullException(nameof(compileLibraries)); + } + if (runtimeLibraries == null) + { + throw new ArgumentNullException(nameof(runtimeLibraries)); + } + if (runtimeGraph == null) + { + throw new ArgumentNullException(nameof(runtimeGraph)); + } + + TargetFramework = targetFramework; + Runtime = runtime; + IsPortable = isPortable; + CompilationOptions = compilationOptions; + CompileLibraries = compileLibraries.ToArray(); + RuntimeLibraries = runtimeLibraries.ToArray(); + RuntimeGraph = runtimeGraph.ToArray(); + } + + public static DependencyContext Default => _defaultContext.Value; + + public string TargetFramework { get; } + + public string Runtime { get; } + + public bool IsPortable { get; } + + public CompilationOptions CompilationOptions { get; } + + public IReadOnlyList CompileLibraries { get; } + + public IReadOnlyList RuntimeLibraries { get; } + + public IReadOnlyList 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()), + RuntimeLibraries.Union(other.RuntimeLibraries, new LibraryMergeEqualityComparer()), + RuntimeGraph.Union(other.RuntimeGraph) + ); + } + + private static DependencyContext LoadDefault() + { + return DependencyContextLoader.Default.Load(Assembly.GetEntryAssembly()); + } + + public static DependencyContext Load(Assembly assembly) + { + return DependencyContextLoader.Default.Load(assembly); + } + + private class LibraryMergeEqualityComparer: IEqualityComparer where T:Library + { + public bool Equals(T x, T y) + { + return string.Equals(x.Name, y.Name, StringComparison.Ordinal); + } + + public int GetHashCode(T obj) + { + return obj.Name.GetHashCode(); + } + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Core/DependencyContext/DependencyContextCsvReader.cs b/src/Microsoft.AspNetCore.Mvc.Core/DependencyContext/DependencyContextCsvReader.cs new file mode 100644 index 0000000000..8d05fa13ea --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/DependencyContext/DependencyContextCsvReader.cs @@ -0,0 +1,135 @@ +// 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 System.Text; + +namespace Microsoft.Extensions.DependencyModel +{ + internal class DependencyContextCsvReader: IDependencyContextReader + { + public DependencyContext Read(Stream stream) + { + var lines = new List(); + using (var reader = new StreamReader(stream)) + { + while (!reader.EndOfStream) + { + var line = new DepsFileLine(); + line.LibraryType = ReadValue(reader); + line.PackageName = ReadValue(reader); + line.PackageVersion = ReadValue(reader); + line.PackageHash = ReadValue(reader); + line.AssetType = ReadValue(reader); + line.AssetName = ReadValue(reader); + line.AssetPath = ReadValue(reader); + + if (line.AssetType == "runtime" && + !line.AssetPath.EndsWith(".ni.dll")) + { + lines.Add(line); + } + SkipWhitespace(reader); + } + } + + var runtimeLibraries = new List(); + var packageGroups = lines.GroupBy(PackageIdentity); + foreach (var packageGroup in packageGroups) + { + var identity = packageGroup.Key; + runtimeLibraries.Add(new RuntimeLibrary( + type: identity.Item1, + name: identity.Item2, + version: identity.Item3, + hash: identity.Item4, + assemblies: packageGroup.Select(l => RuntimeAssembly.Create(l.AssetPath)), + nativeLibraries: Enumerable.Empty(), + resourceAssemblies: Enumerable.Empty(), + subTargets: Enumerable.Empty(), + dependencies: Enumerable.Empty(), + serviceable: false + )); + } + + return new DependencyContext( + targetFramework: string.Empty, + runtime: string.Empty, + isPortable: false, + compilationOptions: CompilationOptions.Default, + compileLibraries: Enumerable.Empty(), + runtimeLibraries: runtimeLibraries.ToArray(), + runtimeGraph: Enumerable.Empty()); + } + + private Tuple PackageIdentity(DepsFileLine line) + { + return Tuple.Create(line.LibraryType, line.PackageName, line.PackageVersion, line.PackageHash); + } + + private void SkipWhitespace(StreamReader reader) + { + // skip all whitespace + while (!reader.EndOfStream && char.IsWhiteSpace((char)reader.Peek())) + { + reader.Read(); + } + } + + private string ReadValue(StreamReader reader) + { + SkipWhitespace(reader); + + var c = ReadSucceed(reader.Read()); + if (c != '"') + { + throw new FormatException("Deps file value should start with '\"'"); + } + + var value = new StringBuilder(); + while (ReadSucceed(reader.Peek()) != '"') + { + c = ReadSucceed(reader.Read()); + if (c == '\\') + { + value.Append(ReadSucceed(reader.Read())); + } + else + { + value.Append(c); + } + } + // Read last " + ReadSucceed(reader.Read()); + // Read comment + if (reader.Peek() == ',') + { + reader.Read(); + } + return value.ToString(); + } + + private char ReadSucceed(int c) + { + if (c == -1) + { + throw new FormatException("Unexpected end of file"); + } + return (char) c; + } + + private struct DepsFileLine + { + public string LibraryType; + public string PackageName; + public string PackageVersion; + public string PackageHash; + public string AssetType; + public string AssetName; + public string AssetPath; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Core/DependencyContext/DependencyContextJsonReader.cs b/src/Microsoft.AspNetCore.Mvc.Core/DependencyContext/DependencyContextJsonReader.cs new file mode 100644 index 0000000000..99831abcd4 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/DependencyContext/DependencyContextJsonReader.cs @@ -0,0 +1,294 @@ +// 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 +{ + internal class DependencyContextJsonReader: IDependencyContextReader + { + 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; + } + + foreach (var pair in runtimes) + { + yield return new RuntimeFallbacks(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 = ReadAssetList(libraryObject, DependencyContextStrings.RuntimeAssembliesKey) + .Select(RuntimeAssembly.Create) + .ToArray(); + + var nativeLibraries = ReadAssetList(libraryObject, DependencyContextStrings.NativeLibrariesKey); + + var resourceAssemblies = ReadResourceAssemblies((JObject)libraryObject[DependencyContextStrings.ResourceAssembliesPropertyName]); + + return new RuntimeLibrary( + type: stub.Type, + name: name, + version: version, + hash: stub.Hash, + assemblies: assemblies, + nativeLibraries: nativeLibraries, + resourceAssemblies: resourceAssemblies, + subTargets: runtimeTargets.ToArray(), + dependencies: dependencies, + serviceable: stub.Serviceable); + } + else + { + var assemblies = ReadAssetList(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[] ReadAssetList(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; + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Core/DependencyContext/DependencyContextLoader.cs b/src/Microsoft.AspNetCore.Mvc.Core/DependencyContext/DependencyContextLoader.cs new file mode 100644 index 0000000000..09df09bd83 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/DependencyContext/DependencyContextLoader.cs @@ -0,0 +1,170 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Reflection; + +namespace Microsoft.Extensions.DependencyModel +{ + internal class DependencyContextLoader + { + private static Lazy _depsFiles = new Lazy(GetHostDepsList); + + private const string DepsJsonExtension = ".deps.json"; + private const string DepsExtension = ".deps"; + + private readonly string _entryPointDepsLocation; + private readonly string _runtimeDepsLocation; + private readonly IDependencyContextReader _jsonReader; + private readonly IDependencyContextReader _csvReader; + + public DependencyContextLoader() : this( + GetDefaultEntrypointDepsLocation(), + GetDefaultRuntimeDepsLocation(), + new DependencyContextJsonReader(), + new DependencyContextCsvReader()) + { + } + + internal DependencyContextLoader( + string entryPointDepsLocation, + string runtimeDepsLocation, + IDependencyContextReader jsonReader, + IDependencyContextReader csvReader) + { + _entryPointDepsLocation = entryPointDepsLocation; + _runtimeDepsLocation = runtimeDepsLocation; + _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 = File.OpenRead(_entryPointDepsLocation)) + { + return _jsonReader.Read(stream); + } + } + return null; + } + + private DependencyContext LoadRuntimeContext() + { + if (!string.IsNullOrEmpty(_runtimeDepsLocation)) + { + Debug.Assert(File.Exists(_runtimeDepsLocation)); + using (var stream = 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 (File.Exists(depsJsonFile)) + { + using (var stream = File.OpenRead(depsJsonFile)) + { + return _jsonReader.Read(stream); + } + } + + var depsFile = Path.ChangeExtension(assembly.Location, DepsExtension); + if (File.Exists(depsFile)) + { + using (var stream = 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); + } + + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Core/DependencyContext/DependencyContextStrings.cs b/src/Microsoft.AspNetCore.Mvc.Core/DependencyContext/DependencyContextStrings.cs new file mode 100644 index 0000000000..b5df3f7a3c --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/DependencyContext/DependencyContextStrings.cs @@ -0,0 +1,76 @@ +// 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. + +namespace Microsoft.Extensions.DependencyModel +{ + internal class DependencyContextStrings + { + internal const char VersionSeperator = '/'; + + internal const string CompileTimeAssembliesKey = "compile"; + + internal const string RuntimeAssembliesKey = "runtime"; + + internal const string NativeLibrariesKey = "native"; + + internal const string RuntimeTargetPropertyName = "runtimeTarget"; + + internal const string LibrariesPropertyName = "libraries"; + + internal const string TargetsPropertyName = "targets"; + + internal const string DependenciesPropertyName = "dependencies"; + + internal const string Sha512PropertyName = "sha512"; + + internal const string TypePropertyName = "type"; + + internal const string ServiceablePropertyName = "serviceable"; + + internal const string CompilationOptionsPropertName = "compilationOptions"; + + internal const string DefinesPropertyName = "defines"; + + internal const string LanguageVersionPropertyName = "languageVersion"; + + internal const string PlatformPropertyName = "platform"; + + internal const string AllowUnsafePropertyName = "allowUnsafe"; + + internal const string WarningsAsErrorsPropertyName = "warningsAsErrors"; + + internal const string OptimizePropertyName = "optimize"; + + internal const string KeyFilePropertyName = "keyFile"; + + internal const string DelaySignPropertyName = "delaySign"; + + internal const string PublicSignPropertyName = "publicSign"; + + internal const string DebugTypePropertyName = "debugType"; + + internal const string EmitEntryPointPropertyName = "emitEntryPoint"; + + internal const string GenerateXmlDocumentationPropertyName = "xmlDoc"; + + internal const string PortablePropertyName = "portable"; + + internal const string RuntimeTargetNamePropertyName = "name"; + + internal const string RuntimesPropertyName = "runtimes"; + + internal const string RuntimeTargetsPropertyName = "runtimeTargets"; + + internal const string RidPropertyName = "rid"; + + internal const string AssetTypePropertyName = "assetType"; + + internal const string RuntimeAssetType = "runtime"; + + internal const string NativeAssetType = "native"; + + internal const string ResourceAssembliesPropertyName = "resources"; + + internal const string LocalePropertyName = "locale"; + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Core/DependencyContext/DependencyContextWriter.cs b/src/Microsoft.AspNetCore.Mvc.Core/DependencyContext/DependencyContextWriter.cs new file mode 100644 index 0000000000..eacc51f5e6 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/DependencyContext/DependencyContextWriter.cs @@ -0,0 +1,309 @@ +// 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.Diagnostics; +using System.IO; +using System.Linq; +using Microsoft.Extensions.PlatformAbstractions; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace Microsoft.Extensions.DependencyModel +{ + internal class DependencyContextWriter + { + public void Write(DependencyContext context, Stream stream) + { + using (var writer = new StreamWriter(stream)) + { + using (var jsonWriter = new JsonTextWriter(writer) { Formatting = Formatting.Indented }) + { + Write(context).WriteTo(jsonWriter); + } + } + } + + private JObject Write(DependencyContext context) + { + var contextObject = new JObject( + new JProperty(DependencyContextStrings.RuntimeTargetPropertyName, WriteRuntimeTargetInfo(context)), + new JProperty(DependencyContextStrings.CompilationOptionsPropertName, WriteCompilationOptions(context.CompilationOptions)), + new JProperty(DependencyContextStrings.TargetsPropertyName, WriteTargets(context)), + new JProperty(DependencyContextStrings.LibrariesPropertyName, WriteLibraries(context)) + ); + if (context.RuntimeGraph.Any()) + { + contextObject.Add(new JProperty(DependencyContextStrings.RuntimesPropertyName, WriteRuntimeGraph(context))); + } + return contextObject; + } + + private string WriteRuntimeTargetInfo(DependencyContext context) + { + return context.IsPortable? + context.TargetFramework : + context.TargetFramework + DependencyContextStrings.VersionSeperator + context.Runtime; + } + + private JObject WriteRuntimeGraph(DependencyContext context) + { + return new JObject( + context.RuntimeGraph.Select(g => new JProperty(g.Runtime, new JArray(g.Fallbacks))) + ); + } + + private JObject WriteCompilationOptions(CompilationOptions compilationOptions) + { + var o = new JObject(); + if (compilationOptions.Defines?.Any() == true) + { + o[DependencyContextStrings.DefinesPropertyName] = new JArray(compilationOptions.Defines); + } + AddPropertyIfNotNull(o, DependencyContextStrings.LanguageVersionPropertyName, compilationOptions.LanguageVersion); + AddPropertyIfNotNull(o, DependencyContextStrings.PlatformPropertyName, compilationOptions.Platform); + AddPropertyIfNotNull(o, DependencyContextStrings.AllowUnsafePropertyName, compilationOptions.AllowUnsafe); + AddPropertyIfNotNull(o, DependencyContextStrings.WarningsAsErrorsPropertyName, compilationOptions.WarningsAsErrors); + AddPropertyIfNotNull(o, DependencyContextStrings.OptimizePropertyName, compilationOptions.Optimize); + AddPropertyIfNotNull(o, DependencyContextStrings.KeyFilePropertyName, compilationOptions.KeyFile); + AddPropertyIfNotNull(o, DependencyContextStrings.DelaySignPropertyName, compilationOptions.DelaySign); + AddPropertyIfNotNull(o, DependencyContextStrings.PublicSignPropertyName, compilationOptions.PublicSign); + AddPropertyIfNotNull(o, DependencyContextStrings.EmitEntryPointPropertyName, compilationOptions.EmitEntryPoint); + AddPropertyIfNotNull(o, DependencyContextStrings.GenerateXmlDocumentationPropertyName, compilationOptions.GenerateXmlDocumentation); + AddPropertyIfNotNull(o, DependencyContextStrings.DebugTypePropertyName, compilationOptions.DebugType); + return o; + } + + private void AddPropertyIfNotNull(JObject o, string name, T value) + { + if (value != null) + { + o.Add(new JProperty(name, value)); + } + } + + private JObject WriteTargets(DependencyContext context) + { + if (context.IsPortable) + { + return new JObject( + new JProperty(context.TargetFramework, WritePortableTarget(context.RuntimeLibraries, context.CompileLibraries)) + ); + } + + return new JObject( + new JProperty(context.TargetFramework, WriteTarget(context.CompileLibraries)), + new JProperty(context.TargetFramework + DependencyContextStrings.VersionSeperator + context.Runtime, + WriteTarget(context.RuntimeLibraries)) + ); + } + + private JObject WriteTarget(IReadOnlyList libraries) + { + return new JObject( + libraries.Select(library => + new JProperty(library.Name + DependencyContextStrings.VersionSeperator + library.Version, WriteTargetLibrary(library)))); + } + + private JObject WritePortableTarget(IReadOnlyList runtimeLibraries, IReadOnlyList compilationLibraries) + { + var runtimeLookup = runtimeLibraries.ToDictionary(l => l.Name); + var compileLookup = compilationLibraries.ToDictionary(l => l.Name); + + var targetObject = new JObject(); + + foreach (var packageName in runtimeLookup.Keys.Concat(compileLookup.Keys).Distinct()) + { + RuntimeLibrary runtimeLibrary; + runtimeLookup.TryGetValue(packageName, out runtimeLibrary); + + CompilationLibrary compilationLibrary; + compileLookup.TryGetValue(packageName, out compilationLibrary); + + if (compilationLibrary != null && runtimeLibrary != null) + { + Debug.Assert(compilationLibrary.Serviceable == runtimeLibrary.Serviceable); + Debug.Assert(compilationLibrary.Version == runtimeLibrary.Version); + Debug.Assert(compilationLibrary.Hash == runtimeLibrary.Hash); + Debug.Assert(compilationLibrary.Type == runtimeLibrary.Type); + } + + var library = (Library)compilationLibrary ?? (Library)runtimeLibrary; + targetObject.Add( + new JProperty(library.Name + DependencyContextStrings.VersionSeperator + library.Version, + WritePortableTargetLibrary(runtimeLibrary, compilationLibrary) + ) + ); + + } + return targetObject; + } + + private void AddCompilationAssemblies(JObject libraryObject, IEnumerable compilationAssemblies) + { + if (!compilationAssemblies.Any()) + { + return; + } + libraryObject.Add(new JProperty(DependencyContextStrings.CompileTimeAssembliesKey, + WriteAssetList(compilationAssemblies)) + ); + } + + private void AddRuntimeAssemblies(JObject libraryObject, IEnumerable runtimeAssemblies) + { + if (!runtimeAssemblies.Any()) + { + return; + } + libraryObject.Add(new JProperty(DependencyContextStrings.RuntimeAssembliesKey, + WriteAssetList(runtimeAssemblies.Select(a => a.Path))) + ); + } + + private void AddDependencies(JObject libraryObject, IEnumerable dependencies) + { + if (!dependencies.Any()) + { + return; + } + libraryObject.AddFirst( + new JProperty(DependencyContextStrings.DependenciesPropertyName, + new JObject( + dependencies.Select(dependency => new JProperty(dependency.Name, dependency.Version)))) + ); + } + + private void AddResourceAssemblies(JObject libraryObject, IEnumerable resourceAssemblies) + { + if (!resourceAssemblies.Any()) + { + return; + } + libraryObject.Add(DependencyContextStrings.ResourceAssembliesPropertyName, + new JObject(resourceAssemblies.Select(a => + new JProperty(NormalizePath(a.Path), new JObject(new JProperty(DependencyContextStrings.LocalePropertyName, a.Locale)))) + ) + ); + } + + private JObject WriteTargetLibrary(Library library) + { + var runtimeLibrary = library as RuntimeLibrary; + if (runtimeLibrary != null) + { + var libraryObject = new JObject(); + AddDependencies(libraryObject, runtimeLibrary.Dependencies); + AddRuntimeAssemblies(libraryObject, runtimeLibrary.Assemblies); + AddResourceAssemblies(libraryObject, runtimeLibrary.ResourceAssemblies); + + if (runtimeLibrary.NativeLibraries.Any()) + { + libraryObject.Add(DependencyContextStrings.NativeLibrariesKey, WriteAssetList(runtimeLibrary.NativeLibraries)); + } + + return libraryObject; + } + + var compilationLibrary = library as CompilationLibrary; + if (compilationLibrary != null) + { + var libraryObject = new JObject(); + AddDependencies(libraryObject, compilationLibrary.Dependencies); + AddCompilationAssemblies(libraryObject, compilationLibrary.Assemblies); + return libraryObject; + } + throw new NotSupportedException(); + } + + private JObject WritePortableTargetLibrary(RuntimeLibrary runtimeLibrary, CompilationLibrary compilationLibrary) + { + var libraryObject = new JObject(); + + var dependencies = new HashSet(); + if (runtimeLibrary != null) + { + + if (runtimeLibrary.RuntimeTargets.Any()) + { + libraryObject.Add(new JProperty( + DependencyContextStrings.RuntimeTargetsPropertyName, + new JObject(runtimeLibrary.RuntimeTargets.SelectMany(WriteRuntimeTarget))) + ); + } + AddRuntimeAssemblies(libraryObject, runtimeLibrary.Assemblies); + AddResourceAssemblies(libraryObject, runtimeLibrary.ResourceAssemblies); + libraryObject.Add(DependencyContextStrings.NativeLibrariesKey, WriteAssetList(runtimeLibrary.NativeLibraries)); + + dependencies.UnionWith(runtimeLibrary.Dependencies); + } + + if (compilationLibrary != null) + { + AddCompilationAssemblies(libraryObject, compilationLibrary.Assemblies); + + dependencies.UnionWith(compilationLibrary.Dependencies); + } + + AddDependencies(libraryObject, dependencies); + return libraryObject; + } + + private IEnumerable WriteRuntimeTarget(RuntimeTarget target) + { + var runtime = WriteRuntimeTargetAssemblies( + target.Assemblies.Select(a => a.Path), + target.Runtime, + DependencyContextStrings.RuntimeAssetType); + + var native = WriteRuntimeTargetAssemblies( + target.NativeLibraries, + target.Runtime, + DependencyContextStrings.NativeAssetType); + + return runtime.Concat(native); + } + + private IEnumerable WriteRuntimeTargetAssemblies(IEnumerable assemblies, string runtime, string assetType) + { + foreach (var assembly in assemblies) + { + yield return new JProperty(NormalizePath(assembly), + new JObject( + new JProperty(DependencyContextStrings.RidPropertyName, runtime), + new JProperty(DependencyContextStrings.AssetTypePropertyName, assetType) + ) + ); + } + } + + private JObject WriteAssetList(IEnumerable assetPaths) + { + return new JObject(assetPaths.Select(assembly => new JProperty(NormalizePath(assembly), new JObject()))); + } + + private JObject WriteLibraries(DependencyContext context) + { + var allLibraries = + context.RuntimeLibraries.Cast().Concat(context.CompileLibraries) + .GroupBy(library => library.Name + DependencyContextStrings.VersionSeperator + library.Version); + + return new JObject(allLibraries.Select(libraries=> new JProperty(libraries.Key, WriteLibrary(libraries.First())))); + } + + private JObject WriteLibrary(Library library) + { + return new JObject( + new JProperty(DependencyContextStrings.TypePropertyName, library.Type), + new JProperty(DependencyContextStrings.ServiceablePropertyName, library.Serviceable), + new JProperty(DependencyContextStrings.Sha512PropertyName, library.Hash) + ); + } + + private string NormalizePath(string path) + { + return path.Replace('\\', '/'); + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Core/DependencyContext/IDependencyContextReader.cs b/src/Microsoft.AspNetCore.Mvc.Core/DependencyContext/IDependencyContextReader.cs new file mode 100644 index 0000000000..8ff86cd584 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/DependencyContext/IDependencyContextReader.cs @@ -0,0 +1,9 @@ +using System.IO; + +namespace Microsoft.Extensions.DependencyModel +{ + internal interface IDependencyContextReader + { + DependencyContext Read(Stream stream); + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Core/DependencyContext/Library.cs b/src/Microsoft.AspNetCore.Mvc.Core/DependencyContext/Library.cs new file mode 100644 index 0000000000..2acb2bf8f3 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/DependencyContext/Library.cs @@ -0,0 +1,33 @@ +// 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.Linq; +using System.Collections.Generic; + +namespace Microsoft.Extensions.DependencyModel +{ + internal class Library + { + public Library(string type, string name, string version, string hash, IEnumerable dependencies, bool serviceable) + { + Type = type; + Name = name; + Version = version; + Hash = hash; + Dependencies = dependencies.ToArray(); + Serviceable = serviceable; + } + + public string Type { get; } + + public string Name { get; } + + public string Version { get; } + + public string Hash { get; } + + public IReadOnlyList Dependencies { get; } + + public bool Serviceable { get; } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Core/DependencyContext/Resolution/AppBaseCompilationAssemblyResolver.cs b/src/Microsoft.AspNetCore.Mvc.Core/DependencyContext/Resolution/AppBaseCompilationAssemblyResolver.cs new file mode 100644 index 0000000000..c50eb15be4 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/DependencyContext/Resolution/AppBaseCompilationAssemblyResolver.cs @@ -0,0 +1,75 @@ +// 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 Microsoft.Extensions.PlatformAbstractions; + +namespace Microsoft.Extensions.DependencyModel.Resolution +{ + internal class AppBaseCompilationAssemblyResolver : ICompilationAssemblyResolver + { + private readonly string _basePath; + + public AppBaseCompilationAssemblyResolver() + { + _basePath = PlatformServices.Default.Application.ApplicationBasePath; + } + + public bool TryResolveAssemblyPaths(CompilationLibrary library, List assemblies) + { + var isProject = string.Equals(library.Type, "project", StringComparison.OrdinalIgnoreCase); + + if (!isProject && + !string.Equals(library.Type, "package", StringComparison.OrdinalIgnoreCase) && + !string.Equals(library.Type, "referenceassembly", StringComparison.OrdinalIgnoreCase)) + { + return false; + } + + var refsPath = Path.Combine(_basePath, "refs"); + var hasRefs = Directory.Exists(refsPath); + + // Resolving packages and reference assebmlies requires refs folder to exist + if (!isProject && !hasRefs) + { + return false; + } + + var directories = new List() + { + _basePath + }; + + if (hasRefs) + { + directories.Insert(0, refsPath); + } + + foreach (var assembly in library.Assemblies) + { + bool resolved = false; + var assemblyFile = Path.GetFileName(assembly); + foreach (var directory in directories) + { + string fullName; + if (ResolverUtils.TryResolveAssemblyFile(directory, assemblyFile, out fullName)) + { + assemblies.Add(fullName); + resolved = true; + break; + } + } + + if (!resolved) + { + throw new InvalidOperationException( + $"Can not find assembly file {assemblyFile} at '{string.Join(",", directories)}'"); + } + } + + return true; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Core/DependencyContext/Resolution/CompositeCompilationAssemblyResolver.cs b/src/Microsoft.AspNetCore.Mvc.Core/DependencyContext/Resolution/CompositeCompilationAssemblyResolver.cs new file mode 100644 index 0000000000..1cb3e05c7f --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/DependencyContext/Resolution/CompositeCompilationAssemblyResolver.cs @@ -0,0 +1,29 @@ +// 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.Resolution +{ + internal class CompositeCompilationAssemblyResolver: ICompilationAssemblyResolver + { + private readonly ICompilationAssemblyResolver[] _resolvers; + + public CompositeCompilationAssemblyResolver(ICompilationAssemblyResolver[] resolvers) + { + _resolvers = resolvers; + } + + public bool TryResolveAssemblyPaths(CompilationLibrary library, List assemblies) + { + foreach (var resolver in _resolvers) + { + if (resolver.TryResolveAssemblyPaths(library, assemblies)) + { + return true; + } + } + return false;; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Core/DependencyContext/Resolution/DotNetReferenceAssembliesPathResolver.cs b/src/Microsoft.AspNetCore.Mvc.Core/DependencyContext/Resolution/DotNetReferenceAssembliesPathResolver.cs new file mode 100644 index 0000000000..f04bd2a9db --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/DependencyContext/Resolution/DotNetReferenceAssembliesPathResolver.cs @@ -0,0 +1,58 @@ +// 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.IO; +using Microsoft.Extensions.PlatformAbstractions; + +namespace Microsoft.Extensions.DependencyModel.Resolution +{ + internal class DotNetReferenceAssembliesPathResolver + { + public static readonly string DotNetReferenceAssembliesPathEnv = "DOTNET_REFERENCE_ASSEMBLIES_PATH"; + + internal static string Resolve(IRuntimeEnvironment runtimeEnvironment) + { + var path = Environment.GetEnvironmentVariable(DotNetReferenceAssembliesPathEnv); + if (!string.IsNullOrEmpty(path)) + { + return path; + } + + return GetDefaultDotNetReferenceAssembliesPath(runtimeEnvironment); + } + + public static string Resolve() + { + return Resolve(PlatformServices.Default.Runtime); + } + + private static string GetDefaultDotNetReferenceAssembliesPath(IRuntimeEnvironment runtimeEnvironment) + { + var os = runtimeEnvironment.OperatingSystemPlatform; + + if (os == Platform.Windows) + { + return null; + } + + if (os == Platform.Darwin && + Directory.Exists("/Library/Frameworks/Mono.framework/Versions/Current/lib/mono/xbuild-frameworks")) + { + return "/Library/Frameworks/Mono.framework/Versions/Current/lib/mono/xbuild-frameworks"; + } + + if (Directory.Exists("/usr/local/lib/mono/xbuild-frameworks")) + { + return "/usr/local/lib/mono/xbuild-frameworks"; + } + + if (Directory.Exists("/usr/lib/mono/xbuild-frameworks")) + { + return "/usr/lib/mono/xbuild-frameworks"; + } + + return null; + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Core/DependencyContext/Resolution/ICompilationAssemblyResolver.cs b/src/Microsoft.AspNetCore.Mvc.Core/DependencyContext/Resolution/ICompilationAssemblyResolver.cs new file mode 100644 index 0000000000..7358aa581c --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/DependencyContext/Resolution/ICompilationAssemblyResolver.cs @@ -0,0 +1,12 @@ +// 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.Resolution +{ + internal interface ICompilationAssemblyResolver + { + bool TryResolveAssemblyPaths(CompilationLibrary library, List assemblies); + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Core/DependencyContext/Resolution/PackageCacheCompilationAssemblyResolver.cs b/src/Microsoft.AspNetCore.Mvc.Core/DependencyContext/Resolution/PackageCacheCompilationAssemblyResolver.cs new file mode 100644 index 0000000000..83e6b8b2ac --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/DependencyContext/Resolution/PackageCacheCompilationAssemblyResolver.cs @@ -0,0 +1,51 @@ +// 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; + +namespace Microsoft.Extensions.DependencyModel.Resolution +{ + internal class PackageCacheCompilationAssemblyResolver: ICompilationAssemblyResolver + { + private readonly string _packageCacheDirectory; + + internal PackageCacheCompilationAssemblyResolver() + { + _packageCacheDirectory = Environment.GetEnvironmentVariable("DOTNET_PACKAGES_CACHE"); + } + + public bool TryResolveAssemblyPaths(CompilationLibrary library, List assemblies) + { + if (!string.Equals(library.Type, "package", StringComparison.OrdinalIgnoreCase)) + { + return false; + } + + if (!string.IsNullOrEmpty(_packageCacheDirectory)) + { + var hashSplitterPos = library.Hash.IndexOf('-'); + if (hashSplitterPos <= 0 || hashSplitterPos == library.Hash.Length - 1) + { + throw new InvalidOperationException($"Invalid hash entry '{library.Hash}' for package '{library.Name}'"); + } + + string packagePath; + if (ResolverUtils.TryResolvePackagePath(library, _packageCacheDirectory, out packagePath)) + { + var hashAlgorithm = library.Hash.Substring(0, hashSplitterPos); + var cacheHashPath = Path.Combine(packagePath, $"{library.Name}.{library.Version}.nupkg.{hashAlgorithm}"); + + if (File.Exists(cacheHashPath) && + File.ReadAllText(cacheHashPath) == library.Hash.Substring(hashSplitterPos + 1)) + { + assemblies.AddRange(ResolverUtils.ResolveFromPackagePath(library, packagePath)); + return true; + } + } + } + return false; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Core/DependencyContext/Resolution/PackageCompilationAssemblyResolver.cs b/src/Microsoft.AspNetCore.Mvc.Core/DependencyContext/Resolution/PackageCompilationAssemblyResolver.cs new file mode 100644 index 0000000000..3e2eb7501b --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/DependencyContext/Resolution/PackageCompilationAssemblyResolver.cs @@ -0,0 +1,63 @@ +// 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 Microsoft.Extensions.PlatformAbstractions; + +namespace Microsoft.Extensions.DependencyModel.Resolution +{ + internal class PackageCompilationAssemblyResolver: ICompilationAssemblyResolver + { + private readonly string _nugetPackageDirectory; + + internal PackageCompilationAssemblyResolver() + { + _nugetPackageDirectory = GetDefaultPackageDirectory(PlatformServices.Default.Runtime); + } + + internal static string GetDefaultPackageDirectory(IRuntimeEnvironment runtimeEnvironment) + { + var packageDirectory = Environment.GetEnvironmentVariable("NUGET_PACKAGES"); + + if (!string.IsNullOrEmpty(packageDirectory)) + { + return packageDirectory; + } + + string basePath; + if (runtimeEnvironment.OperatingSystemPlatform == Platform.Windows) + { + basePath = Environment.GetEnvironmentVariable("USERPROFILE"); + } + else + { + basePath = Environment.GetEnvironmentVariable("HOME"); + } + if (string.IsNullOrEmpty(basePath)) + { + return null; + } + return Path.Combine(basePath, ".nuget", "packages"); + } + + public bool TryResolveAssemblyPaths(CompilationLibrary library, List assemblies) + { + if (string.IsNullOrEmpty(_nugetPackageDirectory) || + !string.Equals(library.Type, "package", StringComparison.OrdinalIgnoreCase)) + { + return false; + } + + string packagePath; + + if (ResolverUtils.TryResolvePackagePath(library, _nugetPackageDirectory, out packagePath)) + { + assemblies.AddRange(ResolverUtils.ResolveFromPackagePath(library, packagePath)); + return true; + } + return false; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Core/DependencyContext/Resolution/ReferenceAssemblyPathResolver.cs b/src/Microsoft.AspNetCore.Mvc.Core/DependencyContext/Resolution/ReferenceAssemblyPathResolver.cs new file mode 100644 index 0000000000..27b8f4b8cc --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/DependencyContext/Resolution/ReferenceAssemblyPathResolver.cs @@ -0,0 +1,121 @@ +// 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 Microsoft.Extensions.PlatformAbstractions; + +namespace Microsoft.Extensions.DependencyModel.Resolution +{ + internal class ReferenceAssemblyPathResolver: ICompilationAssemblyResolver + { + private readonly string _defaultReferenceAssembliesPath; + private readonly string[] _fallbackSearchPaths; + + internal ReferenceAssemblyPathResolver() + { + _defaultReferenceAssembliesPath = GetDefaultReferenceAssembliesPath(PlatformServices.Default.Runtime); + _fallbackSearchPaths = GetFallbackSearchPaths(PlatformServices.Default.Runtime); + } + + public bool TryResolveAssemblyPaths(CompilationLibrary library, List assemblies) + { + if (!string.Equals(library.Type, "referenceassembly", StringComparison.OrdinalIgnoreCase)) + { + return false; + } + foreach (var assembly in library.Assemblies) + { + string fullName; + if (!TryResolveReferenceAssembly(assembly, out fullName)) + { + throw new InvalidOperationException($"Can not find reference assembly '{assembly}' file for package {library.Name}"); + } + assemblies.Add(fullName); + } + return true; + } + + private bool TryResolveReferenceAssembly(string path, out string fullPath) + { + fullPath = null; + + if (_defaultReferenceAssembliesPath != null) + { + var relativeToReferenceAssemblies = Path.Combine(_defaultReferenceAssembliesPath, path); + if (File.Exists(relativeToReferenceAssemblies)) + { + fullPath = relativeToReferenceAssemblies; + return true; + } + } + + var name = Path.GetFileName(path); + foreach (var fallbackPath in _fallbackSearchPaths) + { + var fallbackFile = Path.Combine(fallbackPath, name); + if (File.Exists(fallbackFile)) + { + fullPath = fallbackFile; + return true; + } + } + + return false; + } + + internal static string[] GetFallbackSearchPaths(IRuntimeEnvironment runtimeEnvironment) + { + if (runtimeEnvironment.OperatingSystemPlatform != Platform.Windows) + { + return new string[0]; + } + + var net20Dir = Path.Combine(Environment.GetEnvironmentVariable("WINDIR"), "Microsoft.NET", "Framework", "v2.0.50727"); + + if (!Directory.Exists(net20Dir)) + { + return new string[0]; + } + return new[] { net20Dir }; + } + + internal static string GetDefaultReferenceAssembliesPath(IRuntimeEnvironment runtimeEnvironment) + { + // Allow setting the reference assemblies path via an environment variable + var referenceAssembliesPath = DotNetReferenceAssembliesPathResolver.Resolve(runtimeEnvironment); + if (!string.IsNullOrEmpty(referenceAssembliesPath)) + { + return referenceAssembliesPath; + } + + if (runtimeEnvironment.OperatingSystemPlatform != Platform.Windows) + { + // There is no reference assemblies path outside of windows + // The environment variable can be used to specify one + return null; + } + + // References assemblies are in %ProgramFiles(x86)% on + // 64 bit machines + var programFiles = Environment.GetEnvironmentVariable("ProgramFiles(x86)"); + + if (string.IsNullOrEmpty(programFiles)) + { + // On 32 bit machines they are in %ProgramFiles% + programFiles = Environment.GetEnvironmentVariable("ProgramFiles"); + } + + if (string.IsNullOrEmpty(programFiles)) + { + // Reference assemblies aren't installed + return null; + } + + return Path.Combine( + programFiles, + "Reference Assemblies", "Microsoft", "Framework"); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Core/DependencyContext/Resolution/ResolverUtils.cs b/src/Microsoft.AspNetCore.Mvc.Core/DependencyContext/Resolution/ResolverUtils.cs new file mode 100644 index 0000000000..343689617c --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/DependencyContext/Resolution/ResolverUtils.cs @@ -0,0 +1,45 @@ +// 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; + +namespace Microsoft.Extensions.DependencyModel.Resolution +{ + internal static class ResolverUtils + { + internal static bool TryResolvePackagePath(CompilationLibrary library, string basePath, out string packagePath) + { + packagePath = Path.Combine(basePath, library.Name, library.Version); + if (Directory.Exists(packagePath)) + { + return true; + } + return false; + } + + internal static IEnumerable ResolveFromPackagePath(CompilationLibrary library, string basePath) + { + foreach (var assembly in library.Assemblies) + { + string fullName; + if (!TryResolveAssemblyFile(basePath, assembly, out fullName)) + { + throw new InvalidOperationException($"Can not find assembly file for package {library.Name} at '{fullName}'"); + } + yield return fullName; + } + } + + internal static bool TryResolveAssemblyFile(string basePath, string assemblyPath, out string fullName) + { + fullName = Path.Combine(basePath, assemblyPath); + if (File.Exists(fullName)) + { + return true; + } + return false; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Core/DependencyContext/ResourceAssembly.cs b/src/Microsoft.AspNetCore.Mvc.Core/DependencyContext/ResourceAssembly.cs new file mode 100644 index 0000000000..c472e18a39 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/DependencyContext/ResourceAssembly.cs @@ -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. + +namespace Microsoft.Extensions.DependencyModel +{ + internal class ResourceAssembly + { + public ResourceAssembly(string path, string locale) + { + Locale = locale; + Path = path; + } + + public string Locale { get; set; } + + public string Path { get; set; } + + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Core/DependencyContext/RuntimeAssembly.cs b/src/Microsoft.AspNetCore.Mvc.Core/DependencyContext/RuntimeAssembly.cs new file mode 100644 index 0000000000..98aa116ca7 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/DependencyContext/RuntimeAssembly.cs @@ -0,0 +1,41 @@ +// 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.Diagnostics; +using System.IO; +using System.Reflection; + +namespace Microsoft.Extensions.DependencyModel +{ + internal class RuntimeAssembly + { + private const string NativeImageSufix = ".ni"; + private readonly string _assemblyName; + + public RuntimeAssembly(string assemblyName, string path) + { + _assemblyName = assemblyName; + Path = path; + } + + public AssemblyName Name => new AssemblyName(_assemblyName); + + public string Path { get; } + + public static RuntimeAssembly Create(string path) + { + var assemblyName = System.IO.Path.GetFileNameWithoutExtension(path); + if (assemblyName == null) + { + throw new ArgumentException($"Provided path has empty file name '{path}'", nameof(path)); + } + + if (assemblyName.EndsWith(NativeImageSufix)) + { + assemblyName = assemblyName.Substring(0, assemblyName.Length - NativeImageSufix.Length); + } + return new RuntimeAssembly(assemblyName, path); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Core/DependencyContext/RuntimeFallbacks.cs b/src/Microsoft.AspNetCore.Mvc.Core/DependencyContext/RuntimeFallbacks.cs new file mode 100644 index 0000000000..915b2ba3e5 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/DependencyContext/RuntimeFallbacks.cs @@ -0,0 +1,29 @@ +// 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.Linq; + +namespace Microsoft.Extensions.DependencyModel +{ + internal class RuntimeFallbacks + { + public string Runtime { get; set; } + public IReadOnlyList Fallbacks { get; set; } + + public RuntimeFallbacks(string runtime, IEnumerable fallbacks) + { + if (runtime == null) + { + throw new ArgumentNullException(nameof(runtime)); + } + if (fallbacks == null) + { + throw new ArgumentNullException(nameof(fallbacks)); + } + Runtime = runtime; + Fallbacks = fallbacks.ToArray(); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Core/DependencyContext/RuntimeLibrary.cs b/src/Microsoft.AspNetCore.Mvc.Core/DependencyContext/RuntimeLibrary.cs new file mode 100644 index 0000000000..e50e587d0b --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/DependencyContext/RuntimeLibrary.cs @@ -0,0 +1,38 @@ +// 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; +using System.Linq; + +namespace Microsoft.Extensions.DependencyModel +{ + internal class RuntimeLibrary : Library + { + public RuntimeLibrary( + string type, + string name, + string version, + string hash, + IEnumerable assemblies, + IEnumerable nativeLibraries, + IEnumerable resourceAssemblies, + IEnumerable subTargets, + IEnumerable dependencies, + bool serviceable) + : base(type, name, version, hash, dependencies, serviceable) + { + Assemblies = assemblies.ToArray(); + ResourceAssemblies = resourceAssemblies.ToArray(); + RuntimeTargets = subTargets.ToArray(); + NativeLibraries = nativeLibraries.ToArray(); + } + + public IReadOnlyList Assemblies { get; } + + public IReadOnlyList NativeLibraries { get; } + + public IReadOnlyList ResourceAssemblies { get; } + + public IReadOnlyList RuntimeTargets { get; } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Core/DependencyContext/RuntimeTarget.cs b/src/Microsoft.AspNetCore.Mvc.Core/DependencyContext/RuntimeTarget.cs new file mode 100644 index 0000000000..cdedd19c27 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/DependencyContext/RuntimeTarget.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; +using System.Linq; + +namespace Microsoft.Extensions.DependencyModel +{ + internal class RuntimeTarget + { + public RuntimeTarget(string runtime, IEnumerable assemblies, IEnumerable nativeLibraries) + { + Runtime = runtime; + Assemblies = assemblies.ToArray(); + NativeLibraries = nativeLibraries.ToArray(); + } + + public string Runtime { get; } + + public IReadOnlyList Assemblies { get; } + + public IReadOnlyList NativeLibraries { get; } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/DefaultAssemblyProvider.cs b/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/DefaultAssemblyProvider.cs index ee79a57f30..4380e76f31 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/DefaultAssemblyProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/DefaultAssemblyProvider.cs @@ -88,7 +88,7 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure /// /// A set of . // Internal for unit testing - protected internal virtual IEnumerable GetCandidateLibraries() + internal virtual IEnumerable GetCandidateLibraries() { if (ReferenceAssemblies == null) { diff --git a/src/Microsoft.AspNetCore.Mvc.Core/project.json b/src/Microsoft.AspNetCore.Mvc.Core/project.json index cdd40bf0d6..1f58114cf3 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/project.json +++ b/src/Microsoft.AspNetCore.Mvc.Core/project.json @@ -24,7 +24,6 @@ "version": "1.0.0-*" }, "Microsoft.Extensions.FileProviders.Abstractions": "1.0.0-*", - "Microsoft.Extensions.DependencyModel": "1.0.0-*", "Microsoft.Extensions.ClosedGenericMatcher.Sources": { "version": "1.0.0-*", "type": "build" @@ -34,6 +33,7 @@ "type": "build", "version": "1.0.0-*" }, + "Microsoft.Extensions.PlatformAbstractions": "1.0.0-*", "Microsoft.Extensions.PropertyActivator.Sources": { "version": "1.0.0-*", "type": "build" @@ -46,6 +46,7 @@ "version": "1.0.0-*", "type": "build" }, + "Newtonsoft.Json": "8.0.3", "System.Buffers": "4.0.0-*", "System.Diagnostics.DiagnosticSource": "4.0.0-*" }, diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DependencyContextRazorViewEngineOptionsSetup.cs b/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DependencyContextRazorViewEngineOptionsSetup.cs index e7f4bc5960..b86b2ac346 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DependencyContextRazorViewEngineOptionsSetup.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DependencyContextRazorViewEngineOptionsSetup.cs @@ -80,7 +80,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal { var parseOptions = options.ParseOptions; parseOptions = parseOptions.WithPreprocessorSymbols( - parseOptions.PreprocessorSymbolNames.Concat(compilationOptions.Defines)); + parseOptions.PreprocessorSymbolNames.Concat(compilationOptions.Defines ?? Enumerable.Empty())); LanguageVersion languageVersion; if (!string.IsNullOrEmpty(compilationOptions.LanguageVersion) && diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/project.json b/src/Microsoft.AspNetCore.Mvc.Razor/project.json index 7bc6f72ef2..0666195b9c 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor/project.json +++ b/src/Microsoft.AspNetCore.Mvc.Razor/project.json @@ -13,6 +13,7 @@ ], "xmlDoc": true }, + "compile": "../Microsoft.AspNetCore.Mvc.Core/DependencyContext/**/*.cs", "dependencies": { "Microsoft.AspNetCore.Mvc.Razor.Host": "1.0.0-*", "Microsoft.AspNetCore.Mvc.ViewFeatures": "1.0.0-*", @@ -44,7 +45,7 @@ "netstandard1.5": { "imports": [ "dotnet5.6", - "portable-net45+win8" + "portable-net451+win8" ], "dependencies": { "System.Text.Encoding": "4.0.11-*", diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/DefaultAssemblyProviderTests.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/DefaultAssemblyProviderTests.cs deleted file mode 100644 index eaf7fc37de..0000000000 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/DefaultAssemblyProviderTests.cs +++ /dev/null @@ -1,220 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyModel; -using Xunit; - -namespace Microsoft.AspNetCore.Mvc.Infrastructure -{ - public class DefaultAssemblyProviderTests - { - private static readonly Assembly CurrentAssembly = typeof(DefaultAssemblyProviderTests).GetTypeInfo().Assembly; - - [Fact] - public void GetCandidateLibraries_IgnoresMvcAssemblies() - { - // Arrange - var expected = GetLibrary("SomeRandomAssembly", "Microsoft.AspNetCore.Mvc.Abstractions"); - var dependencyContext = new DependencyContext( - target: null, - runtime: null, - compilationOptions: CompilationOptions.Default, - compileLibraries: new CompilationLibrary[0], - runtimeLibraries: new[] - { - GetLibrary("Microsoft.AspNetCore.Mvc.Core"), - GetLibrary("Microsoft.AspNetCore.Mvc"), - GetLibrary("Microsoft.AspNetCore.Mvc.Abstractions"), - expected, - }); - var provider = new DefaultAssemblyProvider(CurrentAssembly, dependencyContext); - - // Act - var candidates = provider.GetCandidateLibraries(); - - // Assert - Assert.Equal(new[] { expected }, candidates); - } - - [Fact] - public void CandidateAssemblies_ReturnsEntryAssemblyIfDependencyContextIsNull() - { - // Arrange - var provider = new DefaultAssemblyProvider(CurrentAssembly, dependencyContext: null); - - // Act - var candidates = provider.CandidateAssemblies; - - // Assert - Assert.Equal(new[] { CurrentAssembly }, candidates); - } - - [Fact] - public void GetCandidateLibraries_ReturnsLibrariesReferencingAnyMvcAssembly() - { - // Arrange - var dependencyContext = new DependencyContext( - target: null, - runtime: null, - compilationOptions: CompilationOptions.Default, - compileLibraries: new CompilationLibrary[0], - runtimeLibraries: new[] - { - GetLibrary("Foo", "Microsoft.AspNetCore.Mvc.Core"), - GetLibrary("Bar", "Microsoft.AspNetCore.Mvc"), - GetLibrary("Qux", "Not.Mvc.Assembly", "Unofficial.Microsoft.AspNetCore.Mvc"), - GetLibrary("Baz", "Microsoft.AspNetCore.Mvc.Abstractions"), - }); - var provider = new DefaultAssemblyProvider(CurrentAssembly, dependencyContext); - - // Act - var candidates = provider.GetCandidateLibraries(); - - // Assert - Assert.Equal(new[] { "Foo", "Bar", "Baz" }, candidates.Select(a => a.PackageName)); - } - - [Fact] - public void GetCandidateLibraries_ReturnsLibrariesReferencingOverriddenAssemblies() - { - // Arrange - var dependencyContext = new DependencyContext( - target: null, - runtime: null, - compilationOptions: CompilationOptions.Default, - compileLibraries: new CompilationLibrary[0], - runtimeLibraries: new[] - { - GetLibrary("Foo", "CustomMvc.Modules"), - GetLibrary("Bar", "CustomMvc.Application.Loader"), - GetLibrary("Baz", "Microsoft.AspNetCore.Mvc.Abstractions"), - }); - var referenceAssemblies = new HashSet - { - "CustomMvc.Modules", - "CustomMvc.Application.Loader" - }; - var assemblyProvider = new OverridenAssemblyProvider( - CurrentAssembly, - dependencyContext, - referenceAssemblies); - - // Act - var candidates = assemblyProvider.GetCandidateLibraries(); - - // Assert - Assert.Equal(new[] { "Foo", "Bar" }, candidates.Select(a => a.PackageName)); - } - - [Fact] - public void GetCandidateLibraries_ReturnsEmptySequenceWhenReferenceAssembliesIsNull() - { - // Arrange - var dependencyContext = new DependencyContext( - target: null, - runtime: null, - compilationOptions: CompilationOptions.Default, - compileLibraries: new CompilationLibrary[0], - runtimeLibraries: new[] - { - GetLibrary("Foo", "CustomMvc.Modules"), - GetLibrary("Bar", "CustomMvc.Application.Loader"), - GetLibrary("Baz", "Microsoft.AspNetCore.Mvc.Abstractions"), - }); - var assemblyProvider = new OverridenAssemblyProvider( - CurrentAssembly, - dependencyContext, - referenceAssemblies: null); - - // Act - var candidates = assemblyProvider.GetCandidateLibraries(); - - // Assert - Assert.Empty(candidates); - } - - // This test verifies DefaultAssemblyProvider.ReferenceAssemblies reflects the actual loadable assemblies - // of the libraries that Microsoft.AspNetCore.Mvc dependes on. - // If we add or remove dependencies, this test should be changed together. - [Fact] - public void ReferenceAssemblies_ReturnsLoadableReferenceAssemblies() - { - // Arrange - var provider = new TestableAssemblyProvider(CurrentAssembly, dependencyContext: null); - var excludeAssemblies = new string[] - { - "Microsoft.AspNetCore.Mvc.WebApiCompatShim", - "Microsoft.AspNetCore.Mvc.TestCommon", - "Microsoft.AspNetCore.Mvc.Core.Test", - "Microsoft.AspNetCore.Mvc.TestDiagnosticListener.Sources", - }; - - var additionalAssemblies = new[] - { - // The following assemblies are not reachable from Microsoft.AspNetCore.Mvc - "Microsoft.AspNetCore.Mvc.TagHelpers", - "Microsoft.AspNetCore.Mvc.Formatters.Xml", - }; - - var expected = DependencyContext.Load(CurrentAssembly) - .RuntimeLibraries - .Where(r => r.PackageName.StartsWith("Microsoft.AspNetCore.Mvc", StringComparison.Ordinal) && - !excludeAssemblies.Contains(r.PackageName, StringComparer.OrdinalIgnoreCase)) - .Select(r => r.PackageName) - .Concat(additionalAssemblies) - .Distinct() - .OrderBy(p => p, StringComparer.Ordinal); - - // Act - var referenceAssemblies = provider.ReferenceAssemblies.OrderBy(p => p, StringComparer.Ordinal); - - // Assert - Assert.Equal(expected, referenceAssemblies); - } - - private static RuntimeLibrary GetLibrary(string name, params string[] dependencyNames) - { - var dependencies = dependencyNames?.Select(d => new Dependency(d, "42.0.0")) ?? new Dependency[0]; - - return new RuntimeLibrary( - "package", - name, - "23.0.0", - "hash", - new[] { $"{name}.dll" }, - dependencies: dependencies.ToArray(), - serviceable: true); - } - - private class OverridenAssemblyProvider : DefaultAssemblyProvider - { - public OverridenAssemblyProvider( - Assembly entryAssembly, - DependencyContext dependencyContext, - HashSet referenceAssemblies) - : base(entryAssembly, dependencyContext) - { - ReferenceAssemblies = referenceAssemblies; - } - - protected override HashSet ReferenceAssemblies { get; } - } - - private class TestableAssemblyProvider : DefaultAssemblyProvider - { - public TestableAssemblyProvider( - Assembly entryAssembly, - DependencyContext dependencyContext) - : base(entryAssembly, dependencyContext) - { - } - - public new HashSet ReferenceAssemblies => base.ReferenceAssemblies; - } - } -} \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/DefaultRoslynCompilationServiceTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/DefaultRoslynCompilationServiceTest.cs deleted file mode 100644 index ef2a00ead3..0000000000 --- a/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/DefaultRoslynCompilationServiceTest.cs +++ /dev/null @@ -1,425 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Reflection; -using Microsoft.AspNetCore.Mvc.Razor.Compilation; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Text; -using Microsoft.Extensions.DependencyModel; -using Microsoft.Extensions.FileProviders; -using Microsoft.Extensions.Logging.Testing; -using Moq; -using Xunit; - -namespace Microsoft.AspNetCore.Mvc.Razor.Internal -{ - public class DefaultRoslynCompilationServiceTest - { - [Fact] - public void Compile_ReturnsCompilationResult() - { - // Arrange - var content = @" -public class MyTestType {}"; - - var compilationService = new DefaultRoslynCompilationService( - GetDependencyContext(), - GetOptions(), - GetFileProviderAccessor(), - NullLoggerFactory.Instance); - var relativeFileInfo = new RelativeFileInfo( - new TestFileInfo { PhysicalPath = "SomePath" }, - "some-relative-path"); - - // Act - var result = compilationService.Compile(relativeFileInfo, content); - - // Assert - Assert.Equal("MyTestType", result.CompiledType.Name); - } - - [Fact] - public void Compile_ReturnsCompilationFailureWithPathsFromLinePragmas() - { - // Arrange - var viewPath = "some-relative-path"; - var fileContent = "test file content"; - var content = $@" -#line 1 ""{viewPath}"" -this should fail"; - var fileProvider = new TestFileProvider(); - var fileInfo = fileProvider.AddFile(viewPath, fileContent); - - var compilationService = new DefaultRoslynCompilationService( - GetDependencyContext(), - GetOptions(), - GetFileProviderAccessor(fileProvider), - NullLoggerFactory.Instance); - var relativeFileInfo = new RelativeFileInfo(fileInfo, "some-relative-path"); - - // Act - var result = compilationService.Compile(relativeFileInfo, content); - - // Assert - Assert.IsType(result); - Assert.Null(result.CompiledType); - var compilationFailure = Assert.Single(result.CompilationFailures); - Assert.Equal(relativeFileInfo.RelativePath, compilationFailure.SourceFilePath); - Assert.Equal(fileContent, compilationFailure.SourceFileContent); - } - - [Fact] - public void Compile_ReturnsGeneratedCodePath_IfLinePragmaIsNotAvailable() - { - // Arrange - var fileContent = "file content"; - var content = @"this should fail"; - - var compilationService = new DefaultRoslynCompilationService( - GetDependencyContext(), - GetOptions(), - GetFileProviderAccessor(), - NullLoggerFactory.Instance); - var relativeFileInfo = new RelativeFileInfo( - new TestFileInfo { Content = fileContent }, - "some-relative-path"); - - // Act - var result = compilationService.Compile(relativeFileInfo, content); - - // Assert - Assert.IsType(result); - Assert.Null(result.CompiledType); - - var compilationFailure = Assert.Single(result.CompilationFailures); - Assert.Equal("Generated Code", compilationFailure.SourceFilePath); - Assert.Equal(content, compilationFailure.SourceFileContent); - } - - [Fact] - public void Compile_DoesNotThrow_IfFileCannotBeRead() - { - // Arrange - var path = "some-relative-path"; - var content = $@" -#line 1 ""{path}"" -this should fail"; - - var mockFileInfo = new Mock(); - mockFileInfo.Setup(f => f.CreateReadStream()) - .Throws(new Exception()); - var fileProvider = new TestFileProvider(); - fileProvider.AddFile(path, mockFileInfo.Object); - - var compilationService = new DefaultRoslynCompilationService( - GetDependencyContext(), - GetOptions(), - GetFileProviderAccessor(), - NullLoggerFactory.Instance); - var relativeFileInfo = new RelativeFileInfo(mockFileInfo.Object, path); - - // Act - var result = compilationService.Compile(relativeFileInfo, content); - - // Assert - Assert.IsType(result); - Assert.Null(result.CompiledType); - var compilationFailure = Assert.Single(result.CompilationFailures); - Assert.Equal(path, compilationFailure.SourceFilePath); - Assert.Null(compilationFailure.SourceFileContent); - } - - [Fact] - public void Compile_UsesApplicationsCompilationSettings_ForParsingAndCompilation() - { - // Arrange - var content = @" -#if MY_CUSTOM_DEFINE -public class MyCustomDefinedClass {} -#else -public class MyNonCustomDefinedClass {} -#endif -"; - - var options = GetOptions(); - options.ParseOptions = options.ParseOptions.WithPreprocessorSymbols("MY_CUSTOM_DEFINE"); - - var compilationService = new DefaultRoslynCompilationService( - GetDependencyContext(), - options, - GetFileProviderAccessor(), - NullLoggerFactory.Instance); - var relativeFileInfo = new RelativeFileInfo( - new TestFileInfo { PhysicalPath = "SomePath" }, - "some-relative-path"); - - // Act - var result = compilationService.Compile(relativeFileInfo, content); - - // Assert - Assert.NotNull(result.CompiledType); - Assert.Equal("MyCustomDefinedClass", result.CompiledType.Name); - } - - [Fact] - public void GetCompilationFailedResult_ReturnsCompilationResult_WithGroupedMessages() - { - // Arrange - var viewPath = "Views/Home/Index"; - var generatedCodeFileName = "Generated Code"; - var fileProvider = new TestFileProvider(); - fileProvider.AddFile(viewPath, "view-content"); - var options = new RazorViewEngineOptions(); - options.FileProviders.Add(fileProvider); - - var compilationService = new DefaultRoslynCompilationService( - GetDependencyContext(), - options, - GetFileProviderAccessor(fileProvider), - NullLoggerFactory.Instance); - - var assemblyName = "random-assembly-name"; - - var diagnostics = new[] - { - Diagnostic.Create( - GetDiagnosticDescriptor("message-1"), - Location.Create( - viewPath, - new TextSpan(10, 5), - new LinePositionSpan(new LinePosition(10, 1), new LinePosition(10, 2)))), - Diagnostic.Create( - GetDiagnosticDescriptor("message-2"), - Location.Create( - assemblyName, - new TextSpan(1, 6), - new LinePositionSpan(new LinePosition(1, 2), new LinePosition(3, 4)))), - Diagnostic.Create( - GetDiagnosticDescriptor("message-3"), - Location.Create( - viewPath, - new TextSpan(40, 50), - new LinePositionSpan(new LinePosition(30, 5), new LinePosition(40, 12)))), - }; - - // Act - var compilationResult = compilationService.GetCompilationFailedResult( - viewPath, - "compilation-content", - assemblyName, - diagnostics); - - // Assert - Assert.Collection(compilationResult.CompilationFailures, - failure => - { - Assert.Equal(viewPath, failure.SourceFilePath); - Assert.Equal("view-content", failure.SourceFileContent); - Assert.Collection(failure.Messages, - message => - { - Assert.Equal("message-1", message.Message); - Assert.Equal(viewPath, message.SourceFilePath); - Assert.Equal(11, message.StartLine); - Assert.Equal(2, message.StartColumn); - Assert.Equal(11, message.EndLine); - Assert.Equal(3, message.EndColumn); - }, - message => - { - Assert.Equal("message-3", message.Message); - Assert.Equal(viewPath, message.SourceFilePath); - Assert.Equal(31, message.StartLine); - Assert.Equal(6, message.StartColumn); - Assert.Equal(41, message.EndLine); - Assert.Equal(13, message.EndColumn); - }); - }, - failure => - { - Assert.Equal(generatedCodeFileName, failure.SourceFilePath); - Assert.Equal("compilation-content", failure.SourceFileContent); - Assert.Collection(failure.Messages, - message => - { - Assert.Equal("message-2", message.Message); - Assert.Equal(assemblyName, message.SourceFilePath); - Assert.Equal(2, message.StartLine); - Assert.Equal(3, message.StartColumn); - Assert.Equal(4, message.EndLine); - Assert.Equal(5, message.EndColumn); - }); - }); - } - - [Fact] - public void Compile_RunsCallback() - { - // Arrange - var content = "public class MyTestType {}"; - RoslynCompilationContext usedCompilation = null; - - var compilationService = new DefaultRoslynCompilationService( - GetDependencyContext(), - GetOptions(callback: c => usedCompilation = c), - GetFileProviderAccessor(), - NullLoggerFactory.Instance); - - var relativeFileInfo = new RelativeFileInfo( - new TestFileInfo { PhysicalPath = "SomePath" }, - "some-relative-path"); - - // Act - var result = compilationService.Compile(relativeFileInfo, content); - - Assert.NotNull(usedCompilation); - Assert.Single(usedCompilation.Compilation.SyntaxTrees); - } - - [Fact] - public void Compile_ThrowsIfDependencyContextIsNullAndTheApplicationFailsToCompileWithNoReferences() - { - // Arrange - var content = "public class MyTestType {}"; - var compilationService = new DefaultRoslynCompilationService( - dependencyContext: null, - viewEngineOptions: GetOptions(), - fileProviderAccessor: GetFileProviderAccessor(), - loggerFactory: NullLoggerFactory.Instance); - - var relativeFileInfo = new RelativeFileInfo( - new TestFileInfo { PhysicalPath = "SomePath" }, - "some-relative-path.cshtml"); - - var expected = "The Razor page 'some-relative-path.cshtml' failed to compile. Ensure that your " - + "application's project.json sets the 'preserveCompilationContext' compilation property."; - - // Act and Assert - var ex = Assert.Throws(() => - compilationService.Compile(relativeFileInfo, content)); - Assert.Equal(expected, ex.Message); - } - - [Fact] - public void Compile_ThrowsIfDependencyContextReturnsNoReferencesAndTheApplicationFailsToCompile() - { - // Arrange - var content = "public class MyTestType {}"; - var dependencyContext = new DependencyContext( - target: null, - runtime: null, - compilationOptions: Extensions.DependencyModel.CompilationOptions.Default, - compileLibraries: new CompilationLibrary[0], - runtimeLibraries: new RuntimeLibrary[0]); - var compilationService = new DefaultRoslynCompilationService( - dependencyContext: dependencyContext, - viewEngineOptions: GetOptions(), - fileProviderAccessor: GetFileProviderAccessor(), - loggerFactory: NullLoggerFactory.Instance); - - var relativeFileInfo = new RelativeFileInfo( - new TestFileInfo { PhysicalPath = "SomePath" }, - "some-relative-path.cshtml"); - - var expected = "The Razor page 'some-relative-path.cshtml' failed to compile. Ensure that your " - + "application's project.json sets the 'preserveCompilationContext' compilation property."; - - // Act and Assert - var ex = Assert.Throws(() => - compilationService.Compile(relativeFileInfo, content)); - Assert.Equal(expected, ex.Message); - } - - [Fact] - public void Compile_DoesNotThrowIfReferencesWereClearedInCallback() - { - // Arrange - var options = GetOptions(context => - { - context.Compilation = context.Compilation.RemoveAllReferences(); - }); - var content = "public class MyTestType {}"; - var compilationService = new DefaultRoslynCompilationService( - dependencyContext: GetDependencyContext(), - viewEngineOptions: options, - fileProviderAccessor: GetFileProviderAccessor(), - loggerFactory: NullLoggerFactory.Instance); - - var relativeFileInfo = new RelativeFileInfo( - new TestFileInfo { PhysicalPath = "SomePath" }, - "some-relative-path.cshtml"); - - // Act - var result = compilationService.Compile(relativeFileInfo, content); - - // Assert - Assert.Single(result.CompilationFailures); - } - - [Fact] - public void Compile_SucceedsIfReferencesAreAddedInCallback() - { - // Arrange - var options = GetOptions(context => - { - var assemblyLocation = typeof(object).GetTypeInfo().Assembly.Location; - - context.Compilation = context - .Compilation - .AddReferences(MetadataReference.CreateFromFile(assemblyLocation)); - }); - var content = "public class MyTestType {}"; - var compilationService = new DefaultRoslynCompilationService( - dependencyContext: null, - viewEngineOptions: options, - fileProviderAccessor: GetFileProviderAccessor(), - loggerFactory: NullLoggerFactory.Instance); - - var relativeFileInfo = new RelativeFileInfo( - new TestFileInfo { PhysicalPath = "SomePath" }, - "some-relative-path.cshtml"); - - // Act - var result = compilationService.Compile(relativeFileInfo, content); - - // Assert - Assert.Null(result.CompilationFailures); - Assert.NotNull(result.CompiledType); - } - - private static DiagnosticDescriptor GetDiagnosticDescriptor(string messageFormat) - { - return new DiagnosticDescriptor( - id: "someid", - title: "sometitle", - messageFormat: messageFormat, - category: "some-category", - defaultSeverity: DiagnosticSeverity.Error, - isEnabledByDefault: true); - } - - private static RazorViewEngineOptions GetOptions(Action callback = null) - { - return new RazorViewEngineOptions - { - CompilationCallback = callback ?? (c => { }), - }; - } - - private IRazorViewEngineFileProviderAccessor GetFileProviderAccessor(IFileProvider fileProvider = null) - { - var options = new Mock(); - options.SetupGet(o => o.FileProvider) - .Returns(fileProvider ?? new TestFileProvider()); - - return options.Object; - } - - private DependencyContext GetDependencyContext() - { - var assembly = typeof(DefaultRoslynCompilationServiceTest).GetTypeInfo().Assembly; - return DependencyContext.Load(assembly); - } - } -}