diff --git a/scripts/dotnet-cli-build/SharedFrameworkTargets.cs b/scripts/dotnet-cli-build/SharedFrameworkTargets.cs index 93ef44ec2..7dffd64c4 100644 --- a/scripts/dotnet-cli-build/SharedFrameworkTargets.cs +++ b/scripts/dotnet-cli-build/SharedFrameworkTargets.cs @@ -74,20 +74,37 @@ namespace Microsoft.DotNet.Cli.Build File.Move(Path.Combine(SharedFrameworkNameAndVersionRoot, "framework.deps"), Path.Combine(SharedFrameworkNameAndVersionRoot, $"{SharedFrameworkName}.deps")); File.Move(Path.Combine(SharedFrameworkNameAndVersionRoot, "framework.deps.json"), destinationDeps); - // Merge in the RID fallback graph - var fallbackFileName = PlatformServices.Default.Runtime.OperatingSystemPlatform.ToString().ToLowerInvariant() + ".json"; - var fallbackFile = Path.Combine(Dirs.RepoRoot, "src", "sharedframework", "rid-fallbacks", fallbackFileName); - if (File.Exists(fallbackFile)) + // Generate RID fallback graph + string runtimeGraphGeneratorRuntime = null; + switch (PlatformServices.Default.Runtime.OperatingSystemPlatform) { - c.Info($"Merging in RID fallback graph: {fallbackFile}"); - var deps = JObject.Parse(File.ReadAllText(destinationDeps)); - var ridfallback = JObject.Parse(File.ReadAllText(fallbackFile)); - deps["runtimes"] = ridfallback["runtimes"]; - File.WriteAllText(destinationDeps, deps.ToString(Formatting.Indented)); + case Platform.Windows: + runtimeGraphGeneratorRuntime = "win"; + break; + case Platform.Linux: + runtimeGraphGeneratorRuntime = "linux"; + break; + case Platform.Darwin: + runtimeGraphGeneratorRuntime = "osx"; + break; + } + if (!string.IsNullOrEmpty(runtimeGraphGeneratorRuntime)) + { + var runtimeGraphGeneratorName = "RuntimeGraphGenerator"; + var runtimeGraphGeneratorProject = Path.Combine(c.BuildContext.BuildDirectory, "tools", runtimeGraphGeneratorName); + var runtimeGraphGeneratorOutput = Path.Combine(Dirs.Output, "tools", runtimeGraphGeneratorName); + + DotNetCli.Stage2.Publish( + "--output", runtimeGraphGeneratorOutput, + runtimeGraphGeneratorProject).Execute().EnsureSuccessful(); + var runtimeGraphGeneratorExe = Path.Combine(runtimeGraphGeneratorOutput, $"{runtimeGraphGeneratorName}{Constants.ExeSuffix}"); + + Cmd(runtimeGraphGeneratorExe, "--project", SharedFrameworkSourceRoot, "--deps", destinationDeps, runtimeGraphGeneratorRuntime) + .Execute(); } else { - c.Warn($"RID fallback graph file not found: {fallbackFile}"); + c.Error($"Could not determine rid graph generation runtime for platform {PlatformServices.Default.Runtime.OperatingSystemPlatform}"); } // corehost will be renamed to dotnet at some point and then we will not need to rename it here. diff --git a/src/Microsoft.Extensions.DependencyModel/RuntimeFallbacks.cs b/src/Microsoft.Extensions.DependencyModel/RuntimeFallbacks.cs index d759b1942..62f716ba2 100644 --- a/src/Microsoft.Extensions.DependencyModel/RuntimeFallbacks.cs +++ b/src/Microsoft.Extensions.DependencyModel/RuntimeFallbacks.cs @@ -1,19 +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 { public class RuntimeFallbacks { public string Runtime { get; set; } - public IEnumerable Fallbacks { 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; + Fallbacks = fallbacks.ToArray(); } } } \ No newline at end of file diff --git a/tools/RuntimeGraphGenerator/Program.cs b/tools/RuntimeGraphGenerator/Program.cs new file mode 100644 index 000000000..09253af49 --- /dev/null +++ b/tools/RuntimeGraphGenerator/Program.cs @@ -0,0 +1,110 @@ +// 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.CommandLine; +using Microsoft.DotNet.Cli.Utils; +using System.IO; +using System.Runtime.Versioning; +using Microsoft.DotNet.ProjectModel; +using Microsoft.Extensions.DependencyModel; +using NuGet.Frameworks; +using NuGet.Packaging; +using NuGet.Versioning; +using Microsoft.DotNet.ProjectModel.Graph; + +namespace RuntimeGraphGenerator +{ + public class Program + { + public static int Main(string[] args) + { + DebugHelper.HandleDebugSwitch(ref args); + + string projectDirectory = null; + string depsFile = null; + IReadOnlyList runtimes = null; + try + { + ArgumentSyntax.Parse(args, syntax => + { + syntax.ApplicationName = "Runtime GraphGenerator"; + + syntax.HandleHelp = false; + syntax.HandleErrors = false; + + syntax.DefineOption("p|project", ref projectDirectory, "Project location"); + syntax.DefineOption("d|deps", ref depsFile, "Deps file path"); + + syntax.DefineParameterList("runtimes", ref runtimes, "Runtimes"); + }); + } + catch (ArgumentSyntaxException exception) + { + Console.Error.WriteLine(exception.Message); + return 1; + } + + if (runtimes == null || runtimes.Count == 0) + { + Reporter.Error.WriteLine("No runtimes specified"); + return 1; + } + if (!File.Exists(depsFile)) + { + Reporter.Error.WriteLine($"Deps file not found: {depsFile}"); + return 1; + } + if (!Directory.Exists(projectDirectory)) + { + Reporter.Error.WriteLine($"Project directory not found: {projectDirectory}"); + return 1; + } + + try + { + DependencyContext context; + using (var depsStream = File.OpenRead(depsFile)) + { + context = new DependencyContextJsonReader().Read(depsStream); + } + var framework = NuGetFramework.Parse(context.TargetFramework); + var projectContext = ProjectContext.Create(projectDirectory, framework); + + // Configuration is used only for P2P dependencies so were don't care + var exporter = projectContext.CreateExporter("Debug"); + var manager = new RuntimeGraphManager(); + var graph = manager.Collect(exporter.GetDependencies(LibraryType.Package)); + var expandedGraph = manager.Expand(graph, runtimes); + + context = new DependencyContext( + context.TargetFramework, + context.Runtime, + context.IsPortable, + context.CompilationOptions, + context.CompileLibraries, + context.RuntimeLibraries, + expandedGraph + ); + + using (var depsStream = File.Create(depsFile)) + { + new DependencyContextWriter().Write(context, depsStream); + } + + return 0; + } + catch (Exception ex) + { +#if DEBUG + Reporter.Error.WriteLine(ex.ToString()); +#else + Reporter.Error.WriteLine(ex.Message); +#endif + return 1; + } + } + + } +} diff --git a/tools/RuntimeGraphGenerator/RuntimeGraphGenerator.xproj b/tools/RuntimeGraphGenerator/RuntimeGraphGenerator.xproj new file mode 100644 index 000000000..1126cb59b --- /dev/null +++ b/tools/RuntimeGraphGenerator/RuntimeGraphGenerator.xproj @@ -0,0 +1,19 @@ + + + + 14.0.25029 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + efc4fe68-83eb-40e4-bfa8-61d0b4626f25 + RuntimeGraphGenerator + ..\..\artifacts\obj\$(MSBuildProjectName) + ..\..\artifacts\bin\$(MSBuildProjectName)\ + + + + 2.0 + + + \ No newline at end of file diff --git a/tools/RuntimeGraphGenerator/RuntimeGraphManager.cs b/tools/RuntimeGraphGenerator/RuntimeGraphManager.cs new file mode 100644 index 000000000..f1c0629c1 --- /dev/null +++ b/tools/RuntimeGraphGenerator/RuntimeGraphManager.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.DotNet.ProjectModel.Compilation; +using Microsoft.DotNet.ProjectModel.Graph; +using NuGet.RuntimeModel; +using System.IO; +using Microsoft.Extensions.DependencyModel; + +namespace Microsoft.DotNet.ProjectModel +{ + public class RuntimeGraphManager + { + private const string RuntimeJsonFileName = "runtime.json"; + + public NuGet.RuntimeModel.RuntimeGraph Collect(IEnumerable exports) + { + var graph = RuntimeGraph.Empty; + foreach (var export in exports) + { + if (export.Library.Identity.Type == LibraryType.Package) + { + var runtimeJson = ((PackageDescription) export.Library).Library.Files.FirstOrDefault(f => f == RuntimeJsonFileName); + if (runtimeJson != null) + { + var runtimeJsonFullName = Path.Combine(export.Library.Path, runtimeJson); + graph = RuntimeGraph.Merge(graph, JsonRuntimeFormat.ReadRuntimeGraph(runtimeJsonFullName)); + } + } + } + return graph; + } + + public IEnumerable Expand(RuntimeGraph runtimeGraph, IEnumerable runtimes) + { + foreach (var runtime in runtimes) + { + var importers = FindImporters(runtimeGraph, runtime); + foreach (var importer in importers) + { + // ExpandRuntime return runtime itself as first item so we are skiping it + yield return new RuntimeFallbacks(importer, runtimeGraph.ExpandRuntime(importer).Skip(1)); + } + } + } + + private IEnumerable FindImporters(RuntimeGraph runtimeGraph, string runtime) + { + foreach (var runtimePair in runtimeGraph.Runtimes) + { + var expanded = runtimeGraph.ExpandRuntime(runtimePair.Key); + if (expanded.Contains(runtime)) + { + yield return runtimePair.Key; + } + } + } + } +} diff --git a/tools/RuntimeGraphGenerator/project.json b/tools/RuntimeGraphGenerator/project.json new file mode 100644 index 000000000..9719346de --- /dev/null +++ b/tools/RuntimeGraphGenerator/project.json @@ -0,0 +1,25 @@ +{ + "version": "1.0.0-*", + "compilationOptions": { + "emitEntryPoint": true + }, + "dependencies": { + "NuGet.RuntimeModel": "3.4.0-rtm-0763", + "NuGet.Versioning": "3.4.0-rtm-0763", + "System.CommandLine": "0.1.0-e160119-1", + "System.Runtime.Serialization.Json": "1.0.0-rc2-23911", + "Microsoft.DotNet.ProjectModel": "1.0.0-*", + "Microsoft.DotNet.Cli.Utils": "1.0.0-*", + "Microsoft.Extensions.PlatformAbstractions": "1.0.0-rc2-16537", + "Microsoft.NETCore.ConsoleHost": "1.0.0-rc2-23911", + "NETStandard.Library": "1.5.0-rc2-23911" + }, + "frameworks": { + "netstandardapp1.5": { + "imports": [ + "dnxcore50", + "portable-net45+wp80+win8+wpa81+dnxcore50" + ] + } + }, +}