From 1041a65073f97cf20e986971e6ab6f8f5d778a19 Mon Sep 17 00:00:00 2001 From: Swaroop Sridhar Date: Tue, 22 Oct 2019 17:16:04 -0700 Subject: [PATCH] Generate RID graph in self-contained builds In order to support loading components with RID-specific assets, the AssemblyDependencyResolver requires a RID fallback graph. The component itself should not carry the RID fallback graph with it (it would need to have the graph of all the RIDs there are and it would need to be updated with every addition). For framework dependent apps, the RID fallback graph comes from the core framework Microsoft.NETCore.App, so there is no need to write it into the app. If self-contained apps, the (applicable subset of) RID fallback graph needs to be written to the deps.json manifest. Fixes #3361 --- .../GivenADependencyContextBuilder.cs | 60 ++++++++++++++++++- .../DependencyContextBuilder.cs | 32 +++++++--- .../GenerateDepsFile.cs | 20 ++++++- .../Microsoft.NET.DesignerSupport.targets | 1 + .../targets/Microsoft.NET.Publish.targets | 3 +- .../targets/Microsoft.NET.Sdk.targets | 4 +- .../GivenThatWeWantToBuildANetCoreApp.cs | 46 ++++++++++++++ 7 files changed, 151 insertions(+), 15 deletions(-) diff --git a/src/Tasks/Microsoft.NET.Build.Tasks.UnitTests/GivenADependencyContextBuilder.cs b/src/Tasks/Microsoft.NET.Build.Tasks.UnitTests/GivenADependencyContextBuilder.cs index 89182936943a..b42c46c82dfc 100644 --- a/src/Tasks/Microsoft.NET.Build.Tasks.UnitTests/GivenADependencyContextBuilder.cs +++ b/src/Tasks/Microsoft.NET.Build.Tasks.UnitTests/GivenADependencyContextBuilder.cs @@ -11,11 +11,13 @@ using NuGet.Packaging.Core; using NuGet.ProjectModel; using NuGet.Versioning; +using NuGet.RuntimeModel; using System; using System.Collections.Generic; using System.IO; using System.Linq; using Xunit; +using Xunit.Sdk; namespace Microsoft.NET.Build.Tasks.UnitTests { @@ -64,7 +66,7 @@ public void ItBuildsDependencyContextsFromProjectLockFiles( resolvedNuGetFiles = Array.Empty(); } - DependencyContext dependencyContext = new DependencyContextBuilder(mainProject, projectContext, includeRuntimeFileVersions: false) + DependencyContext dependencyContext = new DependencyContextBuilder(mainProject, projectContext, includeRuntimeFileVersions: false, runtimeGraph:null) .WithDirectReferences(directReferences) .WithCompilationOptions(compilationOptions) .WithResolvedNuGetFiles((ResolvedFile[]) resolvedNuGetFiles) @@ -261,7 +263,7 @@ private DependencyContext BuildDependencyContextWithReferenceAssemblies(bool use useCompilationOptions ? CreateCompilationOptions() : null; - DependencyContext dependencyContext = new DependencyContextBuilder(mainProject, projectContext, includeRuntimeFileVersions: false) + DependencyContext dependencyContext = new DependencyContextBuilder(mainProject, projectContext, includeRuntimeFileVersions: false, runtimeGraph: null) .WithReferenceAssemblies(ReferenceInfo.CreateReferenceInfos(referencePaths)) .WithCompilationOptions(compilationOptions) .Build(); @@ -288,5 +290,59 @@ private static CompilationOptions CreateCompilationOptions() emitEntryPoint: true, generateXmlDocumentation: true); } + + [Fact] + public void ItCanGenerateTheRuntimeFallbackGraph() + { + string mainProjectName = "simple.dependencies"; + LockFile lockFile = TestLockFiles.GetLockFile(mainProjectName); + + SingleProjectInfo mainProject = SingleProjectInfo.Create( + "/usr/Path", + mainProjectName, + ".dll", + "1.0.0", + new ITaskItem[] { }); + + ProjectContext projectContext = lockFile.CreateProjectContext( + FrameworkConstants.CommonFrameworks.NetCoreApp10, + runtime: null, + platformLibraryName: Constants.DefaultPlatformLibrary, + runtimeFrameworks: null, + isSelfContained: true); + + var runtimeGraph = new RuntimeGraph( + new RuntimeDescription [] + { + new RuntimeDescription("os-arch", new string [] { "os", "base" }), + new RuntimeDescription("new_os-arch", new string [] { "os-arch", "os", "base" }), + new RuntimeDescription("os-new_arch", new string [] { "os-arch", "os", "base" }), + new RuntimeDescription("new_os-new_arch", new string [] { "new_os-arch", "os-new_arch", "os-arch", "os", "base" }), + new RuntimeDescription("os-another_arch", new string [] { "os", "base" }) + }); + + void CheckRuntimeFallbacks(string runtimeIdentifier, int fallbackCount) + { + projectContext.LockFileTarget.RuntimeIdentifier = runtimeIdentifier; + var dependencyContextBuilder = new DependencyContextBuilder(mainProject, projectContext, includeRuntimeFileVersions: false, runtimeGraph); + var runtimeFallbacks = dependencyContextBuilder.Build().RuntimeGraph; + + runtimeFallbacks + .Count() + .Should() + .Be(fallbackCount); + + runtimeFallbacks + .Any(runtimeFallback => !runtimeFallback.Runtime.Equals(runtimeIdentifier) && !runtimeFallback.Fallbacks.Contains(runtimeIdentifier)) + .Should() + .BeFalse(); + } + + CheckRuntimeFallbacks("os-arch", 4); + CheckRuntimeFallbacks("new_os-arch", 2); + CheckRuntimeFallbacks("os-new_arch", 2); + CheckRuntimeFallbacks("new_os-new_arch", 1); + CheckRuntimeFallbacks("unrelated_os-unknown_arch", 0); + } } } diff --git a/src/Tasks/Microsoft.NET.Build.Tasks/DependencyContextBuilder.cs b/src/Tasks/Microsoft.NET.Build.Tasks/DependencyContextBuilder.cs index 1fa421b789bf..0c3a938131b8 100644 --- a/src/Tasks/Microsoft.NET.Build.Tasks/DependencyContextBuilder.cs +++ b/src/Tasks/Microsoft.NET.Build.Tasks/DependencyContextBuilder.cs @@ -7,6 +7,7 @@ using NuGet.Packaging; using NuGet.Packaging.Core; using NuGet.ProjectModel; +using NuGet.RuntimeModel; using NuGet.Versioning; namespace Microsoft.NET.Build.Tasks @@ -36,6 +37,7 @@ internal class DependencyContextBuilder private string _runtimeIdentifier; private bool _isPortable; private HashSet _usedLibraryNames; + private readonly RuntimeGraph _runtimeGraph; private Dictionary _referenceLibraryNames; @@ -44,10 +46,11 @@ internal class DependencyContextBuilder private const string NetCorePlatformLibrary = "Microsoft.NETCore.App"; - public DependencyContextBuilder(SingleProjectInfo mainProjectInfo, ProjectContext projectContext, bool includeRuntimeFileVersions) + public DependencyContextBuilder(SingleProjectInfo mainProjectInfo, ProjectContext projectContext, bool includeRuntimeFileVersions, RuntimeGraph runtimeGraph) { _mainProjectInfo = mainProjectInfo; _includeRuntimeFileVersions = includeRuntimeFileVersions; + _runtimeGraph = runtimeGraph; var libraryLookup = new LockFileLookup(projectContext.LockFile); @@ -322,19 +325,34 @@ public DependencyContext Build() } } - var targetInfo = new TargetInfo( _dotnetFrameworkName, _runtimeIdentifier, runtimeSignature: string.Empty, _isPortable); + // Compute the runtime fallback graph + // + // If the input RuntimeGraph is empty, or we're not compiling + // for a specific RID, then an runtime fallback graph is empty + // + // Otherwise, it is the set of all runtimes compatible with (inheriting) + // the current runtime-identifier. + + var runtimeFallbackGraph = + (_runtimeGraph == null || _runtimeIdentifier == null) ? + new RuntimeFallbacks[] { } : + _runtimeGraph.Runtimes + .Select(runtimeDict => _runtimeGraph.ExpandRuntime(runtimeDict.Key)) + .Where(expansion => expansion.Contains(_runtimeIdentifier)) + .Select(expansion => new RuntimeFallbacks(expansion.First(), expansion.Skip(1))); // ExpandRuntime return runtime itself as first item. + return new DependencyContext( - targetInfo, - _compilationOptions ?? CompilationOptions.Default, - compilationLibraries, - runtimeLibraries, - new RuntimeFallbacks[] { }); + targetInfo, + _compilationOptions ?? CompilationOptions.Default, + compilationLibraries, + runtimeLibraries, + runtimeFallbackGraph); } private RuntimeLibrary GetProjectRuntimeLibrary() diff --git a/src/Tasks/Microsoft.NET.Build.Tasks/GenerateDepsFile.cs b/src/Tasks/Microsoft.NET.Build.Tasks/GenerateDepsFile.cs index 10a78956bb40..0be5519389e0 100644 --- a/src/Tasks/Microsoft.NET.Build.Tasks/GenerateDepsFile.cs +++ b/src/Tasks/Microsoft.NET.Build.Tasks/GenerateDepsFile.cs @@ -5,10 +5,9 @@ using Microsoft.Build.Utilities; using Microsoft.Extensions.DependencyModel; using Newtonsoft.Json; -using Newtonsoft.Json.Linq; using NuGet.Packaging.Core; +using NuGet.RuntimeModel; using NuGet.ProjectModel; -using NuGet.Versioning; using System; using System.Collections.Generic; using System.IO; @@ -92,6 +91,9 @@ public class GenerateDepsFile : TaskBase public bool IncludeRuntimeFileVersions { get; set; } + [Required] + public string RuntimeGraphPath { get; set; } + List _filesWritten = new List(); [Output] @@ -155,7 +157,19 @@ private void WriteDepsFile(string depsFilePath) RuntimeFrameworks, IsSelfContained); - var builder = new DependencyContextBuilder(mainProject, projectContext, IncludeRuntimeFileVersions); + // Generate the RID-fallback for self-contained builds. + // + // In order to support loading components with RID-specific assets, the AssemblyDependencyResolver requires a RID fallback graph. + // The component itself should not carry the RID fallback graph with it (it would need to have the graph of all the RIDs there are and it would need to be updated with every addition). + // For framework dependent apps, the RID fallback graph comes from the core framework Microsoft.NETCore.App, so there is no need to write it into the app. + // If self-contained apps, the (applicable subset of) RID fallback graph needs to be written to the deps.json manifest. + // + // If a RID-graph is provided to the DependencyContextBuilder, it generates a RID-fallback graph wrt the current RuntimeIdentifier. + + RuntimeGraph runtimeGraph = + IsSelfContained ? new RuntimeGraphCache(this).GetRuntimeGraph(RuntimeGraphPath) : null; + + var builder = new DependencyContextBuilder(mainProject, projectContext, IncludeRuntimeFileVersions, runtimeGraph); builder = builder .WithMainProjectInDepsFile(IncludeMainProject) diff --git a/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.DesignerSupport.targets b/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.DesignerSupport.targets index 76dc53f93a2b..66d21afd7a87 100644 --- a/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.DesignerSupport.targets +++ b/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.DesignerSupport.targets @@ -67,6 +67,7 @@ Copyright (c) .NET Foundation. All rights reserved. ResolvedNuGetFiles="@(NativeCopyLocalItems);@(ResourceCopyLocalItems);@(RuntimeCopyLocalItems)" ResolvedRuntimeTargetsFiles="@(RuntimeTargetsCopyLocalItems)" TargetFramework="$(TargetFrameworkMoniker)" + RuntimeGraphPath="$(BundledRuntimeIdentifierGraphFile)" /> diff --git a/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Publish.targets b/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Publish.targets index 8bdd31a4022d..25f40da15b25 100644 --- a/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Publish.targets +++ b/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Publish.targets @@ -933,7 +933,8 @@ Copyright (c) .NET Foundation. All rights reserved. ResolvedRuntimeTargetsFiles="@(RuntimeTargetsCopyLocalItems)" UserRuntimeAssemblies="@(UserRuntimeAssembly)" IsSelfContained="$(SelfContained)" - IncludeRuntimeFileVersions="$(IncludeFileVersionsInDependencyFile)"/> + IncludeRuntimeFileVersions="$(IncludeFileVersionsInDependencyFile)" + RuntimeGraphPath="$(BundledRuntimeIdentifierGraphFile)"/> <_FilesToBundle Include="$(PublishDepsFilePath)" Condition="'$(PublishSingleFile)' == 'true'"> diff --git a/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Sdk.targets b/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Sdk.targets index facdb64fa036..6a876350ad50 100644 --- a/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Sdk.targets +++ b/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Sdk.targets @@ -212,8 +212,8 @@ Copyright (c) .NET Foundation. All rights reserved. UserRuntimeAssemblies="@(UserRuntimeAssembly)" ResolvedRuntimeTargetsFiles="@(RuntimeTargetsCopyLocalItems)" IsSelfContained="$(SelfContained)" - IncludeRuntimeFileVersions="$(IncludeFileVersionsInDependencyFile)"> - + IncludeRuntimeFileVersions="$(IncludeFileVersionsInDependencyFile)" + RuntimeGraphPath="$(BundledRuntimeIdentifierGraphFile)"/>