diff --git a/src/installer/tests/HostActivation.Tests/DependencyResolution/DependencyResolutionCommandResultExtensions.cs b/src/installer/tests/HostActivation.Tests/DependencyResolution/DependencyResolutionCommandResultExtensions.cs index 1474adc3d58a4..8933efc79501f 100644 --- a/src/installer/tests/HostActivation.Tests/DependencyResolution/DependencyResolutionCommandResultExtensions.cs +++ b/src/installer/tests/HostActivation.Tests/DependencyResolution/DependencyResolutionCommandResultExtensions.cs @@ -162,6 +162,15 @@ public static AndConstraint HaveUsedAdditionalProbingPa .And.HaveStdErrContaining($"probe type=lookup dir=[{path}]"); } + public static AndConstraint HaveReadRidGraph(this CommandResultAssertions assertion, bool readRidGraph) + { + string ridGraphMsg = "RID fallback graph ="; + string hostRidsMsg = "Host RID list ="; + return readRidGraph + ? assertion.HaveStdErrContaining(ridGraphMsg).And.NotHaveStdErrContaining(hostRidsMsg) + : assertion.HaveStdErrContaining(hostRidsMsg).And.NotHaveStdErrContaining(ridGraphMsg); + } + public static AndConstraint HaveUsedFallbackRid(this CommandResultAssertions assertion, bool usedFallbackRid) { string msg = "Falling back to base HostRID"; @@ -209,7 +218,7 @@ private static string[] RelativePathsToAbsoluteAppPaths(string relativePaths, Te } List paths = new List(); - foreach (string relativePath in relativePaths.Split(';')) + foreach (string relativePath in relativePaths.Split(';', StringSplitOptions.RemoveEmptyEntries)) { string path = relativePath.Replace('/', Path.DirectorySeparatorChar); if (app != null) diff --git a/src/installer/tests/HostActivation.Tests/DependencyResolution/RidAssetResolution.cs b/src/installer/tests/HostActivation.Tests/DependencyResolution/RidAssetResolution.cs index 5eb0f597ba70d..58e7fc417a8d4 100644 --- a/src/installer/tests/HostActivation.Tests/DependencyResolution/RidAssetResolution.cs +++ b/src/installer/tests/HostActivation.Tests/DependencyResolution/RidAssetResolution.cs @@ -13,30 +13,95 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation.DependencyResolution { public abstract class RidAssetResolutionBase : ComponentDependencyResolutionBase { + private static Version ReadRidGraphDisabledVersion = new Version(8, 0); + public class TestSetup + { + // Explicit RID (environment variable) to set when running the test + // Value of null indicates unset (default) + public string? Rid { get; init; } + + // Represents the configuration (System.Host.Resolution.ReadRidGraph) for whether to read the RID graph + // Value of null indicates unset (default setting) + public bool? ReadRidGraph { get; init; } + + // Whether or not the root deps file has a RID graph + public bool HasRidGraph { get; init; } + + // Expected behaviour of the test based on above settings + public bool ShouldReadRidGraph => ReadRidGraph == true; + public bool ShouldUseFallbackRid => ShouldReadRidGraph && (Rid == UnknownRid || !HasRidGraph); + + public override string ToString() => $""" + {nameof(Rid)}: {(Rid ?? "")} + {nameof(ReadRidGraph)}: {(ReadRidGraph.HasValue ? ReadRidGraph : "")} + {nameof(HasRidGraph)}: {HasRidGraph} + [computed] + {nameof(ShouldReadRidGraph)}: {ShouldReadRidGraph} + {nameof(ShouldUseFallbackRid)}: {ShouldUseFallbackRid} + """; + }; + + public class ResolvedPaths + { + public string IncludedAssemblyPaths { get; init; } + public string ExcludedAssemblyPaths { get; init; } + public string IncludedNativeLibraryPaths { get; init; } + public string ExcludedNativeLibraryPaths { get; init; } + } + protected abstract void RunTest( Action assetsCustomizer, - string rid, - string includedAssemblyPaths, - string excludedAssemblyPaths, - string includedNativeLibraryPaths, - string excludedNativeLibraryPaths, - bool hasRuntimeFallbacks = true, + TestSetup setup, + ResolvedPaths expected, Action appCustomizer = null); + protected TestApp UpdateAppConfigForTest(TestApp app, TestSetup setup, bool copyOnUpdate) + { + if (!setup.ReadRidGraph.HasValue) + return app; + + if (copyOnUpdate) + app = app.Copy(); + + RuntimeConfig config = RuntimeConfig.FromFile(app.RuntimeConfigJson); + + if (setup.ReadRidGraph.HasValue) + config.WithProperty("System.Host.Resolution.ReadRidGraph", setup.ReadRidGraph.ToString()); + + config.Save(); + + return app; + } + protected const string UnknownRid = "unknown-rid"; private const string LinuxAssembly = "linux/LinuxAssembly.dll"; private const string MacOSAssembly = "osx/MacOSAssembly.dll"; private const string WindowsAssembly = "win/WindowsAssembly.dll"; - private void RidSpecificAssemblyImpl(string rid, string includedPath, string excludedPath, bool hasRuntimeFallbacks) + private static (string included, string excluded) GetExpectedPathsforCurrentRid(string linux, string macos, string windows) + { + if (OperatingSystem.IsLinux()) + return (linux, $"{macos};{windows}"); + + if (OperatingSystem.IsMacOS()) + return (macos, $"{linux};{windows}"); + + if (OperatingSystem.IsWindows()) + return (windows, $"{linux};{macos}"); + + return (null, $"{linux};{macos};{windows}"); + } + + private void RidSpecificAssemblyImpl(TestSetup setup, string includedPath, string excludedPath) { RunTest( p => p .WithAssemblyGroup("win", g => g.WithAsset(WindowsAssembly)) .WithAssemblyGroup("linux", g => g.WithAsset(LinuxAssembly)) .WithAssemblyGroup("osx", g => g.WithAsset(MacOSAssembly)), - rid, includedPath, excludedPath, null, null, hasRuntimeFallbacks); + setup, + new ResolvedPaths() { IncludedAssemblyPaths = includedPath, ExcludedAssemblyPaths = excludedPath }); } [Theory] @@ -44,60 +109,87 @@ private void RidSpecificAssemblyImpl(string rid, string includedPath, string exc [InlineData("win10-x64", WindowsAssembly, $"{LinuxAssembly};{MacOSAssembly}")] [InlineData("linux-x64", LinuxAssembly, $"{MacOSAssembly};{WindowsAssembly}")] [InlineData("osx-x64", MacOSAssembly, $"{LinuxAssembly};{WindowsAssembly}")] - public void RidSpecificAssembly(string rid, string includedPath, string excludedPath) + public void RidSpecificAssembly_RidGraph(string rid, string includedPath, string excludedPath) { - RidSpecificAssemblyImpl(rid, includedPath, excludedPath, hasRuntimeFallbacks: true); + RidSpecificAssemblyImpl( + new TestSetup() { Rid = rid, HasRidGraph = true, ReadRidGraph = true }, + includedPath, + excludedPath); } [Theory] // RID is computed at run-time - [InlineData(null, true)] - [InlineData(null, false)] - // RID is from a compile-time fallback - [InlineData(UnknownRid, true)] - [InlineData(UnknownRid, false)] - public void RidSpecificAssembly_CurrentRid(string rid, bool hasRuntimeFallbacks) + [InlineData(null, true, true)] + [InlineData(null, true, false)] + [InlineData(null, true, null)] + [InlineData(null, false, true)] + [InlineData(null, false, false)] + [InlineData(null, false, null)] + // RID is from a compile-time fallback when using the RID graph + [InlineData(UnknownRid, true, true)] + [InlineData(UnknownRid, true, false)] + [InlineData(UnknownRid, true, null)] + [InlineData(UnknownRid, false, true)] + [InlineData(UnknownRid, false, false)] + [InlineData(UnknownRid, false, null)] + public void RidSpecificAssembly_CurrentRid(string rid, bool hasRuntimeFallbacks, bool? readRidGraph) { - // Host relies on the fallback graph to resolve any RID-specific assets that don't exactly match - // the current RID, so everything remains excluded without the fallback graph currently + // When not using the RID graph, the host uses the target OS for which it was built to determine applicable + // RIDs that apply, so it can find both the arch-specific and the OS-only assets. + // When using the RID graph, the host uses it to resolve any RID-specific assets that don't exactly match + // the current RID, so the OS-only asset remains excluded if there are no fallbacks. string includedPath = null; string excludedPath = $"{LinuxAssembly};{MacOSAssembly};{WindowsAssembly}"; - if (hasRuntimeFallbacks) + if (readRidGraph != true || hasRuntimeFallbacks) { // Host should resolve to the RID corresponding to the platform on which it is running - if (OperatingSystem.IsLinux()) - { - includedPath = LinuxAssembly; - excludedPath = $"{MacOSAssembly};{WindowsAssembly}"; - } - else if (OperatingSystem.IsMacOS()) - { - includedPath = MacOSAssembly; - excludedPath = $"{LinuxAssembly};{WindowsAssembly}"; - } - else if (OperatingSystem.IsWindows()) - { - includedPath = WindowsAssembly; - excludedPath = $"{LinuxAssembly};{MacOSAssembly}"; - } - else - { - includedPath = null; - excludedPath = $"{LinuxAssembly};{MacOSAssembly};{WindowsAssembly}"; - } + (includedPath, excludedPath) = GetExpectedPathsforCurrentRid(LinuxAssembly, MacOSAssembly, WindowsAssembly); } - RidSpecificAssemblyImpl(rid, includedPath, excludedPath, hasRuntimeFallbacks); + RidSpecificAssemblyImpl( + new TestSetup() { Rid = rid, HasRidGraph = hasRuntimeFallbacks, ReadRidGraph = readRidGraph }, + includedPath, + excludedPath); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + [InlineData(null)] + public void RidSpecificAssembly_UnknownRid(bool? readRidGraph) + { + string assetPath = $"{UnknownRid}/{UnknownRid}Asset"; + + string includedPath; + string excludedPath; + if (readRidGraph == true) + { + // Host won't resolve the unknown RID asset + includedPath = null; + excludedPath = assetPath; + } + else + { + // Host should resolve to the specified RID + includedPath = assetPath; + excludedPath = null; + } + + RunTest( + p => p.WithAssemblyGroup(UnknownRid, g => g.WithAsset(assetPath)), + new TestSetup() { Rid = UnknownRid, ReadRidGraph = readRidGraph }, + new ResolvedPaths() { IncludedAssemblyPaths = includedPath, ExcludedAssemblyPaths = excludedPath }); } - private void RidSpecificNativeLibraryImpl(string rid, string includedPath, string excludedPath, bool hasRuntimeFallbacks) + private void RidSpecificNativeLibraryImpl(TestSetup setup, string includedPath, string excludedPath) { RunTest( p => p .WithNativeLibraryGroup("win", g => g.WithAsset("win/WindowsNativeLibrary.dll")) .WithNativeLibraryGroup("linux", g => g.WithAsset("linux/LinuxNativeLibrary.so")) .WithNativeLibraryGroup("osx", g => g.WithAsset("osx/MacOSNativeLibrary.dylib")), - rid, null, null, includedPath, excludedPath, hasRuntimeFallbacks); + setup, + new ResolvedPaths() { IncludedNativeLibraryPaths = includedPath, ExcludedNativeLibraryPaths = excludedPath }); } [Theory] @@ -105,62 +197,93 @@ private void RidSpecificNativeLibraryImpl(string rid, string includedPath, strin [InlineData("win10-x64", "win", "linux;osx")] [InlineData("linux-x64", "linux", "osx;win")] [InlineData("osx-x64", "osx", "linux;win")] - public void RidSpecificNativeLibrary(string rid, string includedPath, string excludedPath) + public void RidSpecificNativeLibrary_RidGraph(string rid, string includedPath, string excludedPath) { - RidSpecificNativeLibraryImpl(rid, includedPath, excludedPath, hasRuntimeFallbacks: true); + RidSpecificNativeLibraryImpl( + new TestSetup() { Rid = rid, HasRidGraph = true, ReadRidGraph = true }, + includedPath, excludedPath); } [Theory] // RID is computed at run-time - [InlineData(null, true)] - [InlineData(null, false)] - // RID is from a compile-time fallback - [InlineData(UnknownRid, true)] - [InlineData(UnknownRid, false)] - public void RidSpecificNativeLibrary_CurrentRid(string rid, bool hasRuntimeFallbacks) + [InlineData(null, true, true)] + [InlineData(null, true, false)] + [InlineData(null, true, null)] + [InlineData(null, false, true)] + [InlineData(null, false, false)] + [InlineData(null, false, null)] + // RID is from a compile-time fallback when using the RID graph + [InlineData(UnknownRid, true, true)] + [InlineData(UnknownRid, true, false)] + [InlineData(UnknownRid, true, null)] + [InlineData(UnknownRid, false, true)] + [InlineData(UnknownRid, false, false)] + [InlineData(UnknownRid, false, null)] + public void RidSpecificNativeLibrary_CurrentRid(string rid, bool hasRuntimeFallbacks, bool? readRidGraph) { - // Host relies on the fallback graph to resolve any RID-specific assets that don't exactly match - // the current RID, so everything remains excluded without the fallback graph currently + // When not using the RID graph, the host uses the target OS for which it was built to determine applicable + // RIDs that apply, so it can find both the arch-specific and the OS-only assets. + // When using the RID graph, the host uses it to resolve any RID-specific assets that don't exactly match + // the current RID, so the OS-only asset remains excluded if there are no fallbacks. string includedPath = null; string excludedPath = "linux;osx;win"; - if (hasRuntimeFallbacks) + if (readRidGraph != true || hasRuntimeFallbacks) { // Host should resolve to the RID corresponding to the platform on which it is running - if (OperatingSystem.IsLinux()) - { - includedPath = "linux"; - excludedPath = "osx;win"; - } - else if (OperatingSystem.IsMacOS()) - { - includedPath = "osx"; - excludedPath = "linux;win"; - } - else if (OperatingSystem.IsWindows()) - { - includedPath = "win"; - excludedPath = "linux;osx"; - } + (includedPath, excludedPath) = GetExpectedPathsforCurrentRid("linux/", "osx/", "win/"); } - RidSpecificNativeLibraryImpl(rid, includedPath, excludedPath, hasRuntimeFallbacks); + RidSpecificNativeLibraryImpl( + new TestSetup() { Rid = rid, HasRidGraph = hasRuntimeFallbacks, ReadRidGraph = readRidGraph }, + includedPath, + excludedPath); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + [InlineData(null)] + public void RidSpecificNativeLibrary_UnknownRid(bool? readRidGraph) + { + string assetPath = $"{UnknownRid}/{UnknownRid}Asset"; + + string includedPath; + string excludedPath; + if (readRidGraph == true) + { + // Host won't resolve the unknown RID asset + includedPath = null; + excludedPath = $"{UnknownRid}/"; + } + else + { + // Host should resolve to the specified RID + includedPath = $"{UnknownRid}/"; + excludedPath = null; + } + + RunTest( + p => p.WithNativeLibraryGroup(UnknownRid, g => g.WithAsset(assetPath)), + new TestSetup() { Rid = UnknownRid, ReadRidGraph = readRidGraph }, + new ResolvedPaths() { IncludedNativeLibraryPaths = includedPath, ExcludedNativeLibraryPaths = excludedPath }); } [Theory] [InlineData("win10-x64", "win-x64/ManagedWin64.dll")] [InlineData("win10-x86", "win/ManagedWin.dll")] [InlineData("linux-x64", "any/ManagedAny.dll")] - public void MostSpecificRidAssemblySelected(string rid, string expectedPath, bool hasRuntimeFallbacks = true) + public void MostSpecificRidAssemblySelected_RidGraph(string rid, string expectedPath) { RunTest( p => p .WithAssemblyGroup("any", g => g.WithAsset("any/ManagedAny.dll")) .WithAssemblyGroup("win", g => g.WithAsset("win/ManagedWin.dll")) .WithAssemblyGroup("win-x64", g => g.WithAsset("win-x64/ManagedWin64.dll")), - rid, expectedPath, null, null, null); + new TestSetup() { Rid = rid, HasRidGraph = true, ReadRidGraph = true }, + new ResolvedPaths() { IncludedAssemblyPaths = expectedPath }); } - + // The build RID from the test context should match the build RID of the host under test private static string CurrentRid = RepoDirectoriesProvider.Default.BuildRID; private static string CurrentRidAsset = $"{CurrentRid}/{CurrentRid}Asset.dll"; @@ -173,17 +296,27 @@ public void MostSpecificRidAssemblySelected(string rid, string expectedPath, boo private static string DifferentArchAsset = $"{DifferentArch}/{DifferentArch}Asset.dll"; [Theory] - [InlineData(true, true)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(false, false)] - public void MostSpecificRidAssemblySelected_ComputedRid(bool includeCurrentArch, bool hasRuntimeFallbacks) + [InlineData(true, true, true)] + [InlineData(true, true, false)] + [InlineData(true, true, null)] + [InlineData(true, false, true)] + [InlineData(true, false, false)] + [InlineData(true, false, null)] + [InlineData(false, true, true)] + [InlineData(false, true, false)] + [InlineData(false, true, null)] + [InlineData(false, false, true)] + [InlineData(false, false, false)] + [InlineData(false, false, null)] + public void MostSpecificRidAssemblySelected(bool includeCurrentArch, bool hasRuntimeFallbacks, bool? readRidGraph) { - // Host relies on the fallback graph to resolve any RID-specific assets that don't exactly match - // the current RID, so the OS-only asset remains excluded without the fallback graph currently + // When not using the RID graph, the host uses the target OS for which it was built to determine applicable + // RIDs that apply, so it can find both the arch-specific and the OS-only assets. + // When using the RID graph, the host uses it to resolve any RID-specific assets that don't exactly match + // the current RID, so the OS-only asset remains excluded if there are no fallbacks. string includedPath = null; string excludedPath = $"{CurrentOSAsset};{DifferentArchAsset}"; - if (hasRuntimeFallbacks) + if (readRidGraph != true || hasRuntimeFallbacks) { if (includeCurrentArch) { @@ -199,41 +332,54 @@ public void MostSpecificRidAssemblySelected_ComputedRid(bool includeCurrentArch, RunTest(p => { p.WithAssemblyGroup(CurrentOS, g => g.WithAsset(CurrentOSAsset)); + p.WithAssemblyGroup(DifferentArch, g => g.WithAsset(DifferentArchAsset)); if (includeCurrentArch) { p.WithAssemblyGroup(CurrentRid, g => g.WithAsset(CurrentRidAsset)); } }, - rid: null, // RID is computed at run-time - includedPath, excludedPath, null, null, hasRuntimeFallbacks); + // RID is computed at run-time + new TestSetup() { Rid = null, HasRidGraph = hasRuntimeFallbacks, ReadRidGraph = readRidGraph }, + new ResolvedPaths() { IncludedAssemblyPaths = includedPath, ExcludedAssemblyPaths = excludedPath }); } [Theory] [InlineData("win10-x64", "win-x64")] [InlineData("win10-x86", "win")] [InlineData("linux-x64", "any")] - public void MostSpecificRidNativeLibrarySelected(string rid, string expectedPath) + public void MostSpecificRidNativeLibrarySelected_RidGraph(string rid, string expectedPath) { RunTest( p => p .WithNativeLibraryGroup("any", g => g.WithAsset("any/NativeAny.dll")) .WithNativeLibraryGroup("win", g => g.WithAsset("win/NativeWin.dll")) .WithNativeLibraryGroup("win-x64", g => g.WithAsset("win-x64/NativeWin64.dll")), - rid, null, null, expectedPath, null); + new TestSetup() { Rid = rid, HasRidGraph = true, ReadRidGraph = true }, + new ResolvedPaths() { IncludedNativeLibraryPaths = expectedPath }); } [Theory] - [InlineData(true, true)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(false, false)] - public void MostSpecificRidNativeLibrarySelected_ComputedRid(bool includeCurrentArch, bool hasRuntimeFallbacks) + [InlineData(true, true, true)] + [InlineData(true, true, false)] + [InlineData(true, true, null)] + [InlineData(true, false, true)] + [InlineData(true, false, false)] + [InlineData(true, false, null)] + [InlineData(false, true, true)] + [InlineData(false, true, false)] + [InlineData(false, true, null)] + [InlineData(false, false, true)] + [InlineData(false, false, false)] + [InlineData(false, false, null)] + public void MostSpecificRidNativeLibrarySelected(bool includeCurrentArch, bool hasRuntimeFallbacks, bool? readRidGraph) { - // Host relies on the fallback graph to resolve any RID-specific assets that don't exactly match - // the current RID, so the OS-only asset remains excluded without the fallback graph currently + // When not using the RID graph, the host uses the target OS for which it was built to determine applicable + // RIDs that apply, so it can find both the arch-specific and the OS-only assets. + // When using the RID graph, the host uses it to resolve any RID-specific assets that don't exactly match + // the current RID, so the OS-only asset remains excluded if there are no fallbacks. string includedPath = null; string excludedPath = $"{CurrentOS}/;{DifferentArch}/"; - if (hasRuntimeFallbacks) + if (readRidGraph != true || hasRuntimeFallbacks) { if (includeCurrentArch) { @@ -249,20 +395,22 @@ public void MostSpecificRidNativeLibrarySelected_ComputedRid(bool includeCurrent RunTest(p => { p.WithNativeLibraryGroup(CurrentOS, g => g.WithAsset(CurrentOSAsset)); + p.WithNativeLibraryGroup(DifferentArch, g => g.WithAsset(DifferentArchAsset)); if (includeCurrentArch) { p.WithNativeLibraryGroup(CurrentRid, g => g.WithAsset(CurrentRidAsset)); } }, - rid: null, // RID is computed at run-time - null, null, includedPath, excludedPath, hasRuntimeFallbacks); + // RID is computed at run-time + new TestSetup() { Rid = null, HasRidGraph = hasRuntimeFallbacks, ReadRidGraph = readRidGraph }, + new ResolvedPaths() { IncludedNativeLibraryPaths = includedPath, ExcludedNativeLibraryPaths = excludedPath }); } [Theory] [InlineData("win10-x64", "win/ManagedWin.dll", "native/win-x64")] [InlineData("win10-x86", "win/ManagedWin.dll", "native/win-x86")] [InlineData("linux-x64", "any/ManagedAny.dll", "native/linux")] - public void MostSpecificRidAssemblySelectedPerType(string rid, string expectedAssemblyPath, string expectedNativePath) + public void MostSpecificRidAssemblySelectedPerType_RidGraph(string rid, string expectedAssemblyPath, string expectedNativePath) { RunTest( p => p @@ -271,7 +419,47 @@ public void MostSpecificRidAssemblySelectedPerType(string rid, string expectedAs .WithNativeLibraryGroup("win-x64", g => g.WithAsset("native/win-x64/n.dll")) .WithNativeLibraryGroup("win-x86", g => g.WithAsset("native/win-x86/n.dll")) .WithNativeLibraryGroup("linux", g => g.WithAsset("native/linux/n.so")), - rid, expectedAssemblyPath, null, expectedNativePath, null); + new TestSetup() { Rid = rid, HasRidGraph = true, ReadRidGraph = true }, + new ResolvedPaths() { IncludedAssemblyPaths = expectedAssemblyPath, IncludedNativeLibraryPaths = expectedNativePath }); + } + + [Theory] + [InlineData(true, true)] + [InlineData(true, false)] + [InlineData(true, null)] + [InlineData(false, true)] + [InlineData(false, false)] + [InlineData(false, null)] + public void MostSpecificRidAssemblySelectedPerType(bool hasRuntimeFallbacks, bool? readRidGraph) + { + // When not using the RID graph, the host uses the target OS for which it was built to determine applicable + // RIDs that apply, so it can find both the arch-specific and the OS-only assets. + // When using the RID graph, the host uses it to resolve any RID-specific assets that don't exactly match + // the current RID, so the OS-only asset remains excluded if there are no fallbacks. + string includedAssemblyPath = null; + string excludedAssemblyPath = CurrentOSAsset; + string includedNativePath = null; + string excludedNativePath = $"native/{CurrentOS}/;native/{CurrentRidAsset}/"; + if (readRidGraph != true || hasRuntimeFallbacks) + { + includedAssemblyPath = CurrentOSAsset; + excludedAssemblyPath = null; + includedNativePath = $"native/{CurrentRid}/"; + excludedNativePath = $"native/{CurrentOS}/"; + } + + RunTest( + p => p + .WithAssemblyGroup(CurrentOS, g => g.WithAsset(CurrentOSAsset)) + .WithNativeLibraryGroup(CurrentOS, g => g.WithAsset($"native/{CurrentOSAsset}")) + .WithNativeLibraryGroup(CurrentRid, g => g.WithAsset($"native/{CurrentRidAsset}")), + // RID is computed at run-time + new TestSetup() { Rid = null, HasRidGraph = hasRuntimeFallbacks, ReadRidGraph = readRidGraph }, + new ResolvedPaths() + { + IncludedAssemblyPaths = includedAssemblyPath, ExcludedAssemblyPaths = excludedAssemblyPath, + IncludedNativeLibraryPaths = includedNativePath, ExcludedNativeLibraryPaths = excludedNativePath + }); } [Theory] @@ -281,8 +469,8 @@ public void MostSpecificRidAssemblySelectedPerType(string rid, string expectedAs [InlineData("win10-x86", "win/ManagedWin.dll;win/AnotherWin.dll", "native/win-x86")] // For "linux" on the other hand the DependencyLib will be resolved because there are // no RID-specific assembly assets available. - [InlineData("linux-x64", "", "native/linux")] - public void MostSpecificRidAssemblySelectedPerTypeMultipleAssets(string rid, string expectedAssemblyPath, string expectedNativePath) + [InlineData("linux-x64", "DependencyLib.dll", "native/linux")] + public void MostSpecificRidAssemblySelectedPerTypeMultipleAssets_RidGraph(string rid, string expectedAssemblyPath, string expectedNativePath) { RunTest( assetsCustomizer: null, @@ -299,11 +487,69 @@ public void MostSpecificRidAssemblySelectedPerTypeMultipleAssets(string rid, str .WithNativeLibraryGroup("linux", g => g.WithAsset("native/linux/n.so"))) .WithPackage("ridAgnosticLib", "2.0.0", p => p .WithAssemblyGroup(null, g => g.WithAsset("PortableLib.dll").WithAsset("PortableLib2.dll"))), - rid: rid, - // The PortableLib an PortableLib2 are from a separate package which has no RID specific assets, + setup: new TestSetup() { Rid = rid, HasRidGraph = true, ReadRidGraph = true }, + // PortableLib and PortableLib2 are from a separate package which has no RID specific assets, // so the RID-agnostic assets are always included - includedAssemblyPaths: expectedAssemblyPath + ";PortableLib.dll;PortableLib2.dll", excludedAssemblyPaths: null, - includedNativeLibraryPaths: expectedNativePath, excludedNativeLibraryPaths: null); + expected: new ResolvedPaths() + { + IncludedAssemblyPaths = expectedAssemblyPath + ";PortableLib.dll;PortableLib2.dll", + IncludedNativeLibraryPaths = expectedNativePath + }); + } + + [Theory] + [InlineData(true, true)] + [InlineData(true, false)] + [InlineData(true, null)] + [InlineData(false, true)] + [InlineData(false, false)] + [InlineData(false, null)] + public void MostSpecificRidAssemblySelectedPerTypeMultipleAssets(bool hasRuntimeFallbacks, bool? readRidGraph) + { + // When not using the RID graph, the host uses the target OS for which it was built to determine applicable + // RIDs that apply, so it can find both the arch-specific and the OS-only assets. + // When using the RID graph, the host uses it to resolve any RID-specific assets that don't exactly match + // the current RID, so the OS-only asset remains excluded if there are no fallbacks. + string suffix = ".2"; + string includedAssemblyPath = "ridSpecificLib.dll"; + string excludedAssemblyPath = $"{CurrentOSAsset};{CurrentOS}/{CurrentOS}Asset{suffix}.dll"; + string includedNativePath = null; + if (readRidGraph != true || hasRuntimeFallbacks) + { + includedAssemblyPath = $"{CurrentOSAsset};{CurrentOS}/{CurrentOS}Asset{suffix}.dll"; + excludedAssemblyPath = "ridSpecificLib.dll"; + includedNativePath = $"native/{CurrentRid}/;native/{CurrentRid}{suffix}/;native{suffix}/{CurrentRid}/"; + } + + RunTest( + assetsCustomizer: null, + appCustomizer: b => b + .WithPackage("ridSpecificLib", "1.0.0", p => p + .WithAssemblyGroup(null, g => g.WithAsset("ridSpecificLib.dll")) + .WithAssemblyGroup(CurrentOS, g => g.WithAsset(CurrentOSAsset)) + .WithAssemblyGroup(CurrentOS, g => g.WithAsset($"{CurrentOS}/{CurrentOS}Asset{suffix}.dll")) + .WithNativeLibraryGroup(CurrentOS, g => g.WithAsset($"native/{CurrentOSAsset}")) + .WithNativeLibraryGroup(CurrentRid, g => g.WithAsset($"native/{CurrentRidAsset}")) + .WithNativeLibraryGroup(CurrentRid, g => g.WithAsset($"native/{CurrentRid}{suffix}/Asset")) + .WithNativeLibraryGroup(CurrentRid, g => g.WithAsset($"native{suffix}/{CurrentRidAsset}"))) + .WithPackage("noRidMatch", "1.0.0", p => p + .WithAssemblyGroup(null, g => g.WithAsset("noRidMatch.dll")) + .WithAssemblyGroup(DifferentArch, g => g.WithAsset(DifferentArchAsset)) + .WithNativeLibraryGroup(null, g => g.WithAsset($"noRidMatch")) + .WithNativeLibraryGroup(DifferentArch, g => g.WithAsset($"native/{DifferentArchAsset}"))) + .WithPackage("ridAgnosticLib", "1.0.0", p => p + .WithAssemblyGroup(null, g => g.WithAsset("PortableLib.dll").WithAsset("PortableLib2.dll"))), + setup: new TestSetup() { Rid = null, HasRidGraph = hasRuntimeFallbacks, ReadRidGraph = readRidGraph }, + // PortableLib and PortableLib2 are from a separate package which has no RID specific assets, + // so the RID-agnostic assets are always included. noRidMatch is from a package where none of + // RID-specific assets match, so the RID-agnostic asset is included. + expected: new ResolvedPaths() + { + IncludedAssemblyPaths = $"{includedAssemblyPath};noRidMatch.dll;PortableLib.dll;PortableLib2.dll", + ExcludedAssemblyPaths = $"{excludedAssemblyPath};{DifferentArchAsset}", + IncludedNativeLibraryPaths = $"{includedNativePath};/", + ExcludedNativeLibraryPaths = $"native/{CurrentOS}/;native/{DifferentArch}/" + }); } protected static void UseFallbacksFromBuiltDotNet(NetCoreAppBuilder builder) @@ -355,12 +601,8 @@ public PortableAppRidAssetResolution(AppSharedTestState sharedState) protected override void RunTest( Action assetsCustomizer, - string rid, - string includedAssemblyPaths, - string excludedAssemblyPaths, - string includedNativeLibraryPaths, - string excludedNativeLibraryPaths, - bool hasRuntimeFallbacks, + TestSetup setup, + ResolvedPaths expected, Action appCustomizer) { using (TestApp app = NetCoreAppBuilder.PortableForNETCoreApp(SharedState.FrameworkReferenceApp) @@ -369,26 +611,29 @@ protected override void RunTest( .Build()) { DotNetCli dotnet; - if (hasRuntimeFallbacks) + if (setup.HasRidGraph) { // Use the fallbacks from the product when testing the computed RID - dotnet = rid == null ? SharedState.DotNetWithNetCoreApp_RuntimeFallbacks : SharedState.DotNetWithNetCoreApp; + dotnet = setup.Rid == null ? SharedState.DotNetWithNetCoreApp_RuntimeFallbacks : SharedState.DotNetWithNetCoreApp; } else { dotnet = SharedState.DotNetWithNetCoreApp_NoRuntimeFallbacks; } + UpdateAppConfigForTest(app, setup, copyOnUpdate: false); + dotnet.Exec(app.AppDll) .EnableTracingAndCaptureOutputs() - .RuntimeId(rid) + .RuntimeId(setup.Rid) .Execute() .Should().Pass() - .And.HaveResolvedAssembly(includedAssemblyPaths, app) - .And.NotHaveResolvedAssembly(excludedAssemblyPaths, app) - .And.HaveResolvedNativeLibraryPath(includedNativeLibraryPaths, app) - .And.NotHaveResolvedNativeLibraryPath(excludedNativeLibraryPaths, app) - .And.HaveUsedFallbackRid(rid == UnknownRid || !hasRuntimeFallbacks) + .And.HaveResolvedAssembly(expected.IncludedAssemblyPaths, app) + .And.NotHaveResolvedAssembly(expected.ExcludedAssemblyPaths, app) + .And.HaveResolvedNativeLibraryPath(expected.IncludedNativeLibraryPaths, app) + .And.NotHaveResolvedNativeLibraryPath(expected.ExcludedNativeLibraryPaths, app) + .And.HaveReadRidGraph(setup.ShouldReadRidGraph) + .And.HaveUsedFallbackRid(setup.ShouldUseFallbackRid) .And.HaveUsedFrameworkProbe(dotnet.GreatestVersionSharedFxPath, level: 1); } } @@ -408,12 +653,8 @@ public PortableComponentOnFrameworkDependentAppRidAssetResolution(AppSharedTestS protected override void RunTest( Action assetsCustomizer, - string rid, - string includedAssemblyPaths, - string excludedAssemblyPaths, - string includedNativeLibraryPaths, - string excludedNativeLibraryPaths, - bool hasRuntimeFallbacks, + TestSetup setup, + ResolvedPaths expected, Action appCustomizer) { var component = SharedState.CreateComponentWithNoDependencies(b => b @@ -421,25 +662,28 @@ protected override void RunTest( .WithCustomizer(appCustomizer)); DotNetCli dotnet; - if (hasRuntimeFallbacks) + if (setup.HasRidGraph) { // Use the fallbacks from the product when testing the computed RID - dotnet = rid == null ? SharedState.DotNetWithNetCoreApp_RuntimeFallbacks : SharedState.DotNetWithNetCoreApp; + dotnet = setup.Rid == null ? SharedState.DotNetWithNetCoreApp_RuntimeFallbacks : SharedState.DotNetWithNetCoreApp; } else { dotnet = SharedState.DotNetWithNetCoreApp_NoRuntimeFallbacks; } - SharedState.RunComponentResolutionTest(component.AppDll, SharedState.FrameworkReferenceApp, dotnet.GreatestVersionHostFxrPath, command => command - .RuntimeId(rid)) + TestApp app = UpdateAppConfigForTest(SharedState.FrameworkReferenceApp, setup, copyOnUpdate: true); + + SharedState.RunComponentResolutionTest(component.AppDll, app, dotnet.GreatestVersionHostFxrPath, command => command + .RuntimeId(setup.Rid)) .Should().Pass() .And.HaveSuccessfullyResolvedComponentDependencies() - .And.HaveResolvedComponentDependencyAssembly(includedAssemblyPaths, component) - .And.NotHaveResolvedComponentDependencyAssembly(excludedAssemblyPaths, component) - .And.HaveResolvedComponentDependencyNativeLibraryPath(includedNativeLibraryPaths, component) - .And.NotHaveResolvedComponentDependencyNativeLibraryPath(excludedNativeLibraryPaths, component) - .And.HaveUsedFallbackRid(rid == UnknownRid || !hasRuntimeFallbacks) + .And.HaveResolvedComponentDependencyAssembly(expected.IncludedAssemblyPaths, component) + .And.NotHaveResolvedComponentDependencyAssembly(expected.ExcludedAssemblyPaths, component) + .And.HaveResolvedComponentDependencyNativeLibraryPath(expected.IncludedNativeLibraryPaths, component) + .And.NotHaveResolvedComponentDependencyNativeLibraryPath(expected.ExcludedNativeLibraryPaths, component) + .And.HaveReadRidGraph(setup.ShouldReadRidGraph) + .And.HaveUsedFallbackRid(setup.ShouldUseFallbackRid) .And.NotHaveUsedFrameworkProbe(dotnet.GreatestVersionSharedFxPath); } } @@ -458,12 +702,8 @@ public PortableComponentOnSelfContainedAppRidAssetResolution(ComponentSharedTest protected override void RunTest( Action assetsCustomizer, - string rid, - string includedAssemblyPaths, - string excludedAssemblyPaths, - string includedNativeLibraryPaths, - string excludedNativeLibraryPaths, - bool hasRuntimeFallbacks, + TestSetup setup, + ResolvedPaths expected, Action appCustomizer) { var component = SharedState.CreateComponentWithNoDependencies(b => b @@ -471,25 +711,28 @@ protected override void RunTest( .WithCustomizer(appCustomizer)); TestApp app; - if (hasRuntimeFallbacks) + if (setup.HasRidGraph) { // Use the fallbacks from the product when testing the computed RID - app = rid == null ? SharedState.HostApp_RuntimeFallbacks : SharedState.HostApp; + app = setup.Rid == null ? SharedState.HostApp_RuntimeFallbacks : SharedState.HostApp; } else { app = SharedState.HostApp_NoRuntimeFallbacks; } + app = UpdateAppConfigForTest(app, setup, copyOnUpdate: true); + SharedState.RunComponentResolutionTest(component.AppDll, app, app.Location, command => command - .RuntimeId(rid)) + .RuntimeId(setup.Rid)) .Should().Pass() .And.HaveSuccessfullyResolvedComponentDependencies() - .And.HaveResolvedComponentDependencyAssembly(includedAssemblyPaths, component) - .And.NotHaveResolvedComponentDependencyAssembly(excludedAssemblyPaths, component) - .And.HaveResolvedComponentDependencyNativeLibraryPath(includedNativeLibraryPaths, component) - .And.NotHaveResolvedComponentDependencyNativeLibraryPath(excludedNativeLibraryPaths, component) - .And.HaveUsedFallbackRid(rid == UnknownRid || !hasRuntimeFallbacks); + .And.HaveResolvedComponentDependencyAssembly(expected.IncludedAssemblyPaths, component) + .And.NotHaveResolvedComponentDependencyAssembly(expected.ExcludedAssemblyPaths, component) + .And.HaveResolvedComponentDependencyNativeLibraryPath(expected.IncludedNativeLibraryPaths, component) + .And.NotHaveResolvedComponentDependencyNativeLibraryPath(expected.ExcludedNativeLibraryPaths, component) + .And.HaveReadRidGraph(setup.ShouldReadRidGraph) + .And.HaveUsedFallbackRid(setup.ShouldUseFallbackRid); } public class ComponentSharedTestState : ComponentSharedTestStateBase diff --git a/src/installer/tests/TestUtils/RuntimeConfig.cs b/src/installer/tests/TestUtils/RuntimeConfig.cs index 29279e47cdf79..8a456bd8b08c5 100644 --- a/src/installer/tests/TestUtils/RuntimeConfig.cs +++ b/src/installer/tests/TestUtils/RuntimeConfig.cs @@ -145,7 +145,7 @@ public static RuntimeConfig FromFile(string path) { foreach (var includedFramework in includedFrameworks) { - runtimeConfig.WithFramework(Framework.FromJson((JsonObject)includedFramework)); + runtimeConfig.WithIncludedFramework(Framework.FromJson((JsonObject)includedFramework)); } } diff --git a/src/native/corehost/hostmisc/utils.cpp b/src/native/corehost/hostmisc/utils.cpp index c49d8535c0412..81245a1d85b45 100644 --- a/src/native/corehost/hostmisc/utils.cpp +++ b/src/native/corehost/hostmisc/utils.cpp @@ -245,13 +245,14 @@ const pal::char_t* get_arch_name(pal::architecture arch) const pal::char_t* get_current_arch_name() { - return get_arch_name(get_current_arch()); + assert(pal::strcmp(get_arch_name(get_current_arch()), _STRINGIFY(CURRENT_ARCH_NAME)) == 0); + return _STRINGIFY(CURRENT_ARCH_NAME); } pal::string_t get_current_runtime_id(bool use_fallback) { pal::string_t rid; - if (pal::getenv(_X("DOTNET_RUNTIME_ID"), &rid)) + if (try_get_runtime_id_from_env(rid)) return rid; rid = pal::get_current_os_rid_platform(); @@ -267,6 +268,11 @@ pal::string_t get_current_runtime_id(bool use_fallback) return rid; } +bool try_get_runtime_id_from_env(pal::string_t& out_rid) +{ + return pal::getenv(_X("DOTNET_RUNTIME_ID"), &out_rid); +} + /** * Multilevel Lookup is enabled by default * It can be disabled by setting DOTNET_MULTILEVEL_LOOKUP env var to a value that is not 1 diff --git a/src/native/corehost/hostmisc/utils.h b/src/native/corehost/hostmisc/utils.h index b890265029aa4..564bffa235d1b 100644 --- a/src/native/corehost/hostmisc/utils.h +++ b/src/native/corehost/hostmisc/utils.h @@ -89,6 +89,8 @@ const pal::char_t* get_arch_name(pal::architecture arch); const pal::char_t* get_current_arch_name(); pal::string_t get_current_runtime_id(bool use_fallback); +bool try_get_runtime_id_from_env(pal::string_t& out_rid); + bool multilevel_lookup_enabled(); void get_framework_and_sdk_locations(const pal::string_t& dotnet_dir, const bool disable_multilevel_lookup, std::vector* locations); bool get_file_path_from_env(const pal::char_t* env_key, pal::string_t* recv); diff --git a/src/native/corehost/hostpolicy/deps_format.cpp b/src/native/corehost/hostpolicy/deps_format.cpp index 739cd04bfe1e7..78c368f4c4390 100644 --- a/src/native/corehost/hostpolicy/deps_format.cpp +++ b/src/native/corehost/hostpolicy/deps_format.cpp @@ -11,6 +11,7 @@ #include #include #include +#include const std::array deps_entry_t::s_known_asset_types = {{ _X("runtime"), _X("resources"), _X("native") @@ -59,7 +60,7 @@ namespace if (trace::is_enabled()) { - trace::verbose(_X("The rid fallback graph is: {")); + trace::verbose(_X("RID fallback graph = {")); for (const auto& rid : rid_fallback_graph) { trace::verbose(_X("%s => ["), rid.first.c_str()); @@ -101,12 +102,11 @@ deps_json_t::rid_fallback_graph_t deps_json_t::get_rid_fallback_graph(const pal: } void deps_json_t::reconcile_libraries_with_targets( - const pal::string_t& deps_path, const json_parser_t::value_t& json, const std::function& library_has_assets_fn, const std::function& get_assets_fn) { - pal::string_t deps_file = get_filename(deps_path); + pal::string_t deps_file = get_filename(m_deps_file); for (const auto& library : json[_X("libraries")].GetObject()) { @@ -180,31 +180,154 @@ void deps_json_t::reconcile_libraries_with_targets( } } -// Returns the RID determined (computed or fallback) for the platform the host is running on. -pal::string_t deps_json_t::get_current_rid(const rid_fallback_graph_t& rid_fallback_graph) +namespace { - pal::string_t currentRid = get_current_runtime_id(false /*use_fallback*/); + #define CURRENT_ARCH_SUFFIX _X("-") _STRINGIFY(CURRENT_ARCH_NAME) + #define RID_CURRENT_ARCH_LIST(os) \ + _X(os) CURRENT_ARCH_SUFFIX, \ + _X(os), + + const pal::char_t* s_host_rids[] = + { +#if defined(TARGET_WINDOWS) + RID_CURRENT_ARCH_LIST("win") +#elif defined(TARGET_OSX) + RID_CURRENT_ARCH_LIST("osx") + RID_CURRENT_ARCH_LIST("unix") +#elif defined(TARGET_ANDROID) + RID_CURRENT_ARCH_LIST("linux-bionic") + RID_CURRENT_ARCH_LIST("linux") + RID_CURRENT_ARCH_LIST("unix") +#else + // Covers non-portable RIDs + RID_CURRENT_ARCH_LIST(FALLBACK_HOST_OS) +#if defined(TARGET_LINUX_MUSL) + RID_CURRENT_ARCH_LIST("linux-musl") + RID_CURRENT_ARCH_LIST("linux") +#elif !defined(FALLBACK_OS_IS_SAME_AS_TARGET_OS) + // Covers "linux" and non-linux like "freebsd", "illumos" + RID_CURRENT_ARCH_LIST(CURRENT_OS_NAME) +#endif + RID_CURRENT_ARCH_LIST("unix") +#endif + _X("any"), + }; + + // Returns the RID determined (computed or fallback) for the platform the host is running on. + pal::string_t get_current_rid(const deps_json_t::rid_fallback_graph_t* rid_fallback_graph) + { + pal::string_t currentRid = get_current_runtime_id(false /*use_fallback*/); + + trace::info(_X("HostRID is %s"), currentRid.empty() ? _X("not available") : currentRid.c_str()); - trace::info(_X("HostRID is %s"), currentRid.empty()? _X("not available"): currentRid.c_str()); + // If the current RID is not present in the RID fallback graph, then the platform + // is unknown to us. At this point, we will fallback to using the base RIDs and attempt + // asset lookup using them. + // + // We do the same even when the RID is empty. + if (currentRid.empty() || (rid_fallback_graph != nullptr && rid_fallback_graph->count(currentRid) == 0)) + { + currentRid = pal::get_current_os_fallback_rid() + pal::string_t(_X("-")) + get_current_arch_name(); + + trace::info(_X("Falling back to base HostRID: %s"), currentRid.c_str()); + } + + return currentRid; + } + + void print_host_rid_list() + { + if (trace::is_enabled()) + { + trace::verbose(_X("Host RID list = [")); + pal::string_t env_rid; + if (try_get_runtime_id_from_env(env_rid)) + trace::verbose(_X(" %s,"), env_rid.c_str()); + + for (const pal::char_t* rid : s_host_rids) + { + trace::verbose(_X(" %s,"), rid); + } + trace::verbose(_X("]")); + } + } - // If the current RID is not present in the RID fallback graph, then the platform - // is unknown to us. At this point, we will fallback to using the base RIDs and attempt - // asset lookup using them. - // - // We do the same even when the RID is empty. - if (currentRid.empty() || (rid_fallback_graph.count(currentRid) == 0)) + bool try_get_matching_rid(const std::unordered_map>& rid_assets, pal::string_t& out_rid) { - currentRid = pal::get_current_os_fallback_rid() + pal::string_t(_X("-")) + get_current_arch_name(); + // Check for match with environment variable RID value + pal::string_t env_rid; + if (try_get_runtime_id_from_env(env_rid)) + { + if (rid_assets.count(env_rid) != 0) + { + out_rid = env_rid; + return true; + } + } - trace::info(_X("Falling back to base HostRID: %s"), currentRid.c_str()); + // Use our list of known portable RIDs + for (const pal::char_t* rid : s_host_rids) + { + const auto& iter = std::find_if(rid_assets.cbegin(), rid_assets.cend(), + [&](const std::pair>& rid_asset) + { + return pal::strcmp(rid_asset.first.c_str(), rid) == 0; + }); + if (iter != rid_assets.cend()) + { + out_rid = rid; + return true; + } + } + + return false; } - return currentRid; + bool try_get_matching_rid_with_fallback_graph(const std::unordered_map>& rid_assets, const pal::string_t& host_rid, const deps_json_t::rid_fallback_graph_t& rid_fallback_graph, pal::string_t& out_rid) + { + // Check for exact match with the host RID + if (rid_assets.count(host_rid) != 0) + { + out_rid = host_rid; + return true; + } + + // Check if the RID exists in the fallback graph + auto rid_fallback_iter = rid_fallback_graph.find(host_rid); + if (rid_fallback_iter == rid_fallback_graph.end()) + { + trace::warning(_X("The targeted framework does not support the runtime '%s'. Some libraries may fail to load on this platform."), host_rid.c_str()); + return false; + } + + // Find the first RID fallback that has assets + const auto& fallback_rids = rid_fallback_iter->second; + auto iter = std::find_if(fallback_rids.begin(), fallback_rids.end(), [&rid_assets](const pal::string_t& rid) { + return rid_assets.count(rid); + }); + if (iter != fallback_rids.end()) + { + out_rid.assign(*iter); + return true; + } + + return false; + } } -void deps_json_t::perform_rid_fallback(rid_specific_assets_t* portable_assets, const rid_fallback_graph_t& rid_fallback_graph) +void deps_json_t::perform_rid_fallback(rid_specific_assets_t* portable_assets) { - pal::string_t host_rid = get_current_rid(rid_fallback_graph); + assert(!m_rid_resolution_options.use_fallback_graph || m_rid_resolution_options.rid_fallback_graph != nullptr); + + pal::string_t host_rid; + if (m_rid_resolution_options.use_fallback_graph) + { + host_rid = get_current_rid(m_rid_resolution_options.rid_fallback_graph); + } + else + { + print_host_rid_list(); + } for (auto& package : portable_assets->libs) { @@ -215,28 +338,11 @@ void deps_json_t::perform_rid_fallback(rid_specific_assets_t* portable_assets, c if (rid_assets.empty()) continue; - pal::string_t matched_rid = rid_assets.count(host_rid) ? host_rid : _X(""); - if (matched_rid.empty()) - { - auto rid_fallback_iter = rid_fallback_graph.find(host_rid); - if (rid_fallback_iter == rid_fallback_graph.end()) - { - trace::warning(_X("The targeted framework does not support the runtime '%s'. Some native libraries from [%s] may fail to load on this platform."), host_rid.c_str(), package.first.c_str()); - } - else - { - const auto& fallback_rids = rid_fallback_iter->second; - auto iter = std::find_if(fallback_rids.begin(), fallback_rids.end(), [&rid_assets](const pal::string_t& rid) { - return rid_assets.count(rid); - }); - if (iter != fallback_rids.end()) - { - matched_rid = *iter; - } - } - } - - if (matched_rid.empty()) + pal::string_t matched_rid; + bool found_match = m_rid_resolution_options.use_fallback_graph + ? try_get_matching_rid_with_fallback_graph(rid_assets, host_rid, *m_rid_resolution_options.rid_fallback_graph, matched_rid) + : try_get_matching_rid(rid_assets, matched_rid); + if (!found_match) { trace::verbose(_X(" No matching %s assets for package %s"), deps_entry_t::s_known_asset_types[asset_type_index], package.first.c_str()); rid_assets.clear(); @@ -260,7 +366,7 @@ void deps_json_t::perform_rid_fallback(rid_specific_assets_t* portable_assets, c } } -void deps_json_t::process_runtime_targets(const json_parser_t::value_t& json, const pal::string_t& target_name, const rid_fallback_graph_t& rid_fallback_graph, rid_specific_assets_t* p_assets) +void deps_json_t::process_runtime_targets(const json_parser_t::value_t& json, const pal::string_t& target_name, rid_specific_assets_t* p_assets) { rid_specific_assets_t& assets = *p_assets; for (const auto& package : json[_X("targets")][target_name.c_str()].GetObject()) @@ -318,7 +424,7 @@ void deps_json_t::process_runtime_targets(const json_parser_t::value_t& json, co } } - perform_rid_fallback(&assets, rid_fallback_graph); + perform_rid_fallback(&assets); } void deps_json_t::process_targets(const json_parser_t::value_t& json, const pal::string_t& target_name, deps_assets_t* p_assets) @@ -374,9 +480,9 @@ void deps_json_t::process_targets(const json_parser_t::value_t& json, const pal: } } -void deps_json_t::load_framework_dependent(const pal::string_t& deps_path, const json_parser_t::value_t& json, const pal::string_t& target_name, const rid_fallback_graph_t& rid_fallback_graph) +void deps_json_t::load_framework_dependent(const json_parser_t::value_t& json, const pal::string_t& target_name) { - process_runtime_targets(json, target_name, rid_fallback_graph, &m_rid_assets); + process_runtime_targets(json, target_name, &m_rid_assets); process_targets(json, target_name, &m_assets); auto package_exists = [&](const pal::string_t& package) -> bool { @@ -409,10 +515,10 @@ void deps_json_t::load_framework_dependent(const pal::string_t& deps_path, const return empty; }; - reconcile_libraries_with_targets(deps_path, json, package_exists, get_relpaths); + reconcile_libraries_with_targets(json, package_exists, get_relpaths); } -void deps_json_t::load_self_contained(const pal::string_t& deps_path, const json_parser_t::value_t& json, const pal::string_t& target_name) +void deps_json_t::load_self_contained(const json_parser_t::value_t& json, const pal::string_t& target_name) { process_targets(json, target_name, &m_assets); @@ -425,8 +531,7 @@ void deps_json_t::load_self_contained(const pal::string_t& deps_path, const json return m_assets.libs[package][type_index]; }; - reconcile_libraries_with_targets(deps_path, json, package_exists, get_relpaths); - populate_rid_fallback_graph(json, m_rid_fallback_graph); + reconcile_libraries_with_targets(json, package_exists, get_relpaths); } bool deps_json_t::has_package(const pal::string_t& name, const pal::string_t& ver) const @@ -456,9 +561,8 @@ bool deps_json_t::has_package(const pal::string_t& name, const pal::string_t& ve // Load the deps file and parse its "entry" lines which contain the "fields" of // the entry. Populate an array of these entries. // -void deps_json_t::load(bool is_framework_dependent, const pal::string_t& deps_path, const rid_fallback_graph_t& rid_fallback_graph) +void deps_json_t::load(bool is_framework_dependent, std::function post_process) { - m_deps_file = deps_path; m_file_exists = deps_file_exists(m_deps_file); if (!m_file_exists) @@ -478,14 +582,44 @@ void deps_json_t::load(bool is_framework_dependent, const pal::string_t& deps_pa runtime_target.GetString() : runtime_target[_X("name")].GetString(); - trace::verbose(_X("Loading deps file... %s as framework dependent=[%d]"), deps_path.c_str(), is_framework_dependent); + trace::verbose(_X("Loading deps file... [%s] as framework dependent=%d, use_fallback_graph=%d"), m_deps_file.c_str(), is_framework_dependent, m_rid_resolution_options.use_fallback_graph); if (is_framework_dependent) { - load_framework_dependent(deps_path, json.document(), name, rid_fallback_graph); + load_framework_dependent(json.document(), name); } else { - load_self_contained(deps_path, json.document(), name); + load_self_contained(json.document(), name); } + + if (post_process) + post_process(json.document()); +} + +std::unique_ptr deps_json_t::create_for_self_contained(const pal::string_t& deps_path, rid_resolution_options_t& rid_resolution_options) +{ + std::unique_ptr deps = std::unique_ptr(new deps_json_t(deps_path, rid_resolution_options)); + if (rid_resolution_options.use_fallback_graph) + { + assert(rid_resolution_options.rid_fallback_graph != nullptr && rid_resolution_options.rid_fallback_graph->empty()); + deps->load(false, + [&](const json_parser_t::value_t& json) + { + populate_rid_fallback_graph(json, *rid_resolution_options.rid_fallback_graph); + }); + } + else + { + deps->load(false); + } + + return deps; +} + +std::unique_ptr deps_json_t::create_for_framework_dependent(const pal::string_t& deps_path, const rid_resolution_options_t& rid_resolution_options) +{ + std::unique_ptr deps = std::unique_ptr(new deps_json_t(deps_path, rid_resolution_options)); + deps->load(true); + return deps; } diff --git a/src/native/corehost/hostpolicy/deps_format.h b/src/native/corehost/hostpolicy/deps_format.h index b1521299ed2c5..9f7dbb3245fff 100644 --- a/src/native/corehost/hostpolicy/deps_format.h +++ b/src/native/corehost/hostpolicy/deps_format.h @@ -26,12 +26,17 @@ class deps_json_t public: typedef str_to_vector_map_t rid_fallback_graph_t; - deps_json_t(bool is_framework_dependent, const pal::string_t& deps_path, const rid_fallback_graph_t* graph) - : m_file_exists(false) - , m_valid(false) + struct rid_resolution_options_t { - load(is_framework_dependent, deps_path, graph == nullptr ? m_rid_fallback_graph : *graph); - } + // Whether or not the RID fallback graph should be used + // For framework-dependent, this indicates whether rid_fallback_graph should be used to filter + // RID-specific assets. For self-contained, this indicates whether the RID graph should be read + // from the deps file. + bool use_fallback_graph; + + // The RID fallback graph to use + deps_json_t::rid_fallback_graph_t* rid_fallback_graph; + }; const std::vector& get_entries(deps_entry_t::asset_types type) const { @@ -51,47 +56,54 @@ class deps_json_t return m_valid; } - const rid_fallback_graph_t& get_rid_fallback_graph() const - { - return m_rid_fallback_graph; - } - const pal::string_t& get_deps_file() const { return m_deps_file; } public: // static + // Create a deps_json_t instance from a self-contained deps file + // If rid_resolution_options specify to read the RID fallback graph, it will be updated with the fallback_graph. + static std::unique_ptr create_for_self_contained(const pal::string_t& deps_path, rid_resolution_options_t& rid_resolution_options); + + // Create a deps_json_t instance from a framework-dependent deps file + static std::unique_ptr create_for_framework_dependent(const pal::string_t& deps_path, const rid_resolution_options_t& rid_resolution_options); + // Get the RID fallback graph for a deps file. // Parse failures or non-existent files will return an empty fallback graph static rid_fallback_graph_t get_rid_fallback_graph(const pal::string_t& deps_path); private: - void load_self_contained(const pal::string_t& deps_path, const json_parser_t::value_t& json, const pal::string_t& target_name); - void load_framework_dependent(const pal::string_t& deps_path, const json_parser_t::value_t& json, const pal::string_t& target_name, const rid_fallback_graph_t& rid_fallback_graph); - void load(bool is_framework_dependent, const pal::string_t& deps_path, const rid_fallback_graph_t& rid_fallback_graph); - void process_runtime_targets(const json_parser_t::value_t& json, const pal::string_t& target_name, const rid_fallback_graph_t& rid_fallback_graph, rid_specific_assets_t* p_assets); + deps_json_t(const pal::string_t& deps_path, const rid_resolution_options_t& rid_resolution_options) + : m_deps_file(deps_path) + , m_file_exists(false) + , m_valid(false) + , m_rid_resolution_options(rid_resolution_options) + { } + + void load(bool is_framework_dependent, std::function post_process = {}); + void load_self_contained(const json_parser_t::value_t& json, const pal::string_t& target_name); + void load_framework_dependent(const json_parser_t::value_t& json, const pal::string_t& target_name); + void process_runtime_targets(const json_parser_t::value_t& json, const pal::string_t& target_name, rid_specific_assets_t* p_assets); void process_targets(const json_parser_t::value_t& json, const pal::string_t& target_name, deps_assets_t* p_assets); void reconcile_libraries_with_targets( - const pal::string_t& deps_path, const json_parser_t::value_t& json, const std::function& library_exists_fn, const std::function& get_assets_fn); - pal::string_t get_current_rid(const rid_fallback_graph_t& rid_fallback_graph); - void perform_rid_fallback(rid_specific_assets_t* portable_assets, const rid_fallback_graph_t& rid_fallback_graph); + void perform_rid_fallback(rid_specific_assets_t* portable_assets); std::vector m_deps_entries[deps_entry_t::asset_types::count]; deps_assets_t m_assets; rid_specific_assets_t m_rid_assets; - rid_fallback_graph_t m_rid_fallback_graph; + pal::string_t m_deps_file; bool m_file_exists; bool m_valid; - pal::string_t m_deps_file; + const rid_resolution_options_t& m_rid_resolution_options; }; #endif // __DEPS_FORMAT_H_ diff --git a/src/native/corehost/hostpolicy/deps_resolver.cpp b/src/native/corehost/hostpolicy/deps_resolver.cpp index f62cc4e196fa0..beedcef62cd06 100644 --- a/src/native/corehost/hostpolicy/deps_resolver.cpp +++ b/src/native/corehost/hostpolicy/deps_resolver.cpp @@ -608,7 +608,7 @@ void deps_resolver_t::init_known_entry_path(const deps_entry_t& entry, const pal } } -void deps_resolver_t::resolve_additional_deps(const pal::char_t* additional_deps_serialized, const deps_json_t::rid_fallback_graph_t* rid_fallback_graph) +void deps_resolver_t::resolve_additional_deps(const pal::char_t* additional_deps_serialized, const deps_json_t::rid_resolution_options_t& rid_resolution_options) { if (!m_is_framework_dependent || m_host_mode == host_mode_t::libhost) @@ -646,8 +646,7 @@ void deps_resolver_t::resolve_additional_deps(const pal::char_t* additional_deps trace::verbose(_X("Using specified additional deps.json: '%s'"), additional_deps_path.c_str()); - m_additional_deps.push_back(std::unique_ptr( - new deps_json_t(true, additional_deps_path, rid_fallback_graph))); + m_additional_deps.push_back(deps_json_t::create_for_framework_dependent(additional_deps_path, rid_resolution_options)); } else { @@ -707,8 +706,7 @@ void deps_resolver_t::resolve_additional_deps(const pal::char_t* additional_deps trace::verbose(_X("Using specified additional deps.json: '%s'"), json_full_path.c_str()); - m_additional_deps.push_back(std::unique_ptr( - new deps_json_t(true, json_full_path, rid_fallback_graph))); + m_additional_deps.push_back(deps_json_t::create_for_framework_dependent(json_full_path, rid_resolution_options)); } } } diff --git a/src/native/corehost/hostpolicy/deps_resolver.h b/src/native/corehost/hostpolicy/deps_resolver.h index 1d32e10bcae29..2a971ef0cfc33 100644 --- a/src/native/corehost/hostpolicy/deps_resolver.h +++ b/src/native/corehost/hostpolicy/deps_resolver.h @@ -39,15 +39,13 @@ typedef std::unordered_map name_to_resolve class deps_resolver_t { public: - // if root_framework_rid_fallback_graph is specified it is assumed that the fx_definitions - // doesn't contain the root framework at all. deps_resolver_t( const arguments_t& args, const fx_definition_vector_t& fx_definitions, const pal::char_t* additional_deps_serialized, const std::vector& shared_stores, const std::vector& additional_probe_paths, - const deps_json_t::rid_fallback_graph_t* root_framework_rid_fallback_graph, + deps_json_t::rid_resolution_options_t rid_resolution_options, bool is_framework_dependent) : m_fx_definitions(fx_definitions) , m_app_dir(args.app_root) @@ -59,9 +57,15 @@ class deps_resolver_t m_fx_deps.resize(m_fx_definitions.size()); pal::get_default_servicing_directory(&m_core_servicing); + // If we are using the RID fallback graph and weren't explicitly given a graph, that of + // the lowest (root) framework is used for higher frameworks. + deps_json_t::rid_fallback_graph_t root_rid_fallback_graph; + if (rid_resolution_options.use_fallback_graph && rid_resolution_options.rid_fallback_graph == nullptr) + { + rid_resolution_options.rid_fallback_graph = &root_rid_fallback_graph; + } + // Process from lowest (root) to highest (app) framework. - // If we weren't explicitly given a rid fallback graph, that of - // the root framework is used for higher frameworks. int lowest_framework = static_cast(m_fx_definitions.size()) - 1; for (int i = lowest_framework; i >= 0; --i) { @@ -70,22 +74,19 @@ class deps_resolver_t : get_fx_deps(m_fx_definitions[i]->get_dir(), m_fx_definitions[i]->get_name()); trace::verbose(_X("Using %s deps file"), deps_file.c_str()); - if (root_framework_rid_fallback_graph == nullptr && i == lowest_framework) + // Parse as framework-dependent if we are not the lowest framework or if there is only one + // framework, but framework-dependent is specified (for example, components) + if (i != lowest_framework || (lowest_framework == 0 && m_is_framework_dependent)) { - m_fx_deps[i] = std::unique_ptr(new deps_json_t(false, deps_file, nullptr)); - - // The fx_definitions contains the root framework, so set the - // rid fallback graph that will be used for other frameworks. - root_framework_rid_fallback_graph = &m_fx_deps[lowest_framework]->get_rid_fallback_graph(); + m_fx_deps[i] = deps_json_t::create_for_framework_dependent(deps_file, rid_resolution_options); } else { - // The rid graph is obtained from the root framework - m_fx_deps[i] = std::unique_ptr(new deps_json_t(true, deps_file, root_framework_rid_fallback_graph)); + m_fx_deps[i] = deps_json_t::create_for_self_contained(deps_file, rid_resolution_options); } } - resolve_additional_deps(additional_deps_serialized, root_framework_rid_fallback_graph); + resolve_additional_deps(additional_deps_serialized, rid_resolution_options); setup_probe_config(shared_stores, additional_probe_paths); } @@ -196,7 +197,7 @@ class deps_resolver_t void resolve_additional_deps( const pal::char_t* additional_deps_serialized, - const deps_json_t::rid_fallback_graph_t* rid_fallback_graph); + const deps_json_t::rid_resolution_options_t& rid_resolution_options); const deps_json_t& get_app_deps() const { diff --git a/src/native/corehost/hostpolicy/hostpolicy.cpp b/src/native/corehost/hostpolicy/hostpolicy.cpp index 6173b1ae368ff..a69d37632c61a 100644 --- a/src/native/corehost/hostpolicy/hostpolicy.cpp +++ b/src/native/corehost/hostpolicy/hostpolicy.cpp @@ -862,6 +862,23 @@ namespace return get_deps_from_app_binary(get_directory(context->application), context->application); } + + deps_json_t::rid_resolution_options_t get_component_rid_resolution_options(const hostpolicy_init_t& init) + { + bool read_fallback_graph = hostpolicy_context_t::should_read_rid_fallback_graph(init); + if (read_fallback_graph) + { + // The RID graph still has to come from the actual root framework, so get the deps file + // for the app's root framework and read in the graph. + static deps_json_t::rid_fallback_graph_t root_rid_fallback_graph = + deps_json_t::get_rid_fallback_graph(get_root_deps_file(init)); + return { read_fallback_graph, &root_rid_fallback_graph }; + } + else + { + return { read_fallback_graph, nullptr }; + } + } } SHARED_API int HOSTPOLICY_CALLTYPE corehost_resolve_component_dependencies( @@ -938,10 +955,7 @@ SHARED_API int HOSTPOLICY_CALLTYPE corehost_resolve_component_dependencies( // TODO Review: Since we're only passing the one component framework, the resolver will not consider // frameworks from the app for probing paths. So potential references to paths inside frameworks will not resolve. - // The RID graph still has to come from the actual root framework, so get the deps file - // for the app's root framework and read in the graph. - static deps_json_t::rid_fallback_graph_t root_rid_fallback_graph = - deps_json_t::get_rid_fallback_graph(get_root_deps_file(init)); + static deps_json_t::rid_resolution_options_t rid_resolution_options = get_component_rid_resolution_options(init); deps_resolver_t resolver( args, @@ -949,7 +963,7 @@ SHARED_API int HOSTPOLICY_CALLTYPE corehost_resolve_component_dependencies( /* additional_deps_serialized */ nullptr, // Additional deps - don't use those from the app, they're already in the app shared_store::get_paths(init.tfm, host_mode, init.host_info.host_path), init.probe_paths, - &root_rid_fallback_graph, + rid_resolution_options, true); pal::string_t resolver_errors; diff --git a/src/native/corehost/hostpolicy/hostpolicy_context.cpp b/src/native/corehost/hostpolicy/hostpolicy_context.cpp index cc0bc2116c69d..5172c8f947080 100644 --- a/src/native/corehost/hostpolicy/hostpolicy_context.cpp +++ b/src/native/corehost/hostpolicy/hostpolicy_context.cpp @@ -135,6 +135,19 @@ namespace } } +bool hostpolicy_context_t::should_read_rid_fallback_graph(const hostpolicy_init_t &init) +{ + const auto &iter = std::find(init.cfg_keys.cbegin(), init.cfg_keys.cend(), _X("System.Host.Resolution.ReadRidGraph")); + if (iter != init.cfg_keys.cend()) + { + size_t idx = iter - init.cfg_keys.cbegin(); + return pal::strcasecmp(init.cfg_values[idx].data(), _X("true")) == 0; + } + + // Reading the RID fallback graph is disabled by default + return false; +} + int hostpolicy_context_t::initialize(const hostpolicy_init_t &hostpolicy_init, const arguments_t &args, bool enable_breadcrumbs) { application = args.managed_application; @@ -142,6 +155,11 @@ int hostpolicy_context_t::initialize(const hostpolicy_init_t &hostpolicy_init, c host_path = hostpolicy_init.host_info.host_path; breadcrumbs_enabled = enable_breadcrumbs; + deps_json_t::rid_resolution_options_t rid_resolution_options + { + should_read_rid_fallback_graph(hostpolicy_init), + nullptr, /*rid_fallback_graph*/ + }; deps_resolver_t resolver { args, @@ -149,7 +167,7 @@ int hostpolicy_context_t::initialize(const hostpolicy_init_t &hostpolicy_init, c hostpolicy_init.additional_deps_serialized.c_str(), shared_store::get_paths(hostpolicy_init.tfm, host_mode, host_path), hostpolicy_init.probe_paths, - /* root_framework_rid_fallback_graph */ nullptr, // This means that the fx_definitions contains the root framework + rid_resolution_options, hostpolicy_init.is_framework_dependent }; diff --git a/src/native/corehost/hostpolicy/hostpolicy_context.h b/src/native/corehost/hostpolicy/hostpolicy_context.h index 608a637419574..a7d8faf6f7a6f 100644 --- a/src/native/corehost/hostpolicy/hostpolicy_context.h +++ b/src/native/corehost/hostpolicy/hostpolicy_context.h @@ -31,6 +31,9 @@ struct hostpolicy_context_t host_runtime_contract host_contract; int initialize(const hostpolicy_init_t &hostpolicy_init, const arguments_t &args, bool enable_breadcrumbs); + +public: // static + static bool should_read_rid_fallback_graph(const hostpolicy_init_t &init); }; #endif // __HOSTPOLICY_CONTEXT_H__ diff --git a/src/native/corehost/setup.cmake b/src/native/corehost/setup.cmake index cedabbce6bca8..5a680969bb9d9 100644 --- a/src/native/corehost/setup.cmake +++ b/src/native/corehost/setup.cmake @@ -56,3 +56,9 @@ if("${CLI_CMAKE_FALLBACK_OS}" STREQUAL "") else() add_definitions(-DFALLBACK_HOST_OS="${CLI_CMAKE_FALLBACK_OS}") endif() + +add_definitions(-DCURRENT_OS_NAME="${CLR_CMAKE_TARGET_OS}") +add_definitions(-DCURRENT_ARCH_NAME="${CLR_CMAKE_TARGET_ARCH}") +if("${CLI_CMAKE_FALLBACK_OS}" STREQUAL "${CLR_CMAKE_TARGET_OS}") + add_definitions(-DFALLBACK_OS_IS_SAME_AS_TARGET_OS) +endif()