Use DependencyContext.CompileLibraries to determine dependency graph (#7626)

* Use DependencyContext.CompileLibraries to determine dependency graph

Fixes https://github.com/aspnet/Mvc/issues/7617
This commit is contained in:
Pranav K 2018-04-11 15:57:11 -07:00 committed by GitHub
parent 9cea47fa7d
commit e34b4e8335
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 307 additions and 149 deletions

View File

@ -51,8 +51,11 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationParts
IEnumerable<AssemblyItem> assemblyItems; IEnumerable<AssemblyItem> assemblyItems;
if (dependencyContext == null) if (dependencyContext == null || dependencyContext.CompileLibraries.Count == 0)
{ {
// If an application was built with PreserveCompilationContext = false, CompileLibraries will be empty and we
// can no longer reliably infer the dependency closure. In this case, treat it the same as a missing
// deps file.
assemblyItems = new[] { GetAssemblyItem(entryAssembly) }; assemblyItems = new[] { GetAssemblyItem(entryAssembly) };
} }
else else
@ -151,30 +154,46 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationParts
// Internal for unit testing // Internal for unit testing
internal static IEnumerable<RuntimeLibrary> GetCandidateLibraries(DependencyContext dependencyContext) internal static IEnumerable<RuntimeLibrary> GetCandidateLibraries(DependencyContext dependencyContext)
{ {
var candidatesResolver = new CandidateResolver(dependencyContext.RuntimeLibraries, ReferenceAssemblies); // When using Microsoft.AspNetCore.App \ Microsoft.AspNetCore.All shared runtimes, entries in the RuntimeLibraries
return candidatesResolver.GetCandidates(); // get "erased" and it is no longer accurate to query to determine a library's dependency closure.
// We'll use CompileLibraries to calculate the dependency graph and runtime library to resolve assemblies to inspect.
var candidatesResolver = new CandidateResolver(dependencyContext.CompileLibraries, ReferenceAssemblies);
foreach (var library in dependencyContext.RuntimeLibraries)
{
if (candidatesResolver.IsCandidate(library))
{
yield return library;
}
}
} }
private class CandidateResolver private class CandidateResolver
{ {
private readonly IDictionary<string, Dependency> _runtimeDependencies; private readonly IDictionary<string, Dependency> _compileLibraries;
public CandidateResolver(IReadOnlyList<RuntimeLibrary> runtimeDependencies, ISet<string> referenceAssemblies) public CandidateResolver(IReadOnlyList<Library> compileLibraries, ISet<string> referenceAssemblies)
{ {
var dependenciesWithNoDuplicates = new Dictionary<string, Dependency>(StringComparer.OrdinalIgnoreCase); var compileLibrariesWithNoDuplicates = new Dictionary<string, Dependency>(StringComparer.OrdinalIgnoreCase);
foreach (var dependency in runtimeDependencies) foreach (var library in compileLibraries)
{ {
if (dependenciesWithNoDuplicates.ContainsKey(dependency.Name)) if (compileLibrariesWithNoDuplicates.ContainsKey(library.Name))
{ {
throw new InvalidOperationException(Resources.FormatCandidateResolver_DifferentCasedReference(dependency.Name)); throw new InvalidOperationException(Resources.FormatCandidateResolver_DifferentCasedReference(library.Name));
} }
dependenciesWithNoDuplicates.Add(dependency.Name, CreateDependency(dependency, referenceAssemblies));
compileLibrariesWithNoDuplicates.Add(library.Name, CreateDependency(library, referenceAssemblies));
} }
_runtimeDependencies = dependenciesWithNoDuplicates; _compileLibraries = compileLibrariesWithNoDuplicates;
} }
private Dependency CreateDependency(RuntimeLibrary library, ISet<string> referenceAssemblies) public bool IsCandidate(Library library)
{
var classification = ComputeClassification(library.Name);
return classification == DependencyClassification.ReferencesMvc;
}
private Dependency CreateDependency(Library library, ISet<string> referenceAssemblies)
{ {
var classification = DependencyClassification.Unknown; var classification = DependencyClassification.Unknown;
if (referenceAssemblies.Contains(library.Name)) if (referenceAssemblies.Contains(library.Name))
@ -187,14 +206,14 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationParts
private DependencyClassification ComputeClassification(string dependency) private DependencyClassification ComputeClassification(string dependency)
{ {
if (!_runtimeDependencies.ContainsKey(dependency)) if (!_compileLibraries.ContainsKey(dependency))
{ {
// Library does not have runtime dependency. Since we can't infer // Library does not have runtime dependency. Since we can't infer
// anything about it's references, we'll assume it does not have a reference to Mvc. // anything about it's references, we'll assume it does not have a reference to Mvc.
return DependencyClassification.DoesNotReferenceMvc; return DependencyClassification.DoesNotReferenceMvc;
} }
var candidateEntry = _runtimeDependencies[dependency]; var candidateEntry = _compileLibraries[dependency];
if (candidateEntry.Classification != DependencyClassification.Unknown) if (candidateEntry.Classification != DependencyClassification.Unknown)
{ {
return candidateEntry.Classification; return candidateEntry.Classification;
@ -219,26 +238,15 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationParts
} }
} }
public IEnumerable<RuntimeLibrary> GetCandidates()
{
foreach (var dependency in _runtimeDependencies)
{
if (ComputeClassification(dependency.Key) == DependencyClassification.ReferencesMvc)
{
yield return dependency.Value.Library;
}
}
}
private class Dependency private class Dependency
{ {
public Dependency(RuntimeLibrary library, DependencyClassification classification) public Dependency(Library library, DependencyClassification classification)
{ {
Library = library; Library = library;
Classification = classification; Classification = classification;
} }
public RuntimeLibrary Library { get; } public Library Library { get; }
public DependencyClassification Classification { get; set; } public DependencyClassification Classification { get; set; }

View File

@ -176,34 +176,35 @@ namespace Microsoft.AspNetCore.Mvc.Testing
{ {
// The default dependency context will be populated in .net core applications. // The default dependency context will be populated in .net core applications.
var context = DependencyContext.Default; var context = DependencyContext.Default;
if (context != null) if (context == null || context.CompileLibraries.Count == 0)
{
// Find the list of projects
var projects = context.CompileLibraries.Where(l => l.Type == "project");
// Find the list of projects runtime information and their assembly names.
var runtimeProjectLibraries = context.RuntimeLibraries
.Where(r => projects.Any(p => p.Name == r.Name))
.ToDictionary(r => r, r => r.GetDefaultAssemblyNames(context).ToArray());
var entryPointAssemblyName = typeof(TEntryPoint).Assembly.GetName().Name;
// Find the project containing TEntryPoint
var entryPointRuntimeLibrary = runtimeProjectLibraries
.Single(rpl => rpl.Value.Any(a => string.Equals(a.Name, entryPointAssemblyName, StringComparison.Ordinal)));
// Find the list of projects referencing TEntryPoint.
var candidates = runtimeProjectLibraries
.Where(rpl => rpl.Key.Dependencies
.Any(d => string.Equals(d.Name, entryPointRuntimeLibrary.Key.Name, StringComparison.Ordinal)));
return candidates.SelectMany(rl => rl.Value).Select(Assembly.Load);
}
else
{ {
// The app domain friendly name will be populated in full framework. // The app domain friendly name will be populated in full framework.
return new[] { Assembly.Load(AppDomain.CurrentDomain.FriendlyName) }; return new[] { Assembly.Load(AppDomain.CurrentDomain.FriendlyName) };
} }
var runtimeProjectLibraries = context.RuntimeLibraries
.ToDictionary(r => r.Name, r => r, StringComparer.Ordinal);
// Find the list of projects
var projects = context.CompileLibraries.Where(l => l.Type == "project");
var entryPointAssemblyName = typeof(TEntryPoint).Assembly.GetName().Name;
// Find the list of projects referencing TEntryPoint.
var candidates = context.CompileLibraries
.Where(library => library.Dependencies.Any(d => string.Equals(d.Name, entryPointAssemblyName, StringComparison.Ordinal)));
var testAssemblies = new List<Assembly>();
foreach (var candidate in candidates)
{
if (runtimeProjectLibraries.TryGetValue(candidate.Name, out var runtimeLibrary))
{
var runtimeAssemblies = runtimeLibrary.GetDefaultAssemblyNames(context);
testAssemblies.AddRange(runtimeAssemblies.Select(Assembly.Load));
}
}
return testAssemblies;
} }
catch (Exception) catch (Exception)
{ {

View File

@ -7,6 +7,7 @@ using System.Linq;
using System.Reflection; using System.Reflection;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyModel; using Microsoft.Extensions.DependencyModel;
using Newtonsoft.Json;
using Xunit; using Xunit;
namespace Microsoft.AspNetCore.Mvc.ApplicationParts namespace Microsoft.AspNetCore.Mvc.ApplicationParts
@ -28,6 +29,30 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationParts
Assert.Equal(new[] { ThisAssembly }, result); Assert.Equal(new[] { ThisAssembly }, result);
} }
[Fact]
public void ResolveAssemblies_ReturnsCurrentAssembly_IfDepsFileDoesNotHaveAnyCompileLibraries()
{
// Arrange
var runtimeLibraries = new[]
{
GetRuntimeLibrary("MyApp", "Microsoft.AspNetCore.All"),
GetRuntimeLibrary("Microsoft.AspNetCore.All", "Microsoft.NETCore.App"),
GetRuntimeLibrary("Microsoft.NETCore.App"),
GetRuntimeLibrary("ClassLibrary"),
};
var dependencyContext = GetDependencyContext(compileLibraries: Array.Empty<CompilationLibrary>(), runtimeLibraries);
var provider = new TestApplicationAssembliesProvider
{
DependencyContext = dependencyContext,
};
// Act
var result = provider.ResolveAssemblies(ThisAssembly);
// Assert
Assert.Equal(new[] { ThisAssembly }, result);
}
[Fact] [Fact]
public void ResolveAssemblies_ReturnsRelatedAssembliesOrderedByName() public void ResolveAssemblies_ReturnsRelatedAssembliesOrderedByName()
{ {
@ -56,12 +81,14 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationParts
var mvcAssembly = typeof(IActionResult).Assembly; var mvcAssembly = typeof(IActionResult).Assembly;
var classLibrary = typeof(FactAttribute).Assembly; var classLibrary = typeof(FactAttribute).Assembly;
var dependencyContext = GetDependencyContext(new[] var libraries = new Dictionary<string, string[]>
{ {
GetLibrary(ThisAssembly.GetName().Name, new[] { mvcAssembly.GetName().Name, classLibrary.GetName().Name }), [ThisAssembly.GetName().Name] = new[] { mvcAssembly.GetName().Name, classLibrary.GetName().Name },
GetLibrary(mvcAssembly.GetName().Name), [mvcAssembly.GetName().Name] = Array.Empty<string>(),
GetLibrary(classLibrary.GetName().Name, new[] { mvcAssembly.GetName().Name }), [classLibrary.GetName().Name] = new[] { mvcAssembly.GetName().Name },
}); };
var dependencyContext = GetDependencyContext(libraries);
var provider = new TestApplicationAssembliesProvider var provider = new TestApplicationAssembliesProvider
{ {
@ -83,13 +110,15 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationParts
var classLibrary = typeof(object).Assembly; var classLibrary = typeof(object).Assembly;
var relatedPart = typeof(FactAttribute).Assembly; var relatedPart = typeof(FactAttribute).Assembly;
var dependencyContext = GetDependencyContext(new[] var libraries = new Dictionary<string, string[]>
{ {
GetLibrary(ThisAssembly.GetName().Name, new[] { relatedPart.GetName().Name, classLibrary.GetName().Name }), [ThisAssembly.GetName().Name] = new[] { relatedPart.GetName().Name, classLibrary.GetName().Name },
GetLibrary(classLibrary.GetName().Name, new[] { mvcAssembly.GetName().Name }), [classLibrary.GetName().Name] = new[] { mvcAssembly.GetName().Name },
GetLibrary(relatedPart.GetName().Name, new[] { mvcAssembly.GetName().Name }), [relatedPart.GetName().Name] = new[] { mvcAssembly.GetName().Name },
GetLibrary(mvcAssembly.GetName().Name), [mvcAssembly.GetName().Name] = Array.Empty<string>(),
}); };
var dependencyContext = GetDependencyContext(libraries);
var provider = new TestApplicationAssembliesProvider var provider = new TestApplicationAssembliesProvider
{ {
@ -136,8 +165,8 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationParts
{ {
// Arrange // Arrange
var mvcAssembly = typeof(IActionResult).Assembly; var mvcAssembly = typeof(IActionResult).Assembly;
var libraryAssembly1 = typeof(object).Assembly; var libraryAssembly1 = typeof(HttpContext).Assembly;
var libraryAssembly2 = typeof(HttpContext).Assembly; var libraryAssembly2 = typeof(JsonConverter).Assembly;
var relatedPart = typeof(FactAttribute).Assembly; var relatedPart = typeof(FactAttribute).Assembly;
var expected = string.Join( var expected = string.Join(
Environment.NewLine, Environment.NewLine,
@ -145,13 +174,15 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationParts
libraryAssembly1.FullName, libraryAssembly1.FullName,
libraryAssembly2.FullName); libraryAssembly2.FullName);
var dependencyContext = GetDependencyContext(new[] var libraries = new Dictionary<string, string[]>
{ {
GetLibrary(ThisAssembly.GetName().Name, new[] { relatedPart.GetName().Name, libraryAssembly1.GetName().Name }), [ThisAssembly.GetName().Name] = new[] { relatedPart.GetName().Name, libraryAssembly1.GetName().Name },
GetLibrary(libraryAssembly1.GetName().Name, new[] { mvcAssembly.GetName().Name }), [libraryAssembly1.GetName().Name] = new[] { mvcAssembly.GetName().Name },
GetLibrary(libraryAssembly2.GetName().Name, new[] { mvcAssembly.GetName().Name }), [libraryAssembly2.GetName().Name] = new[] { mvcAssembly.GetName().Name },
GetLibrary(mvcAssembly.GetName().Name), [mvcAssembly.GetName().Name] = Array.Empty<string>(),
}); };
var dependencyContext = GetDependencyContext(libraries);
var provider = new TestApplicationAssembliesProvider var provider = new TestApplicationAssembliesProvider
{ {
@ -179,31 +210,41 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationParts
var upperCaseLibrary = "Microsoft.AspNetCore.Mvc"; var upperCaseLibrary = "Microsoft.AspNetCore.Mvc";
var mixedCaseLibrary = "microsoft.aspNetCore.mvc"; var mixedCaseLibrary = "microsoft.aspNetCore.mvc";
var dependencyContext = GetDependencyContext(new[] var libraries = new Dictionary<string, string[]>
{ {
GetLibrary(mixedCaseLibrary), [upperCaseLibrary] = Array.Empty<string>(),
GetLibrary(upperCaseLibrary), [mixedCaseLibrary] = Array.Empty<string>(),
}); };
var dependencyContext = GetDependencyContext(libraries);
// Act // Act
var exception = Assert.Throws<InvalidOperationException>(() => ApplicationAssembliesProvider.GetCandidateLibraries(dependencyContext)); var exception = Assert.Throws<InvalidOperationException>(() => ApplicationAssembliesProvider.GetCandidateLibraries(dependencyContext).ToArray());
// Assert // Assert
Assert.Equal($"A duplicate entry for library reference {upperCaseLibrary} was found. Please check that all package references in all projects use the same casing for the same package references.", exception.Message); Assert.Equal($"A duplicate entry for library reference {mixedCaseLibrary} was found. Please check that all package references in all projects use the same casing for the same package references.", exception.Message);
} }
[Fact] [Fact]
public void GetCandidateLibraries_IgnoresMvcAssemblies() public void GetCandidateLibraries_IgnoresMvcAssemblies()
{ {
// Arrange // Arrange
var expected = GetLibrary("SomeRandomAssembly", "Microsoft.AspNetCore.Mvc.Abstractions"); var expected = GetRuntimeLibrary("SomeRandomAssembly", "Microsoft.AspNetCore.Mvc.Abstractions");
var dependencyContext = GetDependencyContext(new[] var runtimeLibraries = new[]
{ {
GetLibrary("Microsoft.AspNetCore.Mvc.Core"), GetRuntimeLibrary("Microsoft.AspNetCore.Mvc.Core"),
GetLibrary("Microsoft.AspNetCore.Mvc"), GetRuntimeLibrary("Microsoft.AspNetCore.Mvc"),
GetLibrary("Microsoft.AspNetCore.Mvc.Abstractions"), GetRuntimeLibrary("Microsoft.AspNetCore.Mvc.Abstractions"),
expected, expected,
}); };
var compileLibraries = new[]
{
GetCompileLibrary("Microsoft.AspNetCore.Mvc.Core"),
GetCompileLibrary("Microsoft.AspNetCore.Mvc"),
GetCompileLibrary("Microsoft.AspNetCore.Mvc.Abstractions"),
GetCompileLibrary("SomeRandomAssembly", "Microsoft.AspNetCore.Mvc.Abstractions"),
};
var dependencyContext = GetDependencyContext(compileLibraries, runtimeLibraries);
// Act // Act
var candidates = ApplicationAssembliesProvider.GetCandidateLibraries(dependencyContext); var candidates = ApplicationAssembliesProvider.GetCandidateLibraries(dependencyContext);
@ -213,16 +254,86 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationParts
} }
[Fact] [Fact]
public void GetCandidateLibraries_DoesNotThrow_IfLibraryDoesNotHaveRuntimeComponent() public void GetCandidateLibraries_ReturnsRuntimeLibraries_IfCompileLibraryDependencyToMvcIsPresent()
{ {
// Arrange // Arrange
var expected = GetLibrary("MyApplication", "Microsoft.AspNetCore.Server.Kestrel", "Microsoft.AspNetCore.Mvc"); // When an app is running against Microsoft.AspNetCore.All shared runtime or if the DependencyContext is queried
var dependencyContext = GetDependencyContext(new[] // from an app that's running on Microsoft.NETCore.App (e.g. in a unit testing scenario), the
// runtime library does not state that the app references Mvc whereas the compile library does. This test validates
// that we correctly recognize this scenario.
var expected = GetRuntimeLibrary("MyApp", "Microsoft.AspNetCore.All");
var runtimeLibraries = new[]
{ {
expected, expected,
GetLibrary("Microsoft.AspNetCore.Server.Kestrel", "Libuv"), GetRuntimeLibrary("Microsoft.AspNetCore.All", "Microsoft.NETCore.App"),
GetLibrary("Microsoft.AspNetCore.Mvc"), GetRuntimeLibrary("Microsoft.NETCore.App"),
}); };
var compileLibraries = new[]
{
GetCompileLibrary("MyApp", "Microsoft.AspNetCore.All"),
GetCompileLibrary("Microsoft.AspNetCore.All", "Microsoft.AspNetCore.Mvc", "Microsoft.NETCore.App"),
GetCompileLibrary("Microsoft.AspNetCore.Mvc"),
GetCompileLibrary("Microsoft.NETCore.App"),
};
var dependencyContext = GetDependencyContext(compileLibraries, runtimeLibraries);
// Act
var candidates = ApplicationAssembliesProvider.GetCandidateLibraries(dependencyContext);
// Assert
Assert.Equal(new[] { expected }, candidates);
}
[Fact]
public void GetCandidateLibraries_DoesNotThrow_IfLibraryDoesNotAppearAsCompileOrRuntimeLibrary()
{
// Arrange
var expected = GetRuntimeLibrary("MyApplication", "Microsoft.AspNetCore.Server.Kestrel", "Microsoft.AspNetCore.Mvc");
var compileLibraries = new[]
{
GetCompileLibrary("MyApplication", "Microsoft.AspNetCore.Server.Kestrel", "Microsoft.AspNetCore.Mvc"),
GetCompileLibrary("Microsoft.AspNetCore.Mvc"),
GetCompileLibrary("Microsoft.AspNetCore.Server.Kestrel", "Libuv"),
};
var runtimeLibraries = new[]
{
expected,
GetRuntimeLibrary("Microsoft.AspNetCore.Server.Kestrel", "Libuv"),
GetRuntimeLibrary("Microsoft.AspNetCore.Mvc"),
};
var dependencyContext = GetDependencyContext(compileLibraries, runtimeLibraries);
// Act
var candidates = ApplicationAssembliesProvider.GetCandidateLibraries(dependencyContext).ToList();
// Assert
Assert.Equal(new[] { expected }, candidates);
}
[Fact]
public void GetCandidateLibraries_DoesNotThrow_IfCompileLibraryIsPresentButNotRuntimeLibrary()
{
// Arrange
var expected = GetRuntimeLibrary("MyApplication", "Microsoft.AspNetCore.Server.Kestrel", "Microsoft.AspNetCore.Mvc");
var compileLibraries = new[]
{
GetCompileLibrary("MyApplication", "Microsoft.AspNetCore.Server.Kestrel", "Microsoft.AspNetCore.Mvc"),
GetCompileLibrary("Microsoft.AspNetCore.Mvc"),
GetCompileLibrary("Microsoft.AspNetCore.Server.Kestrel", "Libuv"),
GetCompileLibrary("Libuv"),
};
var runtimeLibraries = new[]
{
expected,
GetRuntimeLibrary("Microsoft.AspNetCore.Server.Kestrel", "Libuv"),
GetRuntimeLibrary("Microsoft.AspNetCore.Mvc"),
};
var dependencyContext = GetDependencyContext(compileLibraries, runtimeLibraries);
// Act // Act
var candidates = ApplicationAssembliesProvider.GetCandidateLibraries(dependencyContext).ToList(); var candidates = ApplicationAssembliesProvider.GetCandidateLibraries(dependencyContext).ToList();
@ -235,78 +346,81 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationParts
public void GetCandidateLibraries_ReturnsLibrariesReferencingAnyMvcAssembly() public void GetCandidateLibraries_ReturnsLibrariesReferencingAnyMvcAssembly()
{ {
// Arrange // Arrange
var dependencyContext = GetDependencyContext(new[] var libraries = new Dictionary<string, string[]>
{ {
GetLibrary("Foo", "Microsoft.AspNetCore.Mvc.Core"), ["Foo"] = new[] { "Microsoft.AspNetCore.Mvc.Core" },
GetLibrary("Bar", "Microsoft.AspNetCore.Mvc"), ["Bar"] = new[] { "Microsoft.AspNetCore.Mvc" },
GetLibrary("Qux", "Not.Mvc.Assembly", "Unofficial.Microsoft.AspNetCore.Mvc"), ["Qux"] = new[] { "Not.Mvc.Assembly", "Unofficial.Microsoft.AspNetCore.Mvc" },
GetLibrary("Baz", "Microsoft.AspNetCore.Mvc.Abstractions"), ["Baz"] = new[] { "Microsoft.AspNetCore.Mvc.Abstractions" },
GetLibrary("Microsoft.AspNetCore.Mvc.Core"), ["Microsoft.AspNetCore.Mvc.Core"] = Array.Empty<string>(),
GetLibrary("Microsoft.AspNetCore.Mvc"), ["Microsoft.AspNetCore.Mvc"] = Array.Empty<string>(),
GetLibrary("Not.Mvc.Assembly"), ["Not.Mvc.Assembly"] = Array.Empty<string>(),
GetLibrary("Unofficial.Microsoft.AspNetCore.Mvc"), ["Unofficial.Microsoft.AspNetCore.Mvc"] = Array.Empty<string>(),
GetLibrary("Microsoft.AspNetCore.Mvc.Abstractions"), ["Microsoft.AspNetCore.Mvc.Abstractions"] = Array.Empty<string>(),
}); };
var dependencyContext = GetDependencyContext(libraries);
// Act // Act
var candidates = ApplicationAssembliesProvider.GetCandidateLibraries(dependencyContext); var candidates = ApplicationAssembliesProvider.GetCandidateLibraries(dependencyContext);
// Assert // Assert
Assert.Equal(new[] { "Foo", "Bar", "Baz" }, candidates.Select(a => a.Name)); Assert.Equal(new[] { "Bar", "Baz", "Foo", }, candidates.Select(a => a.Name));
} }
[Fact] [Fact]
public void GetCandidateLibraries_LibraryNameComparisonsAreCaseInsensitive() public void GetCandidateLibraries_LibraryNameComparisonsAreCaseInsensitive()
{ {
// Arrange // Arrange
var dependencyContext = GetDependencyContext(new[] var libraries = new Dictionary<string, string[]>
{ {
GetLibrary("Foo", "MICROSOFT.ASPNETCORE.MVC.CORE"), ["Foo"] = new[] { "MICROSOFT.ASPNETCORE.MVC.CORE" },
GetLibrary("Bar", "microsoft.aspnetcore.mvc"), ["Bar"] = new[] { "microsoft.aspnetcore.mvc" },
GetLibrary("Qux", "Not.Mvc.Assembly", "Unofficial.Microsoft.AspNetCore.Mvc"), ["Qux"] = new[] { "Not.Mvc.Assembly", "Unofficial.Microsoft.AspNetCore.Mvc" },
GetLibrary("Baz", "mIcRoSoFt.AsPnEtCoRe.MvC.aBsTrAcTiOnS"), ["Baz"] = new[] { "mIcRoSoFt.AsPnEtCoRe.MvC.aBsTrAcTiOnS" },
GetLibrary("Microsoft.AspNetCore.Mvc.Core"), ["Microsoft.AspNetCore.Mvc.Core"] = Array.Empty<string>(),
GetLibrary("LibraryA", "LIBRARYB"), ["LibraryA"] = new[] { "LIBRARYB" },
GetLibrary("LibraryB", "microsoft.aspnetcore.mvc"), ["LibraryB"] = new[] { "microsoft.aspnetcore.mvc" },
GetLibrary("Microsoft.AspNetCore.Mvc"), ["Microsoft.AspNetCore.Mvc"] = Array.Empty<string>(),
GetLibrary("Not.Mvc.Assembly"), ["Not.Mvc.Assembly"] = Array.Empty<string>(),
GetLibrary("Unofficial.Microsoft.AspNetCore.Mvc"), ["Unofficial.Microsoft.AspNetCore.Mvc"] = Array.Empty<string>(),
GetLibrary("Microsoft.AspNetCore.Mvc.Abstractions"), ["Microsoft.AspNetCore.Mvc.Abstractions"] = Array.Empty<string>(),
}); };
var dependencyContext = GetDependencyContext(libraries);
// Act // Act
var candidates = ApplicationAssembliesProvider.GetCandidateLibraries(dependencyContext); var candidates = ApplicationAssembliesProvider.GetCandidateLibraries(dependencyContext);
// Assert // Assert
Assert.Equal(new[] { "Foo", "Bar", "Baz", "LibraryA", "LibraryB" }, candidates.Select(a => a.Name)); Assert.Equal(new[] { "Bar", "Baz", "Foo", "LibraryA", "LibraryB" }, candidates.Select(a => a.Name));
} }
[Fact] [Fact]
public void GetCandidateLibraries_ReturnsLibrariesWithTransitiveReferencesToAnyMvcAssembly() public void GetCandidateLibraries_ReturnsLibrariesWithTransitiveReferencesToAnyMvcAssembly()
{ {
// Arrange // Arrange
var expectedLibraries = new[] { "Foo", "Bar", "Baz", "LibraryA", "LibraryB", "LibraryC", "LibraryE", "LibraryG", "LibraryH" }; var expectedLibraries = new[] { "Bar", "Baz", "Foo", "LibraryA", "LibraryB", "LibraryC", "LibraryE", "LibraryG", "LibraryH" };
var dependencyContext = GetDependencyContext(new[] var libraries = new Dictionary<string, string[]>
{ {
GetLibrary("Foo", "Bar"), ["Foo"] = new[] { "Bar" },
GetLibrary("Bar", "Microsoft.AspNetCore.Mvc"), ["Bar"] = new[] { "Microsoft.AspNetCore.Mvc" },
GetLibrary("Qux", "Not.Mvc.Assembly", "Unofficial.Microsoft.AspNetCore.Mvc"), ["Qux"] = new[] { "Not.Mvc.Assembly", "Unofficial.Microsoft.AspNetCore.Mvc" },
GetLibrary("Baz", "Microsoft.AspNetCore.Mvc.Abstractions"), ["Baz"] = new[] { "Microsoft.AspNetCore.Mvc.Abstractions" },
GetLibrary("Microsoft.AspNetCore.Mvc"), ["Microsoft.AspNetCore.Mvc"] = Array.Empty<string>(),
GetLibrary("Not.Mvc.Assembly"), ["Not.Mvc.Assembly"] = Array.Empty<string>(),
GetLibrary("Microsoft.AspNetCore.Mvc.Abstractions"), ["Microsoft.AspNetCore.Mvc.Abstractions"] = Array.Empty<string>(),
GetLibrary("Unofficial.Microsoft.AspNetCore.Mvc"), ["Unofficial.Microsoft.AspNetCore.Mvc"] = Array.Empty<string>(),
GetLibrary("LibraryA", "LibraryB"), ["LibraryA"] = new[] { "LibraryB" },
GetLibrary("LibraryB","LibraryC"), ["LibraryB"] = new[] { "LibraryC" },
GetLibrary("LibraryC", "LibraryD", "Microsoft.AspNetCore.Mvc.Abstractions"), ["LibraryC"] = new[] { "LibraryD", "Microsoft.AspNetCore.Mvc.Abstractions" },
GetLibrary("LibraryD"), ["LibraryD"] = Array.Empty<string>(),
GetLibrary("LibraryE","LibraryF","LibraryG"), ["LibraryE"] = new[] { "LibraryF", "LibraryG" },
GetLibrary("LibraryF"), ["LibraryF"] = Array.Empty<string>(),
GetLibrary("LibraryG", "LibraryH"), ["LibraryG"] = new[] { "LibraryH" },
GetLibrary("LibraryH", "LibraryI", "Microsoft.AspNetCore.Mvc"), ["LibraryH"] = new[] { "LibraryI", "Microsoft.AspNetCore.Mvc" },
GetLibrary("LibraryI"), ["LibraryI"] = Array.Empty<string>(),
}); };
var dependencyContext = GetDependencyContext(libraries);
// Act // Act
var candidates = ApplicationAssembliesProvider.GetCandidateLibraries(dependencyContext); var candidates = ApplicationAssembliesProvider.GetCandidateLibraries(dependencyContext);
@ -319,23 +433,25 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationParts
public void GetCandidateLibraries_SkipsMvcAssemblies() public void GetCandidateLibraries_SkipsMvcAssemblies()
{ {
// Arrange // Arrange
var dependencyContext = GetDependencyContext(new[] var libraries = new Dictionary<string, string[]>
{ {
GetLibrary("MvcSandbox", "Microsoft.AspNetCore.Mvc.Core", "Microsoft.AspNetCore.Mvc"), ["MvcSandbox"] = new[] { "Microsoft.AspNetCore.Mvc.Core", "Microsoft.AspNetCore.Mvc" },
GetLibrary("Microsoft.AspNetCore.Mvc.Core", "Microsoft.AspNetCore.HttpAbstractions"), ["Microsoft.AspNetCore.Mvc.Core"] = new[] { "Microsoft.AspNetCore.HttpAbstractions" },
GetLibrary("Microsoft.AspNetCore.HttpAbstractions"), ["Microsoft.AspNetCore.HttpAbstractions"] = Array.Empty<string>(),
GetLibrary("Microsoft.AspNetCore.Mvc", "Microsoft.AspNetCore.Mvc.Abstractions", "Microsoft.AspNetCore.Mvc.Core"), ["Microsoft.AspNetCore.Mvc"] = new[] { "Microsoft.AspNetCore.Mvc.Abstractions", "Microsoft.AspNetCore.Mvc.Core" },
GetLibrary("Microsoft.AspNetCore.Mvc.Abstractions"), ["Microsoft.AspNetCore.Mvc.Abstractions"] = Array.Empty<string>(),
GetLibrary("Microsoft.AspNetCore.Mvc.TagHelpers", "Microsoft.AspNetCore.Mvc.Razor"), ["Microsoft.AspNetCore.Mvc.TagHelpers"] = new[] { "Microsoft.AspNetCore.Mvc.Razor" },
GetLibrary("Microsoft.AspNetCore.Mvc.Razor"), ["Microsoft.AspNetCore.Mvc.Razor"] = Array.Empty<string>(),
GetLibrary("ControllersAssembly", "Microsoft.AspNetCore.Mvc"), ["ControllersAssembly"] = new[] { "Microsoft.AspNetCore.Mvc" },
}); };
var dependencyContext = GetDependencyContext(libraries);
// Act // Act
var candidates = ApplicationAssembliesProvider.GetCandidateLibraries(dependencyContext); var candidates = ApplicationAssembliesProvider.GetCandidateLibraries(dependencyContext);
// Assert // Assert
Assert.Equal(new[] { "MvcSandbox", "ControllersAssembly" }, candidates.Select(a => a.Name)); Assert.Equal(new[] { "ControllersAssembly", "MvcSandbox" }, candidates.Select(a => a.Name));
} }
// This test verifies DefaultAssemblyPartDiscoveryProvider.ReferenceAssemblies reflects the actual loadable assemblies // This test verifies DefaultAssemblyPartDiscoveryProvider.ReferenceAssemblies reflects the actual loadable assemblies
@ -397,18 +513,37 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationParts
} }
} }
private static DependencyContext GetDependencyContext(RuntimeLibrary[] libraries) private static DependencyContext GetDependencyContext(IDictionary<string, string[]> libraries)
{
var compileLibraries = new List<CompilationLibrary>();
var runtimeLibraries = new List<RuntimeLibrary>();
foreach (var kvp in libraries.OrderBy(kvp => kvp.Key, StringComparer.Ordinal))
{
var compileLibrary = GetCompileLibrary(kvp.Key, kvp.Value);
compileLibraries.Add(compileLibrary);
var runtimeLibrary = GetRuntimeLibrary(kvp.Key, kvp.Value);
runtimeLibraries.Add(runtimeLibrary);
}
return GetDependencyContext(compileLibraries, runtimeLibraries);
}
private static DependencyContext GetDependencyContext(
IReadOnlyList<CompilationLibrary> compileLibraries,
IReadOnlyList<RuntimeLibrary> runtimeLibraries)
{ {
var dependencyContext = new DependencyContext( var dependencyContext = new DependencyContext(
new TargetInfo("framework", "runtime", "signature", isPortable: true), new TargetInfo("framework", "runtime", "signature", isPortable: true),
CompilationOptions.Default, CompilationOptions.Default,
new CompilationLibrary[0], compileLibraries,
libraries, runtimeLibraries,
Enumerable.Empty<RuntimeFallbacks>()); Enumerable.Empty<RuntimeFallbacks>());
return dependencyContext; return dependencyContext;
} }
private static RuntimeLibrary GetLibrary(string name, params string[] dependencyNames) private static RuntimeLibrary GetRuntimeLibrary(string name, params string[] dependencyNames)
{ {
var dependencies = dependencyNames?.Select(d => new Dependency(d, "42.0.0")) ?? new Dependency[0]; var dependencies = dependencyNames?.Select(d => new Dependency(d, "42.0.0")) ?? new Dependency[0];
@ -424,6 +559,20 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationParts
serviceable: true); serviceable: true);
} }
private static CompilationLibrary GetCompileLibrary(string name, params string[] dependencyNames)
{
var dependencies = dependencyNames?.Select(d => new Dependency(d, "42.0.0")) ?? new Dependency[0];
return new CompilationLibrary(
"package",
name,
"23.0.0",
"hash",
Enumerable.Empty<string>(),
dependencies: dependencies.ToArray(),
serviceable: true);
}
private class TestAssembly : Assembly private class TestAssembly : Assembly
{ {
public override string FullName => "TestRelatedAssembly"; public override string FullName => "TestRelatedAssembly";