Skip to content

Commit

Permalink
Generate RID graph in self-contained builds
Browse files Browse the repository at this point in the history
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 dotnet#3361
  • Loading branch information
swaroop-sridhar committed Oct 29, 2019
1 parent 9d766f7 commit 1041a65
Show file tree
Hide file tree
Showing 7 changed files with 151 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -64,7 +66,7 @@ public void ItBuildsDependencyContextsFromProjectLockFiles(
resolvedNuGetFiles = Array.Empty<ResolvedFile>();
}

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)
Expand Down Expand Up @@ -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();
Expand All @@ -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);
}
}
}
32 changes: 25 additions & 7 deletions src/Tasks/Microsoft.NET.Build.Tasks/DependencyContextBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -36,6 +37,7 @@ internal class DependencyContextBuilder
private string _runtimeIdentifier;
private bool _isPortable;
private HashSet<string> _usedLibraryNames;
private readonly RuntimeGraph _runtimeGraph;

private Dictionary<ReferenceInfo, string> _referenceLibraryNames;

Expand All @@ -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);

Expand Down Expand Up @@ -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()
Expand Down
20 changes: 17 additions & 3 deletions src/Tasks/Microsoft.NET.Build.Tasks/GenerateDepsFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -92,6 +91,9 @@ public class GenerateDepsFile : TaskBase

public bool IncludeRuntimeFileVersions { get; set; }

[Required]
public string RuntimeGraphPath { get; set; }

List<ITaskItem> _filesWritten = new List<ITaskItem>();

[Output]
Expand Down Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ Copyright (c) .NET Foundation. All rights reserved.
ResolvedNuGetFiles="@(NativeCopyLocalItems);@(ResourceCopyLocalItems);@(RuntimeCopyLocalItems)"
ResolvedRuntimeTargetsFiles="@(RuntimeTargetsCopyLocalItems)"
TargetFramework="$(TargetFrameworkMoniker)"
RuntimeGraphPath="$(BundledRuntimeIdentifierGraphFile)"
/>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -933,7 +933,8 @@ Copyright (c) .NET Foundation. All rights reserved.
ResolvedRuntimeTargetsFiles="@(RuntimeTargetsCopyLocalItems)"
UserRuntimeAssemblies="@(UserRuntimeAssembly)"
IsSelfContained="$(SelfContained)"
IncludeRuntimeFileVersions="$(IncludeFileVersionsInDependencyFile)"/>
IncludeRuntimeFileVersions="$(IncludeFileVersionsInDependencyFile)"
RuntimeGraphPath="$(BundledRuntimeIdentifierGraphFile)"/>

<ItemGroup>
<_FilesToBundle Include="$(PublishDepsFilePath)" Condition="'$(PublishSingleFile)' == 'true'">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -212,8 +212,8 @@ Copyright (c) .NET Foundation. All rights reserved.
UserRuntimeAssemblies="@(UserRuntimeAssembly)"
ResolvedRuntimeTargetsFiles="@(RuntimeTargetsCopyLocalItems)"
IsSelfContained="$(SelfContained)"
IncludeRuntimeFileVersions="$(IncludeFileVersionsInDependencyFile)">
</GenerateDepsFile>
IncludeRuntimeFileVersions="$(IncludeFileVersionsInDependencyFile)"
RuntimeGraphPath="$(BundledRuntimeIdentifierGraphFile)"/>

<ItemGroup>
<!-- Do this in an ItemGroup instead of as an output parameter of the GenerateDepsFile task so that it still gets added to the item set
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,52 @@ public static void Main()
}
}

[Theory]
[InlineData(true)]
[InlineData(false)]
public void It_generates_rid_fallback_graph(bool isSelfContained)
{
var targetFramework = "netcoreapp3.0";
var runtimeIdentifier = EnvironmentInfo.GetCompatibleRid(targetFramework);

TestProject project = new TestProject()
{
Name = "NetCore2App",
TargetFrameworks = targetFramework,
IsExe = true,
IsSdkProject = true,
RuntimeIdentifier = runtimeIdentifier
};

var testAsset = _testAssetsManager.CreateTestProject(project);
string projectFolder = Path.Combine(testAsset.Path, project.Name);

var buildCommand = new BuildCommand(Log, projectFolder);

buildCommand
.Execute($"/p:SelfContained={isSelfContained}")
.Should()
.Pass();

string outputFolder = buildCommand.GetOutputDirectory(project.TargetFrameworks, runtimeIdentifier: runtimeIdentifier).FullName;

using var depsJsonFileStream = File.OpenRead(Path.Combine(outputFolder, $"{project.Name}.deps.json"));
var dependencyContext = new DependencyContextJsonReader().Read(depsJsonFileStream);
var runtimeFallbackGraph = dependencyContext.RuntimeGraph;
if (isSelfContained)
{
runtimeFallbackGraph.Should().NotBeEmpty();
runtimeFallbackGraph
.Any(runtimeFallback => !runtimeFallback.Runtime.Equals(runtimeIdentifier) && !runtimeFallback.Fallbacks.Contains(runtimeIdentifier))
.Should()
.BeFalse();
}
else
{
runtimeFallbackGraph.Should().BeEmpty();
}
}

[Fact]
public void There_are_no_conflicts_when_targeting_netcoreapp_1_1()
{
Expand Down

0 comments on commit 1041a65

Please sign in to comment.