diff --git a/docs/design/features/framework-version-resolution.md b/docs/design/features/framework-version-resolution.md index 178f41d2328e3..3ab1d22d1049d 100644 --- a/docs/design/features/framework-version-resolution.md +++ b/docs/design/features/framework-version-resolution.md @@ -1,7 +1,7 @@ # Framework version resolution This document describes .NET Core 3.0 version resolution behavior when the host resolves framework references for framework dependent apps. -It's just a part of the overall framework resolution scenario described in [multilevel-sharedfx-lookup](multilevel-sharedfx-lookup.md). +It's just a part of the overall framework resolution scenario described in [sharedfx-lookup](sharedfx-lookup.md). ## Framework references Application defines its framework dependencies in its `.runtimeconfig.json` file. Each framework then defines its dependencies in its copy of `.runtimeconfig.json`. Each dependency is expressed as a framework reference. Together these form a graph. The host must resolve the references by finding the actual frameworks which are available on the machine. It must also unify references if there are multi references to the same framework. diff --git a/docs/design/features/host-components.md b/docs/design/features/host-components.md index 59669d661d39e..6c8def6efdd02 100644 --- a/docs/design/features/host-components.md +++ b/docs/design/features/host-components.md @@ -20,7 +20,7 @@ The entry-point typically does just one thing: it finds the `hostfxr` library an ## Host FXR This library finds and resolves the runtime and all the frameworks the app needs. Then it loads the `hostpolicy` library and transfers control to it. -The host FXR library reads the `.runtimeconfig.json` of the app (and all it's dependent frameworks) and resolves the frameworks. It implements the algorithm for framework resolution as described in [SharedFX Lookup](multilevel-sharedfx-lookup.md) and in [Framework version resolution](framework-version-resolution.md). +The host FXR library reads the `.runtimeconfig.json` of the app (and all it's dependent frameworks) and resolves the frameworks. It implements the algorithm for framework resolution as described in [SharedFX Lookup](sharedfx-lookup.md) and in [Framework version resolution](framework-version-resolution.md). In most cases the latest available version of `hostfxr` is used. Self-contained apps use `hostfxr` from the app folder. diff --git a/docs/design/features/host-probing.md b/docs/design/features/host-probing.md index 38df2347406b8..febdf24932a1b 100644 --- a/docs/design/features/host-probing.md +++ b/docs/design/features/host-probing.md @@ -48,7 +48,7 @@ The list of probing paths ordered according to their priority. First path in the If the app (or framework) has dependencies on frameworks, these frameworks are used as probing paths. The order is from the higher level framework to lower level framework. The app is considered the highest level, it direct dependencies are next and so on. For assets from frameworks, only that framework and lower level frameworks are considered. - Note: These directories come directly out of the framework resolution process. Special note on Windows where global locations are always considered even if the app is not executed via the shared `dotnet.exe`. More details can be found in [Multi-level Shared FX Lookup](multilevel-sharedfx-lookup.md). + Note: These directories come directly out of the framework resolution process. Special note on Windows where global locations are always considered even if the app is not executed via the shared `dotnet.exe`. More details can be found in [Shared FX Lookup](sharedfx-lookup.md). * Shared store paths * `$DOTNET_SHARED_STORE/|arch|/|tfm|` - The environment variable `DOTNET_SHARED_STORE` can contain multiple paths, in which case each is appended with `|arch|/|tfm|` and used as a probing path. * If the app is executed through `dotnet.exe` then path relative to the directory with the `dotnet.exe` is used diff --git a/docs/design/features/multilevel-sharedfx-lookup.md b/docs/design/features/sharedfx-lookup.md similarity index 91% rename from docs/design/features/multilevel-sharedfx-lookup.md rename to docs/design/features/sharedfx-lookup.md index b729819efe8bf..8453350f13c1a 100644 --- a/docs/design/features/multilevel-sharedfx-lookup.md +++ b/docs/design/features/sharedfx-lookup.md @@ -1,10 +1,10 @@ -# Multi-level SharedFX Lookup +# SharedFX Lookup ## Introduction -There are two possible ways of running .NET Core Applications: through dotnet.exe or through a custom executable appname.exe. The first one is used when the user wants to run a framework-dependent app or a .NET Core command while the second one is used for self-contained applications. Both executables share exactly the same source code. +There are two main ways of running .NET Applications: through `dotnet` or through the `apphost` executables. The executable is in charge of finding and loading `hostfxr`. `hostfxr`, in turn, must find and load `hostpolicy`. It is also responsible for searching for the SDK when running .NET SDK commands. Finally, `hostpolicy` must find and load the runtime (`coreclr`). See [host components](host-components.md) for details. -The executable is in charge of finding and loading the hostfxr.dll file. The hostfxr, in turn, must find and load the hostpolicy.dll file (it’s also responsible for searching for the SDK when running .NET commands). At last the coreclr.dll file must be found and loaded by the hostpolicy. Self-contained apps are supposed to keep all its dependencies in the same location as the executable. Framework-dependent apps must have the runtime files inside predefined folders. +An application can either be [framework-dependent](https://docs.microsoft.com/dotnet/core/deploying/#publish-framework-dependent) or [self-contained](https://docs.microsoft.com/dotnet/core/deploying/#publish-self-contained). Framework-dependent apps must have the runtime files inside predefined folders. Self-contained apps are expected to have their dependencies in the same location as the executable. ## Semantic Versioning @@ -219,6 +219,16 @@ In order to compare versions of an assembly, the assemblyVersion and fileVersion ## Global locations +Global install locations are described in the [install locations design](https://github.com/dotnet/designs/blob/main/accepted/2020/install-locations.md) document. + +**.NET 7.0 and above** + +When running `dotnet`, only the executable directory will be searched and global locations are not searched. For all other [entry-point hosts](host-components.md#entry-point-hosts), if the `DOTNET_ROOT` environment variable is set, that path is searched. If the environment variable is not set, the global location as described in [install locations](https://github.com/dotnet/designs/blob/main/accepted/2020/install-locations.md) is searched. + +See [disable multi-level lookup](https://github.com/dotnet/designs/blob/main/accepted/2022/disable-multi-level-lookup-by-default.md) for more details. + +**Before .NET 7.0** + In addition to searching the executable directory, the global .NET location is also searched. The global folders may vary depending on the running operational system. They are defined as follows: Global .NET location: @@ -271,11 +281,19 @@ To make sure that the changes are working correctly, the following behavior cond ### SDK search -Like the Framework search, the SDK is searched for a compatible version. Instead of looking for it only in relation to the executable directory, it is also searched in the folders specified above by following the same priority rank. +Like the Framework search, the SDK is searched for a compatible version. + +Unlike the Framework search, the SDK search does a roll-forward for pre-release versions when the patch version changes. For example, if you install v2.0.1-pre, it will be used over v2.0.0. + +**.NET 7.0 and above** + +Only the executable directory will be searched. See [disable multi-level lookup](https://github.com/dotnet/designs/blob/main/accepted/2022/disable-multi-level-lookup-by-default.md) for more details. + +**Before .NET 7.0** + +Aside from looking for it in relation to the executable directory, it is also searched in the folders specified above by following the same priority rank. The search is conducted as follows: 1. In relation to the executable directory: search for the specified version. If it cannot be found, choose the most appropriate available version. If there’s no available version, proceed to the next step. 2. In relation to the global location: search for the specified version. If it cannot be found, choose the most appropriate available version. If there’s no available version, then we were not able to find any version folder and an error message is returned. - -Unlike the Framework search, the SDK search does a roll-forward for pre-release versions when the patch version changes. For example, if you install v2.0.1-pre, it will be used over v2.0.0. diff --git a/src/installer/tests/HostActivation.Tests/DotnetArgValidation.cs b/src/installer/tests/HostActivation.Tests/DotnetArgValidation.cs index bffa811215b67..f7c0bdc468fca 100644 --- a/src/installer/tests/HostActivation.Tests/DotnetArgValidation.cs +++ b/src/installer/tests/HostActivation.Tests/DotnetArgValidation.cs @@ -79,7 +79,7 @@ public void InvalidFileOrCommand_NoSDK_ListsPossibleIssues() .Execute(fExpectedToFail: true) .Should().Fail() .And.HaveStdErrContaining($"The application '{fileName}' does not exist") - .And.HaveStdErrContaining($"It was not possible to find any installed .NET SDKs"); + .And.FindAnySdk(false); } // Return a non-exisitent path that contains a mix of / and \ diff --git a/src/installer/tests/HostActivation.Tests/FrameworkResolution/FrameworkResolutionCommandResultExtensions.cs b/src/installer/tests/HostActivation.Tests/FrameworkResolution/FrameworkResolutionCommandResultExtensions.cs index 017a601e99491..d7280481ddaff 100644 --- a/src/installer/tests/HostActivation.Tests/FrameworkResolution/FrameworkResolutionCommandResultExtensions.cs +++ b/src/installer/tests/HostActivation.Tests/FrameworkResolution/FrameworkResolutionCommandResultExtensions.cs @@ -35,10 +35,10 @@ public static AndConstraint ShouldHaveResolvedFramework /// Constraint public static AndConstraint ShouldHaveResolvedFrameworkOrFailToFind(this CommandResult result, string resolvedFrameworkName, string resolvedFrameworkVersion, string resolvedFrameworkBasePath = null) { - if (resolvedFrameworkName == null || resolvedFrameworkVersion == null || + if (resolvedFrameworkName == null || resolvedFrameworkVersion == null || resolvedFrameworkVersion == FrameworkResolutionBase.ResolvedFramework.NotFound) { - return result.ShouldFailToFindCompatibleFrameworkVersion(); + return result.ShouldFailToFindCompatibleFrameworkVersion(resolvedFrameworkName); } else { @@ -46,15 +46,21 @@ public static AndConstraint ShouldHaveResolvedFramework } } - public static AndConstraint DidNotFindCompatibleFrameworkVersion(this CommandResultAssertions assertion) + public static AndConstraint DidNotFindCompatibleFrameworkVersion(this CommandResultAssertions assertion, string frameworkName, string requestedVersion) { - return assertion.HaveStdErrContaining("It was not possible to find any compatible framework version"); + var constraint = assertion.HaveStdErrContaining("You must install or update .NET to run this application."); + if (frameworkName is not null) + { + constraint = constraint.And.HaveStdErrContaining($"Framework: '{frameworkName}', {(requestedVersion is null ? "" : $"version '{requestedVersion}'")}"); + } + + return constraint; } - public static AndConstraint ShouldFailToFindCompatibleFrameworkVersion(this CommandResult result) + public static AndConstraint ShouldFailToFindCompatibleFrameworkVersion(this CommandResult result, string frameworkName, string requestedVersion = null) { return result.Should().Fail() - .And.DidNotFindCompatibleFrameworkVersion(); + .And.DidNotFindCompatibleFrameworkVersion(frameworkName, requestedVersion); } public static AndConstraint FailedToReconcileFrameworkReference( @@ -96,7 +102,7 @@ public static AndConstraint ShouldHaveResolvedFramework } else if (resolvedVersion == FrameworkResolutionBase.ResolvedFramework.NotFound) { - return result.ShouldFailToFindCompatibleFrameworkVersion(); + return result.ShouldFailToFindCompatibleFrameworkVersion(frameworkName, null); } else { diff --git a/src/installer/tests/HostActivation.Tests/FrameworkResolution/MultipleHives.cs b/src/installer/tests/HostActivation.Tests/FrameworkResolution/MultipleHives.cs index 1ae3df06f1631..52236a4cc4076 100644 --- a/src/installer/tests/HostActivation.Tests/FrameworkResolution/MultipleHives.cs +++ b/src/installer/tests/HostActivation.Tests/FrameworkResolution/MultipleHives.cs @@ -25,7 +25,7 @@ public MultipleHives(SharedTestState sharedState) [Theory] // MLL where global hive has a better match [InlineData("5.0.0", "net5.0", true, "5.1.2")] - [InlineData("5.0.0", "net5.0", null, "5.1.2")] // MLL is on by default, so same as true + [InlineData("5.0.0", "net5.0", null, "5.1.2")] // MLL is on by default before 7.0, so same as true [InlineData("5.0.0", "net5.0", false, "5.2.0")] // No global hive allowed // MLL (where global hive has better match) with various TFMs [InlineData("5.0.0", "netcoreapp3.0", true, "5.1.2")] @@ -37,11 +37,12 @@ public MultipleHives(SharedTestState sharedState) [InlineData("5.0.0", "net6.0", true, "5.1.2")] [InlineData("5.0.0", "net6.0", null, "5.1.2")] [InlineData("5.0.0", "net6.0", false, "5.2.0")] - [InlineData("7.0.0", "net7.0", true, "7.0.1")] - [InlineData("7.0.0", "net7.0", null, "7.0.1")] + // MLL is disabled for 7.0+ + [InlineData("7.0.0", "net7.0", true, "7.1.2")] // MLL disabled for 7.0+ - setting it doesn't change anything + [InlineData("7.0.0", "net7.0", null, "7.1.2")] [InlineData("7.0.0", "net7.0", false, "7.1.2")] - [InlineData("7.0.0", "net8.0", true, "7.0.1")] - [InlineData("7.0.0", "net8.0", null, "7.0.1")] + [InlineData("7.0.0", "net8.0", true, "7.1.2")] // MLL disabled for 7.0+ - setting it doesn't change anything + [InlineData("7.0.0", "net8.0", null, "7.1.2")] [InlineData("7.0.0", "net8.0", false, "7.1.2")] // MLL where main hive has a better match [InlineData("6.0.0", "net6.0", true, "6.1.4")] // Global hive with better version (higher patch) @@ -82,7 +83,7 @@ public void FrameworkHiveSelection_CurrentDirectoryIsIgnored() [InlineData("6.1.4", "net6.0", true, "6.1.4", true)] [InlineData("6.1.4", "net6.0", null, "6.1.4", true)] [InlineData("6.1.4", "net6.0", false, ResolvedFramework.NotFound, false)] - [InlineData("6.1.4", "net7.0", true, "6.1.4", true)] + [InlineData("6.1.4", "net7.0", true, ResolvedFramework.NotFound, false)] // MLL disabled for 7.0+ [InlineData("7.1.2", "net6.0", true, "7.1.2", false)] // 7.1.2 is in both main and global hives - the main should always win with exact match [InlineData("7.1.2", "net6.0", null, "7.1.2", false)] [InlineData("7.1.2", "net6.0", false, "7.1.2", false)] @@ -154,7 +155,7 @@ public void ListRuntimes(bool? multiLevelLookup) string expectedOutput = string.Join( string.Empty, - GetExpectedFrameworks(multiLevelLookup) + GetExpectedFrameworks(false) // MLL Is always disabled for dotnet --list-runtimes .Select(t => $"{MicrosoftNETCoreApp} {t.Version} [{Path.Combine(t.Path, "shared", MicrosoftNETCoreApp)}]{Environment.NewLine}")); // !!IMPORTANT!!: This test verifies the exact match of the entire output of the command (not a substring!) @@ -179,7 +180,7 @@ public void DotnetInfo(bool? multiLevelLookup) string expectedOutput = $".NET runtimes installed:{Environment.NewLine}" + string.Join(string.Empty, - GetExpectedFrameworks(multiLevelLookup) + GetExpectedFrameworks(false) // MLL is always disabled for dotnet --info .Select(t => $" {MicrosoftNETCoreApp} {t.Version} [{Path.Combine(t.Path, "shared", MicrosoftNETCoreApp)}]{Environment.NewLine}")); RunTest( @@ -190,13 +191,14 @@ public void DotnetInfo(bool? multiLevelLookup) } [Theory] - [InlineData("net5.0", true)] - [InlineData("net5.0", null)] - [InlineData("net5.0", false)] - [InlineData("net7.0", true)] - [InlineData("net7.0", null)] - [InlineData("net7.0", false)] - public void FrameworkResolutionError(string tfm, bool? multiLevelLookup) + [InlineData("net5.0", true, true)] + [InlineData("net5.0", null, true)] + [InlineData("net5.0", false, false)] + // MLL is disabled for 7.0+ + [InlineData("net7.0", true, false)] + [InlineData("net7.0", null, false)] + [InlineData("net7.0", false, false)] + public void FrameworkResolutionError(string tfm, bool? multiLevelLookup, bool effectiveMultiLevelLookup) { // Multi-level lookup is only supported on Windows. if (!OperatingSystem.IsWindows() && multiLevelLookup != false) @@ -205,8 +207,8 @@ public void FrameworkResolutionError(string tfm, bool? multiLevelLookup) string expectedOutput = $"The following frameworks were found:{Environment.NewLine}" + string.Join(string.Empty, - GetExpectedFrameworks(multiLevelLookup) - .Select(t => $" {t.Version} at [{Path.Combine(t.Path, "shared", MicrosoftNETCoreApp)}]{Environment.NewLine}")); + GetExpectedFrameworks(effectiveMultiLevelLookup) + .Select(t => $" {t.Version} at [{Path.Combine(t.Path, "shared", MicrosoftNETCoreApp)}]{Environment.NewLine}")); RunTest( runtimeConfig => runtimeConfig @@ -214,7 +216,8 @@ public void FrameworkResolutionError(string tfm, bool? multiLevelLookup) .WithFramework(MicrosoftNETCoreApp, "9999.9.9"), multiLevelLookup) .Should().Fail() - .And.HaveStdErrContaining(expectedOutput); + .And.HaveStdErrContaining(expectedOutput) + .And.HaveStdErrContaining("https://aka.ms/dotnet/app-launch-failed"); } private CommandResult RunTest(Func runtimeConfig, bool? multiLevelLookup = true) diff --git a/src/installer/tests/HostActivation.Tests/FrameworkResolution/RollForwardAndRollForwardOnNoCandidateFxSettings.cs b/src/installer/tests/HostActivation.Tests/FrameworkResolution/RollForwardAndRollForwardOnNoCandidateFxSettings.cs index b5fb6718fdb23..b12199a7bb842 100644 --- a/src/installer/tests/HostActivation.Tests/FrameworkResolution/RollForwardAndRollForwardOnNoCandidateFxSettings.cs +++ b/src/installer/tests/HostActivation.Tests/FrameworkResolution/RollForwardAndRollForwardOnNoCandidateFxSettings.cs @@ -101,10 +101,11 @@ public void Precedence( SettingLocation rollForwardOnNoCandidateFxLocation, bool passes) { + string requestedVersion = "5.0.0"; CommandResult result = RunTest( new TestSettings() .WithRuntimeConfigCustomizer(runtimeConfig => runtimeConfig - .WithFramework(MicrosoftNETCoreApp, "5.0.0")) + .WithFramework(MicrosoftNETCoreApp, requestedVersion)) .With(RollForwardSetting(rollForwardLocation, Constants.RollForwardSetting.Major)) .With(RollForwardOnNoCandidateFxSetting(rollForwardOnNoCandidateFxLocation, 0))); @@ -114,7 +115,7 @@ public void Precedence( } else { - result.Should().Fail().And.DidNotFindCompatibleFrameworkVersion(); + result.ShouldFailToFindCompatibleFrameworkVersion(MicrosoftNETCoreApp, requestedVersion); } } diff --git a/src/installer/tests/HostActivation.Tests/FrameworkResolution/RollForwardOnNoCandidateFx.cs b/src/installer/tests/HostActivation.Tests/FrameworkResolution/RollForwardOnNoCandidateFx.cs index 935d4122fa7d3..dda6a1dd6d185 100644 --- a/src/installer/tests/HostActivation.Tests/FrameworkResolution/RollForwardOnNoCandidateFx.cs +++ b/src/installer/tests/HostActivation.Tests/FrameworkResolution/RollForwardOnNoCandidateFx.cs @@ -8,7 +8,7 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation.FrameworkResolution { - public class RollForwardOnNoCandidateFx : + public class RollForwardOnNoCandidateFx : FrameworkResolutionBase, IClassFixture { @@ -113,18 +113,19 @@ public void RollForwardToLatestPatch_RollForwardOnNoCandidateFx(int? rollForward [InlineData(2, false, true)] public void RollForwardOnMinor_RollForwardOnNoCandidateFx(int? rollForwardOnNoCandidateFx, bool? applyPatches, bool passes) { + string requestedVersion = "5.0.0"; CommandResult result = RunTestWithOneFramework( runtimeConfig => runtimeConfig .WithRollForwardOnNoCandidateFx(rollForwardOnNoCandidateFx) .WithApplyPatches(applyPatches) - .WithFramework(MicrosoftNETCoreApp, "5.0.0")); + .WithFramework(MicrosoftNETCoreApp, requestedVersion)); if (passes) { result.ShouldHaveResolvedFramework(MicrosoftNETCoreApp, "5.1.3"); } else { - result.ShouldFailToFindCompatibleFrameworkVersion(); + result.ShouldFailToFindCompatibleFrameworkVersion(MicrosoftNETCoreApp, requestedVersion); } } @@ -140,18 +141,19 @@ public void RollForwardOnMinor_RollForwardOnNoCandidateFx(int? rollForwardOnNoCa [InlineData(2, false, true)] public void RollForwardOnMajor_RollForwardOnNoCandidateFx(int? rollForwardOnNoCandidateFx, bool? applyPatches, bool passes) { + string requestedVersion = "4.1.0"; CommandResult result = RunTestWithOneFramework( runtimeConfig => runtimeConfig .WithRollForwardOnNoCandidateFx(rollForwardOnNoCandidateFx) .WithApplyPatches(applyPatches) - .WithFramework(MicrosoftNETCoreApp, "4.1.0")); + .WithFramework(MicrosoftNETCoreApp, requestedVersion)); if (passes) { result.ShouldHaveResolvedFramework(MicrosoftNETCoreApp, "5.1.3"); } else { - result.ShouldFailToFindCompatibleFrameworkVersion(); + result.ShouldFailToFindCompatibleFrameworkVersion(MicrosoftNETCoreApp, requestedVersion); } } @@ -165,12 +167,13 @@ public void RollForwardOnMajor_RollForwardOnNoCandidateFx(int? rollForwardOnNoCa [InlineData(2, false)] public void NeverRollBackOnRelease(int? rollForwardOnNoCandidateFx, bool? applyPatches) { + string requestedVersion = "5.1.4"; RunTestWithOneFramework( runtimeConfig => runtimeConfig .WithRollForwardOnNoCandidateFx(rollForwardOnNoCandidateFx) .WithApplyPatches(applyPatches) - .WithFramework(MicrosoftNETCoreApp, "5.1.4")) - .ShouldFailToFindCompatibleFrameworkVersion(); + .WithFramework(MicrosoftNETCoreApp, requestedVersion)) + .ShouldFailToFindCompatibleFrameworkVersion(MicrosoftNETCoreApp, requestedVersion); } // Verifies that if both rollForwardOnNoCandidateFx=0 and applyPatches=0 there will be no rolling forward. @@ -203,14 +206,14 @@ public void RollForwardDisabledOnCandidateFxAndDisabledApplyPatches_MatchesExact [Fact] public void RollForwardOnMinorDisabledOnNoCandidateFx_FailsToRoll() { + string requestedVersion = "5.0.0"; RunTestWithOneFramework( runtimeConfig => runtimeConfig .WithRollForwardOnNoCandidateFx(0) - .WithFramework(MicrosoftNETCoreApp, "5.0.0")) + .WithFramework(MicrosoftNETCoreApp, requestedVersion)) // Will still attempt roll forward to latest patch - .Should().Fail() - .And.HaveStdErrContaining("Attempting FX roll forward") - .And.DidNotFindCompatibleFrameworkVersion(); + .ShouldFailToFindCompatibleFrameworkVersion(MicrosoftNETCoreApp, requestedVersion) + .And.HaveStdErrContaining("Attempting FX roll forward"); } // 3.0 change: In 2.* pre-release never rolled to release. In 3.* it will follow normal roll-forward rules. @@ -313,22 +316,23 @@ public void RollForwardToPreReleaseLatestPatch_RollForwardOnNoCandidateFx(int? r [InlineData(2, null, true)] [InlineData(2, false, true)] public void RollForwardToPreReleaseOnMinor_RollForwardOnNoCandidateFx( - int? rollForwardOnNoCandidateFx, - bool? applyPatches, + int? rollForwardOnNoCandidateFx, + bool? applyPatches, bool passes) { + string requestedVersion = "5.0.0"; CommandResult result = RunTestWithPreReleaseFramework( runtimeConfig => runtimeConfig .WithRollForwardOnNoCandidateFx(rollForwardOnNoCandidateFx) .WithApplyPatches(applyPatches) - .WithFramework(MicrosoftNETCoreApp, "5.0.0")); + .WithFramework(MicrosoftNETCoreApp, requestedVersion)); if (passes) { result.ShouldHaveResolvedFramework(MicrosoftNETCoreApp, "5.1.3-preview.2"); } else { - result.ShouldFailToFindCompatibleFrameworkVersion(); + result.ShouldFailToFindCompatibleFrameworkVersion(MicrosoftNETCoreApp, requestedVersion); } } @@ -346,18 +350,19 @@ public void RollForwardToPreReleaseOnMajor_RollForwardOnNoCandidateFx( bool? applyPatches, bool passes) { + string requestedVersion = "4.1.0"; CommandResult result = RunTestWithPreReleaseFramework( runtimeConfig => runtimeConfig .WithRollForwardOnNoCandidateFx(rollForwardOnNoCandidateFx) .WithApplyPatches(applyPatches) - .WithFramework(MicrosoftNETCoreApp, "4.1.0")); + .WithFramework(MicrosoftNETCoreApp, requestedVersion)); if (passes) { result.ShouldHaveResolvedFramework(MicrosoftNETCoreApp, "5.1.3-preview.2"); } else { - result.ShouldFailToFindCompatibleFrameworkVersion(); + result.ShouldFailToFindCompatibleFrameworkVersion(MicrosoftNETCoreApp, requestedVersion); } } @@ -371,12 +376,13 @@ public void RollForwardToPreReleaseOnMajor_RollForwardOnNoCandidateFx( [InlineData(2, false)] public void NeverRollBackOnPreRelease(int? rollForwardOnNoCandidateFx, bool? applyPatches) { + string requestedVersion = "5.1.3-preview.9"; RunTestWithPreReleaseFramework( runtimeConfig => runtimeConfig .WithRollForwardOnNoCandidateFx(rollForwardOnNoCandidateFx) .WithApplyPatches(applyPatches) - .WithFramework(MicrosoftNETCoreApp, "5.1.3-preview.9")) - .ShouldFailToFindCompatibleFrameworkVersion(); + .WithFramework(MicrosoftNETCoreApp, requestedVersion)) + .ShouldFailToFindCompatibleFrameworkVersion(MicrosoftNETCoreApp, requestedVersion); } private CommandResult RunTestWithPreReleaseFramework(Func runtimeConfig) @@ -623,7 +629,7 @@ public void RollForwardToPreReleaseToClosestPreRelease_FromRelease( [InlineData(2, false, "2.3.2")] // Pre-release is ignored, roll forward to closest release available public void RollForwardToClosestReleaseWithPreReleaseAvailable_FromRelease( int? rollForwardOnNoCandidateFx, - bool? applyPatches, + bool? applyPatches, string resolvedFramework) { RunTestWithManyVersions( diff --git a/src/installer/tests/HostActivation.Tests/FrameworkResolution/RollForwardOnNoCandidateFxSettings.cs b/src/installer/tests/HostActivation.Tests/FrameworkResolution/RollForwardOnNoCandidateFxSettings.cs index 3258653cdf40b..b18b8c8131c6b 100644 --- a/src/installer/tests/HostActivation.Tests/FrameworkResolution/RollForwardOnNoCandidateFxSettings.cs +++ b/src/installer/tests/HostActivation.Tests/FrameworkResolution/RollForwardOnNoCandidateFxSettings.cs @@ -24,12 +24,12 @@ public RollForwardOnNoCandidateFxSettings(SharedTestState sharedState) [Fact] public void Default() { + string requestedVersion = "4.0.0"; RunTest( new TestSettings() .WithRuntimeConfigCustomizer(runtimeConfig => runtimeConfig - .WithFramework(MicrosoftNETCoreApp, "4.0.0"))) - .Should().Fail() - .And.DidNotFindCompatibleFrameworkVersion(); + .WithFramework(MicrosoftNETCoreApp, requestedVersion))) + .ShouldFailToFindCompatibleFrameworkVersion(MicrosoftNETCoreApp, requestedVersion); RunTest( new TestSettings() @@ -122,7 +122,7 @@ public void EnvironmentPriority(SettingLocation settingLocation, bool envVariabl // Verifies interaction between variour and inner framework reference setting [Theory] // settingLocation innerReferenceWins // Command line overrides everything - even inner framework references - [InlineData(SettingLocation.CommandLine, false)] + [InlineData(SettingLocation.CommandLine, false)] [InlineData(SettingLocation.RuntimeOptions, true)] [InlineData(SettingLocation.FrameworkReference, true)] [InlineData(SettingLocation.Environment, true)] @@ -149,7 +149,7 @@ public void InnerFrameworkReference(SettingLocation settingLocation, bool innerR // RuntimeOptions and FrameworkReference settings are not inherited to inner reference [InlineData(SettingLocation.FrameworkReference, false)] // Since none is specified for the inner reference, environment is used - [InlineData(SettingLocation.Environment, true)] + [InlineData(SettingLocation.Environment, true)] public void NoInheritance_MoreRelaxed(SettingLocation settingLocation, bool appWins) { RunTest( @@ -172,7 +172,7 @@ public void NoInheritance_MoreRelaxed(SettingLocation settingLocation, bool appW // RuntimeOptions and FrameworkReference settings are not inherited to inner reference [InlineData(SettingLocation.FrameworkReference, false)] // Since none is specified for the inner reference, environment is used - [InlineData(SettingLocation.Environment, true)] + [InlineData(SettingLocation.Environment, true)] public void NoInheritance_MoreRestrictive(SettingLocation settingLocation, bool appWins) { RunTest( @@ -186,7 +186,7 @@ public void NoInheritance_MoreRestrictive(SettingLocation settingLocation, bool .ShouldHaveResolvedFrameworkOrFailToFind(MicrosoftNETCoreApp, appWins ? null : "5.1.3"); } - private CommandResult RunTest(TestSettings testSettings) => + private CommandResult RunTest(TestSettings testSettings) => RunTest(SharedState.DotNetWithFrameworks, SharedState.FrameworkReferenceApp, testSettings); public class SharedTestState : SharedTestStateBase @@ -202,7 +202,7 @@ public SharedTestState() .AddMicrosoftNETCoreAppFrameworkMockHostPolicy("2.5.5") .AddMicrosoftNETCoreAppFrameworkMockHostPolicy("5.1.3") .AddFramework( - MiddleWare, "2.1.2", + MiddleWare, "2.1.2", runtimeConfig => runtimeConfig.WithFramework(MicrosoftNETCoreApp, "5.1.3")) .Build(); diff --git a/src/installer/tests/HostActivation.Tests/FrameworkResolution/RollForwardPreReleaseOnly.cs b/src/installer/tests/HostActivation.Tests/FrameworkResolution/RollForwardPreReleaseOnly.cs index b3b6cf8a93536..9e35ed8ff2a63 100644 --- a/src/installer/tests/HostActivation.Tests/FrameworkResolution/RollForwardPreReleaseOnly.cs +++ b/src/installer/tests/HostActivation.Tests/FrameworkResolution/RollForwardPreReleaseOnly.cs @@ -122,11 +122,12 @@ public void RollForwardOnMajor_FromReleaseToPreRelease(string rollForward, bool? [InlineData(Constants.RollForwardSetting.LatestPatch, false)] public void NeverRollBackOnPreRelease_PreReleaseOnly(string rollForward, bool? applyPatches) { + string requestedVersion = "5.1.2-preview.3"; RunTest( - "5.1.2-preview.3", + requestedVersion, rollForward, applyPatches) - .ShouldFailToFindCompatibleFrameworkVersion(); + .ShouldFailToFindCompatibleFrameworkVersion(MicrosoftNETCoreApp, requestedVersion); } // Verifies that rollForward settings won't roll back (on patch). @@ -139,11 +140,12 @@ public void NeverRollBackOnPreRelease_PreReleaseOnly(string rollForward, bool? a [InlineData(Constants.RollForwardSetting.LatestPatch, false)] public void NeverRollBackOnPatch_PreReleaseOnly(string rollForward, bool? applyPatches) { + string requestedVersion = "5.1.3-preview.1"; RunTest( - "5.1.3-preview.1", + requestedVersion, rollForward, applyPatches) - .ShouldFailToFindCompatibleFrameworkVersion(); + .ShouldFailToFindCompatibleFrameworkVersion(MicrosoftNETCoreApp, requestedVersion); } // Verifies that rollForward settings won't roll back (on minor). @@ -157,11 +159,12 @@ public void NeverRollBackOnPatch_PreReleaseOnly(string rollForward, bool? applyP [InlineData(Constants.RollForwardSetting.LatestMinor, false)] public void NeverRollBackOnMinor_PreReleaseOnly(string rollForward, bool? applyPatches) { + string requestedVersion = "5.3.0-preview.1"; RunTest( - "5.3.0-preview.1", + requestedVersion, rollForward, applyPatches) - .ShouldFailToFindCompatibleFrameworkVersion(); + .ShouldFailToFindCompatibleFrameworkVersion(MicrosoftNETCoreApp, requestedVersion); } // Verifies that rollForward settings won't roll back (on major). @@ -175,11 +178,12 @@ public void NeverRollBackOnMinor_PreReleaseOnly(string rollForward, bool? applyP [InlineData(Constants.RollForwardSetting.LatestMinor, false)] public void NeverRollBackOnMajor_PreReleaseOnly(string rollForward, bool? applyPatches) { + string requestedVersion = "7.1.0-preview.1"; RunTest( - "7.1.0-preview.1", + requestedVersion, rollForward, applyPatches) - .ShouldFailToFindCompatibleFrameworkVersion(); + .ShouldFailToFindCompatibleFrameworkVersion(MicrosoftNETCoreApp, requestedVersion); } // Verifies that rollForward settings behave as expected starting with framework reference diff --git a/src/installer/tests/HostActivation.Tests/FrameworkResolution/RollForwardReleaseOnly.cs b/src/installer/tests/HostActivation.Tests/FrameworkResolution/RollForwardReleaseOnly.cs index 1f438a9d96d59..9899c4364a6cb 100644 --- a/src/installer/tests/HostActivation.Tests/FrameworkResolution/RollForwardReleaseOnly.cs +++ b/src/installer/tests/HostActivation.Tests/FrameworkResolution/RollForwardReleaseOnly.cs @@ -166,11 +166,12 @@ public void RollForwardOnMajor_ReleaseOnly(string rollForward, bool? applyPatche [InlineData(Constants.RollForwardSetting.LatestPatch, false)] public void NeverRollBackOnPatch_ReleaseOnly(string rollForward, bool? applyPatches) { + string requestedVersion = "2.1.4"; RunTest( - "2.1.4", + requestedVersion, rollForward, applyPatches) - .ShouldFailToFindCompatibleFrameworkVersion(); + .ShouldFailToFindCompatibleFrameworkVersion(MicrosoftNETCoreApp, requestedVersion); } // Verify that rollForward settings will never roll back to lower minor version. @@ -183,11 +184,12 @@ public void NeverRollBackOnPatch_ReleaseOnly(string rollForward, bool? applyPatc [InlineData(Constants.RollForwardSetting.LatestMinor, false)] public void NeverRollBackOnMinor_ReleaseOnly(string rollForward, bool? applyPatches) { + string requestedVersion = "2.5.0"; RunTest( - "2.5.0", + requestedVersion, rollForward, applyPatches) - .ShouldFailToFindCompatibleFrameworkVersion(); + .ShouldFailToFindCompatibleFrameworkVersion(MicrosoftNETCoreApp, requestedVersion); } // Verify that rollForward settings will never roll back to lower major version. @@ -200,11 +202,12 @@ public void NeverRollBackOnMinor_ReleaseOnly(string rollForward, bool? applyPatc [InlineData(Constants.RollForwardSetting.LatestMinor, false)] public void NeverRollBackOnMajor_ReleaseOnly(string rollForward, bool? applyPatches) { + string requestedVersion = "4.1.0"; RunTest( - "4.1.0", + requestedVersion, rollForward, applyPatches) - .ShouldFailToFindCompatibleFrameworkVersion(); + .ShouldFailToFindCompatibleFrameworkVersion(MicrosoftNETCoreApp, requestedVersion); } private CommandResult RunTest( diff --git a/src/installer/tests/HostActivation.Tests/FrameworkResolution/RollForwardSettings.cs b/src/installer/tests/HostActivation.Tests/FrameworkResolution/RollForwardSettings.cs index 7b35129c0577d..b5e278c74ccab 100644 --- a/src/installer/tests/HostActivation.Tests/FrameworkResolution/RollForwardSettings.cs +++ b/src/installer/tests/HostActivation.Tests/FrameworkResolution/RollForwardSettings.cs @@ -24,12 +24,12 @@ public RollForwardSettings(SharedTestState sharedState) [Fact] public void Default() { + string requestedVersion = "4.0.0"; RunTest( new TestSettings() .WithRuntimeConfigCustomizer(runtimeConfig => runtimeConfig .WithFramework(MicrosoftNETCoreApp, "4.0.0"))) - .Should().Fail() - .And.DidNotFindCompatibleFrameworkVersion(); + .ShouldFailToFindCompatibleFrameworkVersion(MicrosoftNETCoreApp, requestedVersion); RunTest( new TestSettings() @@ -138,7 +138,7 @@ private void ValidateValueIgnoresCase(SettingLocation settingLocation, string ro // RuntimeOptions and FrameworkReference settings are not inherited to inner reference [InlineData(SettingLocation.FrameworkReference, false)] // Since none is specified for the inner reference, environment is used - [InlineData(SettingLocation.Environment, true)] + [InlineData(SettingLocation.Environment, true)] public void NoInheritance_MoreRelaxed(SettingLocation settingLocation, bool appWins) { RunTest( @@ -161,7 +161,7 @@ public void NoInheritance_MoreRelaxed(SettingLocation settingLocation, bool appW // RuntimeOptions and FrameworkReference settings are not inherited to inner reference [InlineData(SettingLocation.FrameworkReference, false)] // Since none is specified for the inner reference, environment is used - [InlineData(SettingLocation.Environment, true)] + [InlineData(SettingLocation.Environment, true)] public void NoInheritance_MoreRestrictive(SettingLocation settingLocation, bool appWins) { RunTest( diff --git a/src/installer/tests/HostActivation.Tests/MultilevelSDKLookup.cs b/src/installer/tests/HostActivation.Tests/MultilevelSDKLookup.cs index 96d7ca63d691a..ecbd4529694d3 100644 --- a/src/installer/tests/HostActivation.Tests/MultilevelSDKLookup.cs +++ b/src/installer/tests/HostActivation.Tests/MultilevelSDKLookup.cs @@ -81,17 +81,20 @@ public void Dispose() [PlatformSpecific(TestPlatforms.Windows)] // Multi-level lookup is only supported on Windows. public void SdkMultilevelLookup_Global_Json_Single_Digit_Patch_Rollup() { + // Multi-level lookup is disabled for 7.0+, so the resolved SDK should never be from the registered directory. + // Set specified SDK version = 9999.3.4-global-dummy - SetGlobalJsonVersion("SingleDigit-global.json"); + string globalJsonPath = SetGlobalJsonVersion("SingleDigit-global.json"); + string requestedVersion = "9999.3.4-global-dummy"; // Specified SDK version: 9999.3.4-global-dummy // Cwd: empty // Exe: empty // Reg: empty - // Expected: no compatible version and a specific error messages + // Expected: no compatible version RunTest() .Should().Fail() - .And.HaveStdErrContaining("A compatible installed .NET SDK for global.json version"); + .And.NotFindCompatibleSdk(globalJsonPath, requestedVersion); // Add SDK versions AddAvailableSdkVersions(_exeSdkBaseDir, "9999.4.1", "9999.3.4-dummy"); @@ -100,10 +103,10 @@ public void SdkMultilevelLookup_Global_Json_Single_Digit_Patch_Rollup() // Cwd: empty // Exe: 9999.4.1, 9999.3.4-dummy // Reg: empty - // Expected: no compatible version and a specific error message + // Expected: no compatible version RunTest() .Should().Fail() - .And.HaveStdErrContaining("A compatible installed .NET SDK for global.json version"); + .And.NotFindCompatibleSdk(globalJsonPath, requestedVersion); // Add SDK versions AddAvailableSdkVersions(_regSdkBaseDir, "9999.3.3"); @@ -112,10 +115,10 @@ public void SdkMultilevelLookup_Global_Json_Single_Digit_Patch_Rollup() // Cwd: empty // Exe: 9999.4.1, 9999.3.4-dummy // Reg: 9999.3.3 - // Expected: no compatible version and a specific error message + // Expected: no compatible version RunTest() .Should().Fail() - .And.HaveStdErrContaining("A compatible installed .NET SDK for global.json version"); + .And.NotFindCompatibleSdk(globalJsonPath, requestedVersion); // Add SDK versions AddAvailableSdkVersions(_exeSdkBaseDir, "9999.3.4"); @@ -136,10 +139,10 @@ public void SdkMultilevelLookup_Global_Json_Single_Digit_Patch_Rollup() // Cwd: empty // Exe: 9999.4.1, 9999.3.4-dummy, 9999.3.4 // Reg: 9999.3.3, 9999.3.5-dummy - // Expected: 9999.3.5-dummy from reg dir + // Expected: 9999.3.4 from exe dir RunTest() .Should().Pass() - .And.HaveStdErrContaining(Path.Combine(_regSelectedMessage, "9999.3.5-dummy", _dotnetSdkDllMessageTerminator)); + .And.HaveStdErrContaining(Path.Combine(_exeSelectedMessage, "9999.3.4", _dotnetSdkDllMessageTerminator)); // Add SDK versions AddAvailableSdkVersions(_exeSdkBaseDir, "9999.3.600"); @@ -148,10 +151,10 @@ public void SdkMultilevelLookup_Global_Json_Single_Digit_Patch_Rollup() // Cwd: empty // Exe: 9999.4.1, 9999.3.4-dummy, 9999.3.4, 9999.3.600 // Reg: 9999.3.3, 9999.3.5-dummy - // Expected: 9999.3.5-dummy from reg dir + // Expected: 9999.3.4-dummy from exe dir RunTest() .Should().Pass() - .And.HaveStdErrContaining(Path.Combine(_regSelectedMessage, "9999.3.5-dummy", _dotnetSdkDllMessageTerminator)); + .And.HaveStdErrContaining(Path.Combine(_exeSelectedMessage, "9999.3.4", _dotnetSdkDllMessageTerminator)); // Add SDK versions AddAvailableSdkVersions(_exeSdkBaseDir, "9999.3.4-global-dummy"); @@ -171,27 +174,28 @@ public void SdkMultilevelLookup_Global_Json_Single_Digit_Patch_Rollup() .And.HaveStdOutContaining("9999.3.4-dummy") .And.HaveStdOutContaining("9999.3.4-global-dummy") .And.HaveStdOutContaining("9999.4.1") - .And.HaveStdOutContaining("9999.3.3") .And.HaveStdOutContaining("9999.3.4") - .And.HaveStdOutContaining("9999.3.600") - .And.HaveStdOutContaining("9999.3.5-dummy"); + .And.HaveStdOutContaining("9999.3.600"); } [Fact] [PlatformSpecific(TestPlatforms.Windows)] // Multi-level lookup is only supported on Windows. public void SdkMultilevelLookup_Global_Json_Two_Part_Patch_Rollup() { + // Multi-level lookup is disabled for 7.0+, so the resolved SDK should never be from the registered directory. + // Set specified SDK version = 9999.3.304-global-dummy - SetGlobalJsonVersion("TwoPart-global.json"); + string globalJsonPath = SetGlobalJsonVersion("TwoPart-global.json"); + string requestedVersion = "9999.3.304-global-dummy"; // Specified SDK version: 9999.3.304-global-dummy // Cwd: empty // Exe: empty // Reg: empty - // Expected: no compatible version and a specific error messages + // Expected: no compatible version RunTest() .Should().Fail() - .And.HaveStdErrContaining("A compatible installed .NET SDK for global.json version"); + .And.NotFindCompatibleSdk(globalJsonPath, requestedVersion); // Add SDK versions AddAvailableSdkVersions(_regSdkBaseDir, "9999.3.57", "9999.3.4-dummy"); @@ -200,10 +204,10 @@ public void SdkMultilevelLookup_Global_Json_Two_Part_Patch_Rollup() // Cwd: empty // Exe: empty // Reg: 9999.3.57, 9999.3.4-dummy - // Expected: no compatible version and a specific error message + // Expected: no compatible version RunTest() .Should().Fail() - .And.HaveStdErrContaining("A compatible installed .NET SDK for global.json version"); + .And.NotFindCompatibleSdk(globalJsonPath, requestedVersion); // Add SDK versions AddAvailableSdkVersions(_exeSdkBaseDir, "9999.3.300", "9999.7.304-global-dummy"); @@ -212,10 +216,10 @@ public void SdkMultilevelLookup_Global_Json_Two_Part_Patch_Rollup() // Cwd: empty // Exe: 9999.3.300, 9999.7.304-global-dummy // Reg: 9999.3.57, 9999.3.4-dummy - // Expected: no compatible version and a specific error message + // Expected: no compatible version RunTest() .Should().Fail() - .And.HaveStdErrContaining("A compatible installed .NET SDK for global.json version"); + .And.NotFindCompatibleSdk(globalJsonPath, requestedVersion); // Add SDK versions AddAvailableSdkVersions(_regSdkBaseDir, "9999.3.304"); @@ -224,10 +228,10 @@ public void SdkMultilevelLookup_Global_Json_Two_Part_Patch_Rollup() // Cwd: empty // Exe: 9999.3.300, 9999.7.304-global-dummy // Reg: 9999.3.57, 9999.3.4-dummy, 9999.3.304 - // Expected: 9999.3.304 from reg dir + // Expected: no compatible version RunTest() - .Should().Pass() - .And.HaveStdErrContaining(Path.Combine(_regSelectedMessage, "9999.3.304", _dotnetSdkDllMessageTerminator)); + .Should().Fail() + .And.NotFindCompatibleSdk(globalJsonPath, requestedVersion); // Add SDK versions AddAvailableSdkVersions(_exeSdkBaseDir, "9999.3.399", "9999.3.399-dummy", "9999.3.400"); @@ -261,25 +265,21 @@ public void SdkMultilevelLookup_Global_Json_Two_Part_Patch_Rollup() // Cwd: empty // Exe: 9999.3.300, 9999.7.304-global-dummy, 9999.3.399, 9999.3.399-dummy, 9999.3.400, 9999.3.2400, 9999.3.3004 // Reg: 9999.3.57, 9999.3.4-dummy, 9999.3.304, 9999.3.2400, 9999.3.3004, 9999.3.304-global-dummy - // Expected: 9999.3.304-global-dummy from reg dir + // Expected: 9999.3.399 from exe dir RunTest() .Should().Pass() - .And.HaveStdErrContaining(Path.Combine(_regSelectedMessage, "9999.3.304-global-dummy", _dotnetSdkDllMessageTerminator)); + .And.HaveStdErrContaining(Path.Combine(_exeSelectedMessage, "9999.3.399", _dotnetSdkDllMessageTerminator)); // Verify we have the expected SDK versions RunTest("--list-sdks") .Should().Pass() - .And.HaveStdOutContaining("9999.3.57") - .And.HaveStdOutContaining("9999.3.4-dummy") .And.HaveStdOutContaining("9999.3.300") .And.HaveStdOutContaining("9999.7.304-global-dummy") .And.HaveStdOutContaining("9999.3.399") .And.HaveStdOutContaining("9999.3.399-dummy") .And.HaveStdOutContaining("9999.3.400") .And.HaveStdOutContaining("9999.3.2400") - .And.HaveStdOutContaining("9999.3.3004") - .And.HaveStdOutContaining("9999.3.304") - .And.HaveStdOutContaining("9999.3.304-global-dummy"); + .And.HaveStdOutContaining("9999.3.3004"); } [Fact] @@ -295,10 +295,10 @@ public void SdkMultilevelLookup_Precedential_Order() // Cwd: empty // Exe: empty // Reg: 9999.0.4 - // Expected: 9999.0.4 from reg dir + // Expected: no SDKs found RunTest() - .Should().Pass() - .And.HaveStdErrContaining(Path.Combine(_regSelectedMessage, "9999.0.4", _dotnetSdkDllMessageTerminator)); + .Should().Fail() + .And.FindAnySdk(false); // Add SDK versions AddAvailableSdkVersions(_exeSdkBaseDir, "9999.0.4"); @@ -319,12 +319,12 @@ public void SdkMultilevelLookup_RegistryAccess() { // The purpose of this test is to verify that the product uses correct code to access // the registry to extract the path to search for SDKs. - // Most of our tests rely on a shortcut which is to set _DOTNET_TEST_SDK_SELF_REGISTERED_DIR env variable + // Most of our tests rely on a shortcut which is to set _DOTNET_TEST_GLOBALLY_REGISTERED_PATH env variable // which will skip the registry reading code in the product and simply use the specified value. // This test is different since it actually runs the registry reading code. // Normally the reg key the product uses is in HKEY_LOCAL_MACHINE which is only writable as admin // so we would require the tests to run as admin to modify that key (and it may introduce races with other code running on the machine). - // So instead the tests use _DOTENT_TEST_SDK_REGISTRY_PATH env variable to point to the produce to use + // So instead the tests use _DOTENT_TEST_REGISTRY_PATH env variable to point to the produce to use // different registry key, inside the HKEY_CURRENT_USER hive which is writable without admin. // Note that the test creates a unique key (based on PID) for every run, to avoid collisions between parallel running tests. @@ -341,15 +341,15 @@ public void SdkMultilevelLookup_RegistryAccess() // Cwd: empty // Exe: empty // Reg: 9999.0.4 - // Expected: 9999.0.4 from reg dir + // Expected: no SDKs found DotNet.Exec("help") .WorkingDirectory(_currentWorkingDir) .MultilevelLookup(true) .ApplyRegisteredInstallLocationOverride(registeredInstallLocationOverride) .EnableTracingAndCaptureOutputs() .Execute() - .Should().Pass() - .And.HaveStdErrContaining(Path.Combine(_regSelectedMessage, "9999.0.4", _dotnetSdkDllMessageTerminator)); + .Should().Fail() + .And.FindAnySdk(false); } } @@ -366,10 +366,10 @@ public void SdkMultilevelLookup_Must_Pick_The_Highest_Semantic_Version() // Cwd: empty // Exe: empty // Reg: 9999.0.0, 9999.0.3-dummy - // Expected: 9999.0.3-dummy from reg dir + // Expected: no SDKs found RunTest() - .Should().Pass() - .And.HaveStdErrContaining(Path.Combine(_regSelectedMessage, "9999.0.3-dummy", _dotnetSdkDllMessageTerminator)); + .Should().Fail() + .And.FindAnySdk(false); // Add SDK versions AddAvailableSdkVersions(_exeSdkBaseDir, "9999.0.3"); @@ -394,7 +394,7 @@ public void SdkMultilevelLookup_Must_Pick_The_Highest_Semantic_Version() // Expected: 9999.0.100 from reg dir RunTest() .Should().Pass() - .And.HaveStdErrContaining(Path.Combine(_regSelectedMessage, "9999.0.100", _dotnetSdkDllMessageTerminator)); + .And.HaveStdErrContaining(Path.Combine(_exeSelectedMessage, "9999.0.3", _dotnetSdkDllMessageTerminator)); // Add SDK versions AddAvailableSdkVersions(_exeSdkBaseDir, "9999.0.80"); @@ -406,7 +406,7 @@ public void SdkMultilevelLookup_Must_Pick_The_Highest_Semantic_Version() // Expected: 9999.0.100 from reg dir RunTest() .Should().Pass() - .And.HaveStdErrContaining(Path.Combine(_regSelectedMessage, "9999.0.100", _dotnetSdkDllMessageTerminator)); + .And.HaveStdErrContaining(Path.Combine(_exeSelectedMessage, "9999.0.80", _dotnetSdkDllMessageTerminator)); // Add SDK versions AddAvailableSdkVersions(_exeSdkBaseDir, "9999.0.5500000"); @@ -430,21 +430,17 @@ public void SdkMultilevelLookup_Must_Pick_The_Highest_Semantic_Version() // Expected: 9999.0.52000000 from reg dir RunTest() .Should().Pass() - .And.HaveStdErrContaining(Path.Combine(_regSelectedMessage, "9999.0.52000000", _dotnetSdkDllMessageTerminator)); + .And.HaveStdErrContaining(Path.Combine(_exeSelectedMessage, "9999.0.5500000", _dotnetSdkDllMessageTerminator)); // Verify we have the expected SDK versions RunTest("--list-sdks") .Should().Pass() - .And.HaveStdOutContaining("9999.0.0") - .And.HaveStdOutContaining("9999.0.3-dummy") .And.HaveStdOutContaining("9999.0.3") - .And.HaveStdOutContaining("9999.0.100") .And.HaveStdOutContaining("9999.0.80") - .And.HaveStdOutContaining("9999.0.5500000") - .And.HaveStdOutContaining("9999.0.52000000"); + .And.HaveStdOutContaining("9999.0.5500000"); } - private List<(string version, string rootPath)> AddSdkVersionsAndGetExpectedList(bool? multiLevelLookup) + private List<(string version, string rootPath)> AddSdkVersionsAndGetExpectedList() { AddAvailableSdkVersions(_exeSdkBaseDir, "5.0.2"); AddAvailableSdkVersions(_exeSdkBaseDir, "6.1.1"); @@ -457,11 +453,7 @@ public void SdkMultilevelLookup_Must_Pick_The_Highest_Semantic_Version() expectedList.Add(("5.0.2", _exeSdkBaseDir)); expectedList.Add(("6.1.1", _exeSdkBaseDir)); expectedList.Add(("7.1.2", _exeSdkBaseDir)); - if (multiLevelLookup is null || multiLevelLookup == true) - { - expectedList.Add(("6.2.0", _regSdkBaseDir)); - expectedList.Add(("7.0.1", _regSdkBaseDir)); - } + // MLL is always disabled for SDK resolution, so only the "exe" SDKs are listed expectedList.Sort((a, b) => { if (!Version.TryParse(a.version, out var aVersion)) @@ -485,7 +477,7 @@ public void ListSdks(bool? multiLevelLookup) if (!OperatingSystem.IsWindows() && multiLevelLookup != false) return; - var expectedList = AddSdkVersionsAndGetExpectedList(multiLevelLookup); + var expectedList = AddSdkVersionsAndGetExpectedList(); string expectedOutput = string.Join(string.Empty, expectedList.Select(t => $"{t.version} [{t.rootPath}]{Environment.NewLine}")); // !!IMPORTANT!!: This test verifies the exact match of the entire output of the command (not a substring!) @@ -506,14 +498,16 @@ public void SdkResolutionError(bool? multiLevelLookup) return; // Set specified SDK version = 9999.3.4-global-dummy - such SDK doesn't exist - SetGlobalJsonVersion("SingleDigit-global.json"); + string globalJsonPath = SetGlobalJsonVersion("SingleDigit-global.json"); + string requestedVersion = "9999.3.4-global-dummy"; // When we fail to resolve SDK version, we print out all available SDKs - var expectedList = AddSdkVersionsAndGetExpectedList(multiLevelLookup); - string expectedOutput = string.Join(string.Empty, expectedList.Select(t => $" {t.version} [{t.rootPath}]{Environment.NewLine}")); + var expectedList = AddSdkVersionsAndGetExpectedList(); + string expectedOutput = string.Join(string.Empty, expectedList.Select(t => $"{t.version} [{t.rootPath}]{Environment.NewLine}")); RunTest("help", multiLevelLookup) .Should().Fail() + .And.NotFindCompatibleSdk(globalJsonPath, requestedVersion) .And.HaveStdOutContaining(expectedOutput); } @@ -527,7 +521,7 @@ public void DotnetInfo(bool? multiLevelLookup) if (!OperatingSystem.IsWindows() && multiLevelLookup != false) return; - var expectedList = AddSdkVersionsAndGetExpectedList(multiLevelLookup); + var expectedList = AddSdkVersionsAndGetExpectedList(); string expectedOutput = $".NET SDKs installed:{Environment.NewLine}" + string.Join(string.Empty, expectedList.Select(t => $" {t.version} [{t.rootPath}]{Environment.NewLine}")); @@ -575,13 +569,14 @@ private void AddAvailableSdkVersions(string sdkBaseDir, params string[] availabl } // Put a global.json file in the cwd in order to specify a CLI - private void SetGlobalJsonVersion(string globalJsonFileName) + private string SetGlobalJsonVersion(string globalJsonFileName) { string destFile = Path.Combine(_currentWorkingDir, "global.json"); string srcFile = Path.Combine(RepoDirectories.TestAssetsFolder, "TestUtils", "SDKLookup", globalJsonFileName); File.Copy(srcFile, destFile, true); + return destFile; } private void WriteGlobalJson(string contents) diff --git a/src/installer/tests/HostActivation.Tests/NativeHostApis.cs b/src/installer/tests/HostActivation.Tests/NativeHostApis.cs index d9feb540d70bc..869c264239e2f 100644 --- a/src/installer/tests/HostActivation.Tests/NativeHostApis.cs +++ b/src/installer/tests/HostActivation.Tests/NativeHostApis.cs @@ -128,24 +128,18 @@ public SdkResolutionFixture(SharedTestState state) } [Fact] - [PlatformSpecific(TestPlatforms.Windows)] // Multi-level lookup is only supported on Windows. + [PlatformSpecific(TestPlatforms.Windows)] // The test setup only works on Windows (and MLL was Windows-only anyway) public void Hostfxr_get_available_sdks_with_multilevel_lookup() { var f = new SdkResolutionFixture(sharedTestState); - // With multi-level lookup (windows only): get local and global sdks sorted by ascending version, - // with global sdk coming before local sdk when versions are equal + // Starting with .NET 7, multi-level lookup is completely disabled for hostfxr API calls. + // This test is still valuable to validate that it is in fact disabled string expectedList = string.Join(';', new[] { Path.Combine(f.LocalSdkDir, "0.1.2"), - Path.Combine(f.ProgramFilesGlobalSdkDir, "1.2.3"), Path.Combine(f.LocalSdkDir, "1.2.3"), - Path.Combine(f.ProgramFilesGlobalSdkDir, "2.3.4-preview"), - Path.Combine(f.SelfRegisteredGlobalSdkDir, "3.0.0"), - Path.Combine(f.ProgramFilesGlobalSdkDir, "4.5.6"), Path.Combine(f.LocalSdkDir, "5.6.7-preview"), - Path.Combine(f.SelfRegisteredGlobalSdkDir, "5.6.7"), - Path.Combine(f.SelfRegisteredGlobalSdkDir, "15.1.4-preview"), }); using (TestOnlyProductBehavior.Enable(f.Dotnet.GreatestVersionHostFxrFilePath)) @@ -317,7 +311,7 @@ public void Hostfxr_get_dotnet_environment_info_dotnet_root_only() } [Fact] - [PlatformSpecific(TestPlatforms.Windows)] // Multi-level lookup is only supported on Windows. + [PlatformSpecific(TestPlatforms.Windows)] // The test setup only works on Windows (and MLL was Windows-only anyway) public void Hostfxr_get_dotnet_environment_info_with_multilevel_lookup_with_dotnet_root() { var f = new SdkResolutionFixture(sharedTestState); @@ -325,33 +319,18 @@ public void Hostfxr_get_dotnet_environment_info_with_multilevel_lookup_with_dotn { "0.1.2", "1.2.3", - "1.2.3", - "2.3.4-preview", - "3.0.0", - "4.5.6", "5.6.7-preview", - "5.6.7", - "15.1.4-preview" }); string expectedSdkPaths = string.Join(';', new[] { Path.Combine(f.LocalSdkDir, "0.1.2"), - Path.Combine(f.ProgramFilesGlobalSdkDir, "1.2.3"), Path.Combine(f.LocalSdkDir, "1.2.3"), - Path.Combine(f.ProgramFilesGlobalSdkDir, "2.3.4-preview"), - Path.Combine(f.SelfRegisteredGlobalSdkDir, "3.0.0"), - Path.Combine(f.ProgramFilesGlobalSdkDir, "4.5.6"), Path.Combine(f.LocalSdkDir, "5.6.7-preview"), - Path.Combine(f.SelfRegisteredGlobalSdkDir, "5.6.7"), - Path.Combine(f.SelfRegisteredGlobalSdkDir, "15.1.4-preview"), }); string expectedFrameworkNames = string.Join(';', new[] { - "HostFxr.Test.A", - "HostFxr.Test.A", - "HostFxr.Test.B", "HostFxr.Test.B", "HostFxr.Test.B", "HostFxr.Test.C" @@ -359,20 +338,14 @@ public void Hostfxr_get_dotnet_environment_info_with_multilevel_lookup_with_dotn string expectedFrameworkVersions = string.Join(';', new[] { - "1.2.3", - "3.0.0", "4.0.0", "5.6.7-A", - "5.6.7-A", "3.0.0" }); string expectedFrameworkPaths = string.Join(';', new[] { - Path.Combine(f.ProgramFilesGlobalFrameworksDir, "HostFxr.Test.A"), - Path.Combine(f.ProgramFilesGlobalFrameworksDir, "HostFxr.Test.A"), Path.Combine(f.LocalFrameworksDir, "HostFxr.Test.B"), - Path.Combine(f.ProgramFilesGlobalFrameworksDir, "HostFxr.Test.B"), Path.Combine(f.LocalFrameworksDir, "HostFxr.Test.B"), Path.Combine(f.LocalFrameworksDir, "HostFxr.Test.C") }); @@ -396,51 +369,13 @@ public void Hostfxr_get_dotnet_environment_info_with_multilevel_lookup_with_dotn } [Fact] - [PlatformSpecific(TestPlatforms.Windows)] // Multi-level lookup is only supported on Windows. + [PlatformSpecific(TestPlatforms.Windows)] // The test setup only works on Windows (and MLL was Windows-only anyway) public void Hostfxr_get_dotnet_environment_info_with_multilevel_lookup_only() { var f = new SdkResolutionFixture(sharedTestState); - string expectedSdkVersions = string.Join(';', new[] - { - "1.2.3", - "2.3.4-preview", - "3.0.0", - "4.5.6", - "5.6.7", - "15.1.4-preview" - }); - - string expectedSdkPaths = string.Join(';', new[] - { - Path.Combine(f.ProgramFilesGlobalSdkDir, "1.2.3"), - Path.Combine(f.ProgramFilesGlobalSdkDir, "2.3.4-preview"), - Path.Combine(f.SelfRegisteredGlobalSdkDir, "3.0.0"), - Path.Combine(f.ProgramFilesGlobalSdkDir, "4.5.6"), - Path.Combine(f.SelfRegisteredGlobalSdkDir, "5.6.7"), - Path.Combine(f.SelfRegisteredGlobalSdkDir, "15.1.4-preview"), - }); - - string expectedFrameworkNames = string.Join(';', new[] - { - "HostFxr.Test.A", - "HostFxr.Test.A", - "HostFxr.Test.B", - }); - - string expectedFrameworkVersions = string.Join(';', new[] - { - "1.2.3", - "3.0.0", - "5.6.7-A", - }); - - string expectedFrameworkPaths = string.Join(';', new[] - { - Path.Combine(f.ProgramFilesGlobalFrameworksDir, "HostFxr.Test.A"), - Path.Combine(f.ProgramFilesGlobalFrameworksDir, "HostFxr.Test.A"), - Path.Combine(f.ProgramFilesGlobalFrameworksDir, "HostFxr.Test.B"), - }); + // Multi-level lookup is completely disabled on 7+ + // The test runs the API with the dotnet root directory set to a location which doesn't have any SDKs or frameworks using (TestOnlyProductBehavior.Enable(f.Dotnet.GreatestVersionHostFxrFilePath)) { // We pass f.WorkingDir so that we don't resolve dotnet_dir to the global installation @@ -453,41 +388,20 @@ public void Hostfxr_get_dotnet_environment_info_with_multilevel_lookup_only() .Execute() .Should().Pass() .And.HaveStdOutContaining("hostfxr_get_dotnet_environment_info:Success") - .And.HaveStdOutContaining($"hostfxr_get_dotnet_environment_info sdk versions:[{expectedSdkVersions}]") - .And.HaveStdOutContaining($"hostfxr_get_dotnet_environment_info sdk paths:[{expectedSdkPaths}]") - .And.HaveStdOutContaining($"hostfxr_get_dotnet_environment_info framework names:[{expectedFrameworkNames}]") - .And.HaveStdOutContaining($"hostfxr_get_dotnet_environment_info framework versions:[{expectedFrameworkVersions}]") - .And.HaveStdOutContaining($"hostfxr_get_dotnet_environment_info framework paths:[{expectedFrameworkPaths}]"); + .And.HaveStdOutContaining($"hostfxr_get_dotnet_environment_info sdk versions:[]") + .And.HaveStdOutContaining($"hostfxr_get_dotnet_environment_info sdk paths:[]") + .And.HaveStdOutContaining($"hostfxr_get_dotnet_environment_info framework names:[]") + .And.HaveStdOutContaining($"hostfxr_get_dotnet_environment_info framework versions:[]") + .And.HaveStdOutContaining($"hostfxr_get_dotnet_environment_info framework paths:[]"); } } [Fact] - [PlatformSpecific(TestPlatforms.Windows)] // Multi-level lookup is only supported on Windows. + [PlatformSpecific(TestPlatforms.Windows)] // The test setup only works on Windows (and MLL was Windows-only anyway) public void Hostfxr_get_dotnet_environment_info_with_multilevel_lookup_only_self_register_program_files() { var f = new SdkResolutionFixture(sharedTestState); - string expectedFrameworkNames = string.Join(';', new[] - { - "HostFxr.Test.A", - "HostFxr.Test.A", - "HostFxr.Test.B", - }); - - string expectedFrameworkVersions = string.Join(';', new[] - { - "1.2.3", - "3.0.0", - "5.6.7-A", - }); - - string expectedFrameworkPaths = string.Join(';', new[] - { - Path.Combine(f.ProgramFilesGlobalFrameworksDir, "HostFxr.Test.A"), - Path.Combine(f.ProgramFilesGlobalFrameworksDir, "HostFxr.Test.A"), - Path.Combine(f.ProgramFilesGlobalFrameworksDir, "HostFxr.Test.B"), - }); - using (TestOnlyProductBehavior.Enable(f.Dotnet.GreatestVersionHostFxrFilePath)) { // We pass f.WorkingDir so that we don't resolve dotnet_dir to the global installation @@ -501,9 +415,9 @@ public void Hostfxr_get_dotnet_environment_info_with_multilevel_lookup_only_self .Execute() .Should().Pass() .And.HaveStdOutContaining("hostfxr_get_dotnet_environment_info:Success") - .And.HaveStdOutContaining($"hostfxr_get_dotnet_environment_info framework names:[{expectedFrameworkNames}]") - .And.HaveStdOutContaining($"hostfxr_get_dotnet_environment_info framework versions:[{expectedFrameworkVersions}]") - .And.HaveStdOutContaining($"hostfxr_get_dotnet_environment_info framework paths:[{expectedFrameworkPaths}]"); + .And.HaveStdOutContaining($"hostfxr_get_dotnet_environment_info framework names:[]") + .And.HaveStdOutContaining($"hostfxr_get_dotnet_environment_info framework versions:[]") + .And.HaveStdOutContaining($"hostfxr_get_dotnet_environment_info framework paths:[]"); } } diff --git a/src/installer/tests/HostActivation.Tests/PortableAppActivation.cs b/src/installer/tests/HostActivation.Tests/PortableAppActivation.cs index b0b52d901efd2..b8fced8d26ea6 100644 --- a/src/installer/tests/HostActivation.Tests/PortableAppActivation.cs +++ b/src/installer/tests/HostActivation.Tests/PortableAppActivation.cs @@ -474,8 +474,8 @@ public void AppHost_CLI_FrameworkDependent_MissingRuntimeFramework_ErrorReported .BinPath; expectedErrorCode = Constants.ErrorCode.FrameworkMissingFailure; - expectedStdErr = $"The framework '{Constants.MicrosoftNETCoreApp}', " + - $"version '{sharedTestState.RepoDirectories.MicrosoftNETCoreAppVersion}' ({fixture.RepoDirProvider.BuildArchitecture}) was not found."; + expectedStdErr = $"Framework: '{Constants.MicrosoftNETCoreApp}', " + + $"version '{sharedTestState.RepoDirectories.MicrosoftNETCoreAppVersion}' ({fixture.RepoDirProvider.BuildArchitecture})"; expectedUrlQuery = $"framework={Constants.MicrosoftNETCoreApp}&framework_version={sharedTestState.RepoDirectories.MicrosoftNETCoreAppVersion}"; } @@ -487,7 +487,7 @@ public void AppHost_CLI_FrameworkDependent_MissingRuntimeFramework_ErrorReported var result = command.WaitForExit(true); result.Should().Fail() - .And.HaveStdErrContaining($"- https://aka.ms/dotnet-core-applaunch?{expectedUrlQuery}") + .And.HaveStdErrContaining($"https://aka.ms/dotnet-core-applaunch?{expectedUrlQuery}") .And.HaveStdErrContaining(expectedStdErr); // Some Unix systems will have 8 bit exit codes. @@ -529,11 +529,13 @@ public void AppHost_GUI_FrameworkDependent_MissingRuntimeFramework_ErrorReported WindowsUtils.WaitForPopupFromProcess(command.Process); command.Process.Kill(); + string expectedMissingFramework = $"'{Constants.MicrosoftNETCoreApp}', version '{sharedTestState.RepoDirectories.MicrosoftNETCoreAppVersion}' ({fixture.RepoDirProvider.BuildArchitecture})"; var result = command.WaitForExit(true) .Should().Fail() .And.HaveStdErrContaining($"Showing error dialog for application: '{Path.GetFileName(appExe)}' - error code: 0x{expectedErrorCode}") .And.HaveStdErrContaining($"url: 'https://aka.ms/dotnet-core-applaunch?{expectedUrlQuery}") - .And.HaveStdErrContaining("&gui=true"); + .And.HaveStdErrContaining("&gui=true") + .And.HaveStdErrMatching($"dialog message: (?>.|\\s)*{System.Text.RegularExpressions.Regex.Escape(expectedMissingFramework)}"); } } diff --git a/src/installer/tests/HostActivation.Tests/SDKLookup.cs b/src/installer/tests/HostActivation.Tests/SDKLookup.cs index a8ec31b413cce..12093a70e94b7 100644 --- a/src/installer/tests/HostActivation.Tests/SDKLookup.cs +++ b/src/installer/tests/HostActivation.Tests/SDKLookup.cs @@ -39,15 +39,16 @@ public SDKLookup(SharedTestState sharedState) public void SdkLookup_Global_Json_Single_Digit_Patch_Rollup() { // Set specified SDK version = 9999.3.4-global-dummy - CopyGlobalJson("SingleDigit-global.json"); + string globalJsonPath = CopyGlobalJson("SingleDigit-global.json"); + string requestedVersion = "9999.3.4-global-dummy"; // Specified SDK version: 9999.3.4-global-dummy // Exe: empty - // Expected: no compatible version and a specific error messages + // Expected: no compatible version, no SDKs found RunTest() .Should().Fail() - .And.HaveStdErrContaining("A compatible installed .NET SDK for global.json version") - .And.HaveStdErrContaining("It was not possible to find any installed .NET SDKs") + .And.NotFindCompatibleSdk(globalJsonPath, requestedVersion) + .And.FindAnySdk(false) .And.HaveStdErrContaining("aka.ms/dotnet-download") .And.NotHaveStdErrContaining("Checking if resolved SDK dir"); @@ -56,22 +57,22 @@ public void SdkLookup_Global_Json_Single_Digit_Patch_Rollup() // Specified SDK version: 9999.3.4-global-dummy // Exe: 9999.4.1, 9999.3.4-dummy - // Expected: no compatible version and a specific error message + // Expected: no compatible version RunTest() .Should().Fail() - .And.HaveStdErrContaining("A compatible installed .NET SDK for global.json version") - .And.NotHaveStdErrContaining("It was not possible to find any installed .NET SDKs"); + .And.NotFindCompatibleSdk(globalJsonPath, requestedVersion) + .And.FindAnySdk(true); // Add SDK versions AddAvailableSdkVersions("9999.3.3"); // Specified SDK version: 9999.3.4-global-dummy // Exe: 9999.4.1, 9999.3.4-dummy, 9999.3.3 - // Expected: no compatible version and a specific error message + // Expected: no compatible version RunTest() .Should().Fail() - .And.HaveStdErrContaining("A compatible installed .NET SDK for global.json version") - .And.NotHaveStdErrContaining("It was not possible to find any installed .NET SDKs"); + .And.NotFindCompatibleSdk(globalJsonPath, requestedVersion) + .And.FindAnySdk(true); // Add SDK versions AddAvailableSdkVersions("9999.3.4"); @@ -129,37 +130,38 @@ public void SdkLookup_Global_Json_Single_Digit_Patch_Rollup() public void SdkLookup_Global_Json_Two_Part_Patch_Rollup() { // Set specified SDK version = 9999.3.304-global-dummy - CopyGlobalJson("TwoPart-global.json"); + string globalJsonPath = CopyGlobalJson("TwoPart-global.json"); + string requestedVersion = "9999.3.304-global-dummy"; // Specified SDK version: 9999.3.304-global-dummy // Exe: empty - // Expected: no compatible version and a specific error messages + // Expected: no compatible version, no SDKs found RunTest() .Should().Fail() - .And.HaveStdErrContaining("A compatible installed .NET SDK for global.json version") - .And.HaveStdErrContaining("It was not possible to find any installed .NET SDKs"); + .And.NotFindCompatibleSdk(globalJsonPath, requestedVersion) + .And.FindAnySdk(false); // Add SDK versions AddAvailableSdkVersions("9999.3.57", "9999.3.4-dummy"); // Specified SDK version: 9999.3.304-global-dummy // Exe: 9999.3.57, 9999.3.4-dummy - // Expected: no compatible version and a specific error message + // Expected: no compatible version RunTest() .Should().Fail() - .And.HaveStdErrContaining("A compatible installed .NET SDK for global.json version") - .And.NotHaveStdErrContaining("It was not possible to find any installed .NET SDKs"); + .And.NotFindCompatibleSdk(globalJsonPath, requestedVersion) + .And.FindAnySdk(true); // Add SDK versions AddAvailableSdkVersions("9999.3.300", "9999.7.304-global-dummy"); // Specified SDK version: 9999.3.304-global-dummy // Exe: 9999.3.57, 9999.3.4-dummy, 9999.3.300, 9999.7.304-global-dummy - // Expected: no compatible version and a specific error message + // Expected: no compatible version RunTest() .Should().Fail() - .And.HaveStdErrContaining("A compatible installed .NET SDK for global.json version") - .And.NotHaveStdErrContaining("It was not possible to find any installed .NET SDKs"); + .And.NotFindCompatibleSdk(globalJsonPath, requestedVersion) + .And.FindAnySdk(true); // Add SDK versions AddAvailableSdkVersions("9999.3.304"); @@ -227,11 +229,10 @@ public void SdkLookup_Negative_Version() // Specified SDK version: none // Exe: -1.-1.-1 - // Expected: no compatible version and a specific error messages + // Expected: no compatible version, no SDKs found RunTest() .Should().Fail() - .And.HaveStdErrContaining("It was not possible to find any installed .NET SDKs") - .And.HaveStdErrContaining("Install a .NET SDK from"); + .And.FindAnySdk(false); // Add SDK versions AddAvailableSdkVersions("9999.0.4"); @@ -391,16 +392,13 @@ public void It_rolls_forward_as_expected(string policy, string requested, bool a if (expected == null) { result - .Should() - .Fail() - .And.HaveStdErrContaining($"A compatible installed .NET SDK for global.json version [{requested}] from [{globalJson}] was not found") - .And.HaveStdErrContaining($"Install the [{requested}] .NET SDK or update [{globalJson}] with an installed .NET SDK:"); + .Should().Fail() + .And.NotFindCompatibleSdk(globalJson, requested); } else { result - .Should() - .Pass() + .Should().Pass() .And.HaveStdErrContaining($"SDK path resolved to [{Path.Combine(ExecutableDotNet.BinPath, "sdk", expected)}]"); } } @@ -1001,12 +999,13 @@ private void AddAvailableSdkVersions(params string[] availableVersions) } // Put a global.json file in the cwd in order to specify a CLI - private void CopyGlobalJson(string globalJsonFileName) + private string CopyGlobalJson(string globalJsonFileName) { string destFile = Path.Combine(SharedState.CurrentWorkingDir, "global.json"); string srcFile = Path.Combine(SharedState.TestAssetsPath, globalJsonFileName); File.Copy(srcFile, destFile, true); + return destFile; } private static string FormatGlobalJson(string version = null, string policy = null, bool? allowPrerelease = null) diff --git a/src/installer/tests/HostActivation.Tests/SDKResolutionCommandResultExtensions.cs b/src/installer/tests/HostActivation.Tests/SDKResolutionCommandResultExtensions.cs new file mode 100644 index 0000000000000..322af9abf695d --- /dev/null +++ b/src/installer/tests/HostActivation.Tests/SDKResolutionCommandResultExtensions.cs @@ -0,0 +1,43 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.IO; +using FluentAssertions; +using Microsoft.DotNet.Cli.Build.Framework; + +namespace Microsoft.DotNet.CoreSetup.Test.HostActivation +{ + internal static class SDKResolutionCommandResultExtensions + { + public static AndConstraint FindAnySdk(this CommandResultAssertions assertion, bool shouldFindAnySdk) + { + string noSdkMessage = "No .NET SDKs were found"; + return shouldFindAnySdk + ? assertion.NotHaveStdErrContaining(noSdkMessage) + : assertion.HaveStdErrContaining(noSdkMessage) + .And.HaveStdErrContaining("Download a .NET SDK:"); + } + + public static AndConstraint NotFindCompatibleSdk(this CommandResultAssertions assertion, string globalJsonPath = null, string requestedVersion = null) + { + var constraint = assertion.HaveStdErrContaining("compatible .NET SDK was not found"); + + if (globalJsonPath is not null) + { + constraint = constraint.And.HaveStdErrContaining($"global.json file: {globalJsonPath}"); + } + + if (requestedVersion is not null) + { + constraint = constraint.And.HaveStdErrContaining($"Requested SDK version: {requestedVersion}"); + } + + if (globalJsonPath is not null && requestedVersion is not null) + { + constraint = constraint.And.HaveStdErrContaining($"Install the [{requestedVersion}] .NET SDK or update [{globalJsonPath}] to match an installed SDK."); + } + + return constraint; + } + } +} diff --git a/src/installer/tests/TestUtils/Assertions/AssertionScopeExtensions.cs b/src/installer/tests/TestUtils/Assertions/AssertionScopeExtensions.cs index 24a8e58ed8b8b..5dcc970d16fd1 100644 --- a/src/installer/tests/TestUtils/Assertions/AssertionScopeExtensions.cs +++ b/src/installer/tests/TestUtils/Assertions/AssertionScopeExtensions.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using FluentAssertions.Execution; diff --git a/src/native/corehost/apphost/apphost.windows.cpp b/src/native/corehost/apphost/apphost.windows.cpp index 7a3a94a960b39..bb113d84faa3b 100644 --- a/src/native/corehost/apphost/apphost.windows.cpp +++ b/src/native/corehost/apphost/apphost.windows.cpp @@ -50,6 +50,26 @@ namespace ::DeregisterEventSource(eventSource); } + bool try_get_url_from_line(const pal::string_t& line, pal::string_t& url) + { + const pal::char_t url_prefix[] = DOTNET_CORE_APPLAUNCH_URL _X("?"); + if (utils::starts_with(line, url_prefix, true)) + { + url.assign(line); + return true; + } + + const pal::char_t url_prefix_before_7_0[] = _X(" - ") DOTNET_CORE_APPLAUNCH_URL _X("?"); + if (utils::starts_with(line, url_prefix_before_7_0, true)) + { + size_t offset = utils::strlen(url_prefix_before_7_0) - utils::strlen(DOTNET_CORE_APPLAUNCH_URL) - 1; + url.assign(line.substr(offset, line.length() - offset)); + return true; + } + + return false; + } + void show_error_dialog(const pal::char_t *executable_name, int error_code) { pal::string_t gui_errors_disabled; @@ -58,17 +78,15 @@ namespace pal::string_t dialogMsg; pal::string_t url; - const pal::string_t url_prefix = _X(" - ") DOTNET_CORE_APPLAUNCH_URL _X("?"); if (error_code == StatusCode::CoreHostLibMissingFailure) { dialogMsg = pal::string_t(_X("To run this application, you must install .NET Desktop Runtime ")) + _STRINGIFY(COMMON_HOST_PKG_VER) + _X(" (") + get_arch() + _X(").\n\n"); pal::string_t line; pal::stringstream_t ss(g_buffered_errors); - while (std::getline(ss, line, _X('\n'))) { - if (starts_with(line, url_prefix, true)) + while (std::getline(ss, line, _X('\n'))) + { + if (try_get_url_from_line(line, url)) { - size_t offset = url_prefix.length() - pal::strlen(DOTNET_CORE_APPLAUNCH_URL) - 1; - url = line.substr(offset, line.length() - offset); break; } } @@ -77,28 +95,29 @@ namespace { // We don't have a great way of passing out different kinds of detailed error info across components, so // just match the expected error string. See fx_resolver.messages.cpp. - dialogMsg = pal::string_t(_X("To run this application, you must install missing frameworks for .NET.\n\n")); + dialogMsg = pal::string_t(INSTALL_OR_UPDATE_NET_ERROR_MESSAGE _X("\n\n")); pal::string_t line; pal::stringstream_t ss(g_buffered_errors); - while (std::getline(ss, line, _X('\n'))){ - const pal::string_t prefix = _X("The framework '"); - const pal::string_t suffix = _X("' was not found."); - const pal::string_t custom_prefix = _X(" _ "); - if (starts_with(line, prefix, true) && ends_with(line, suffix, true)) + while (std::getline(ss, line, _X('\n'))) + { + const pal::char_t prefix[] = _X("Framework: '"); + const pal::char_t prefix_before_7_0[] = _X("The framework '"); + const pal::char_t suffix_before_7_0[] = _X(" was not found."); + const pal::char_t custom_prefix[] = _X(" _ "); + if (utils::starts_with(line, prefix, true) + || (utils::starts_with(line, prefix_before_7_0, true) && utils::ends_with(line, suffix_before_7_0, true))) { dialogMsg.append(line); dialogMsg.append(_X("\n\n")); } - else if (starts_with(line, custom_prefix, true)) + else if (utils::starts_with(line, custom_prefix, true)) { dialogMsg.erase(); - dialogMsg.append(line.substr(custom_prefix.length())); + dialogMsg.append(line.substr(utils::strlen(custom_prefix))); dialogMsg.append(_X("\n\n")); } - else if (starts_with(line, url_prefix, true)) + else if (try_get_url_from_line(line, url)) { - size_t offset = url_prefix.length() - pal::strlen(DOTNET_CORE_APPLAUNCH_URL) - 1; - url = line.substr(offset, line.length() - offset); break; } } @@ -107,8 +126,9 @@ namespace { pal::string_t line; pal::stringstream_t ss(g_buffered_errors); - while (std::getline(ss, line, _X('\n'))) { - if (starts_with(line, _X("Bundle header version compatibility check failed."), true)) + while (std::getline(ss, line, _X('\n'))) + { + if (utils::starts_with(line, _X("Bundle header version compatibility check failed."), true)) { dialogMsg = pal::string_t(_X("To run this application, you must install .NET Desktop Runtime ")) + _STRINGIFY(COMMON_HOST_PKG_VER) + _X(" (") + get_arch() + _X(").\n\n"); url = get_download_url(); @@ -121,15 +141,20 @@ namespace return; } else + { return; + } - dialogMsg.append(_X("Would you like to download it now?")); + dialogMsg.append( + _X("Would you like to download it now?\n\n") + _X("Learn about framework resolution:\n") + DOTNET_APP_LAUNCH_FAILED_URL); assert(url.length() > 0); assert(is_gui_application()); url.append(_X("&gui=true")); - trace::verbose(_X("Showing error dialog for application: '%s' - error code: 0x%x - url: '%s'"), executable_name, error_code, url.c_str()); + trace::verbose(_X("Showing error dialog for application: '%s' - error code: 0x%x - url: '%s' - dialog message: %s"), executable_name, error_code, url.c_str(), dialogMsg.c_str()); if (::MessageBoxW(nullptr, dialogMsg.c_str(), executable_name, MB_ICONERROR | MB_YESNO) == IDYES) { // Open the URL in default browser diff --git a/src/native/corehost/fxr/command_line.cpp b/src/native/corehost/fxr/command_line.cpp index deb80b38b7f52..4912c29d60509 100644 --- a/src/native/corehost/fxr/command_line.cpp +++ b/src/native/corehost/fxr/command_line.cpp @@ -282,11 +282,12 @@ int command_line::parse_args_for_sdk_command( void command_line::print_muxer_info(const pal::string_t &dotnet_root) { trace::println(); - trace::println(_X("Host (useful for support):")); - trace::println(_X(" Version: %s"), _STRINGIFY(HOST_FXR_PKG_VER)); + trace::println(_X("Host:")); + trace::println(_X(" Version: %s"), _STRINGIFY(HOST_FXR_PKG_VER)); + trace::println(_X(" Architecture: %s"), get_arch()); pal::string_t commit = _STRINGIFY(REPO_COMMIT_HASH); - trace::println(_X(" Commit: %s"), commit.substr(0, 10).c_str()); + trace::println(_X(" Commit: %s"), commit.substr(0, 10).c_str()); trace::println(); trace::println(_X(".NET SDKs installed:")); @@ -303,9 +304,12 @@ void command_line::print_muxer_info(const pal::string_t &dotnet_root) } trace::println(); - trace::println(_X("To install additional .NET runtimes or SDKs:")); + trace::println(_X("Download .NET:")); trace::println(_X(" %s"), DOTNET_CORE_DOWNLOAD_URL); -} + + trace::println(); + trace::println(_X("Learn about .NET Runtimes and SDKs:")); + trace::println(_X(" %s"), DOTNET_INFO_URL);} void command_line::print_muxer_usage(bool is_sdk_present) { diff --git a/src/native/corehost/fxr/framework_info.cpp b/src/native/corehost/fxr/framework_info.cpp index 0859f6e57c9d7..439cde6e3980e 100644 --- a/src/native/corehost/fxr/framework_info.cpp +++ b/src/native/corehost/fxr/framework_info.cpp @@ -35,10 +35,11 @@ bool compare_by_name_and_version(const framework_info &a, const framework_info & /*static*/ void framework_info::get_all_framework_infos( const pal::string_t& own_dir, const pal::string_t& fx_name, + bool disable_multilevel_lookup, std::vector* framework_infos) { std::vector hive_dir; - get_framework_and_sdk_locations(own_dir, &hive_dir); + get_framework_and_sdk_locations(own_dir, disable_multilevel_lookup, &hive_dir); int32_t hive_depth = 0; @@ -97,7 +98,7 @@ bool compare_by_name_and_version(const framework_info &a, const framework_info & /*static*/ bool framework_info::print_all_frameworks(const pal::string_t& own_dir, const pal::string_t& leading_whitespace) { std::vector framework_infos; - get_all_framework_infos(own_dir, _X(""), &framework_infos); + get_all_framework_infos(own_dir, _X(""), /*disable_multilevel_lookup*/ true, &framework_infos); for (framework_info info : framework_infos) { trace::println(_X("%s%s %s [%s]"), leading_whitespace.c_str(), info.name.c_str(), info.version.as_str().c_str(), info.path.c_str()); diff --git a/src/native/corehost/fxr/framework_info.h b/src/native/corehost/fxr/framework_info.h index e4eea09cb14c2..8e95bdea0cc61 100644 --- a/src/native/corehost/fxr/framework_info.h +++ b/src/native/corehost/fxr/framework_info.h @@ -18,6 +18,7 @@ struct framework_info static void get_all_framework_infos( const pal::string_t& own_dir, const pal::string_t& fx_name, + bool disable_multilevel_lookup, std::vector* framework_infos); static bool print_all_frameworks(const pal::string_t& own_dir, const pal::string_t& leading_whitespace); diff --git a/src/native/corehost/fxr/fx_muxer.cpp b/src/native/corehost/fxr/fx_muxer.cpp index 8647130c18dc1..a97da2f90f7d6 100644 --- a/src/native/corehost/fxr/fx_muxer.cpp +++ b/src/native/corehost/fxr/fx_muxer.cpp @@ -466,7 +466,7 @@ namespace } else { - rc = fx_resolver_t::resolve_frameworks_for_app(host_info, override_settings, app_config, fx_definitions); + rc = fx_resolver_t::resolve_frameworks_for_app(host_info, app_config.get_is_multilevel_lookup_disabled(), override_settings, app_config, fx_definitions, mode == host_mode_t::muxer ? app_candidate.c_str() : nullptr); if (rc != StatusCode::Success) { return rc; @@ -620,7 +620,7 @@ namespace return StatusCode::InvalidConfigFile; } - rc = fx_resolver_t::resolve_frameworks_for_app(host_info, override_settings, app_config, fx_definitions); + rc = fx_resolver_t::resolve_frameworks_for_app(host_info, app_config.get_is_multilevel_lookup_disabled(), override_settings, app_config, fx_definitions); if (rc != StatusCode::Success) return rc; @@ -1066,15 +1066,17 @@ int fx_muxer_t::handle_cli( } else if (pal::strcasecmp(_X("--info"), argv[1]) == 0) { + resolver.print_global_file_path(); command_line::print_muxer_info(host_info.dotnet_root); return StatusCode::Success; } - trace::error(_X("Could not execute because the application was not found or a compatible .NET SDK is not installed.")); - trace::error(_X("Possible reasons for this include:")); - trace::error(_X(" * You intended to execute a .NET program:")); - trace::error(_X(" The application '%s' does not exist."), app_candidate.c_str()); - trace::error(_X(" * You intended to execute a .NET SDK command:")); + trace::error( + _X("The command could not be loaded, possibly because:\n") + _X(" * You intended to execute a .NET application:\n") + _X(" The application '%s' does not exist.\n") + _X(" * You intended to execute a .NET SDK command:"), + app_candidate.c_str()); resolver.print_resolution_error(host_info.dotnet_root, _X(" ")); return StatusCode::LibHostSdkFindFailure; @@ -1122,6 +1124,7 @@ int fx_muxer_t::handle_cli( if (pal::strcasecmp(_X("--info"), argv[1]) == 0) { + resolver.print_global_file_path(); command_line::print_muxer_info(host_info.dotnet_root); } diff --git a/src/native/corehost/fxr/fx_resolver.cpp b/src/native/corehost/fxr/fx_resolver.cpp index 5a5fef7aac0ec..df93428b74ed6 100644 --- a/src/native/corehost/fxr/fx_resolver.cpp +++ b/src/native/corehost/fxr/fx_resolver.cpp @@ -190,7 +190,8 @@ namespace fx_definition_t* resolve_framework_reference( const fx_reference_t & fx_ref, const pal::string_t & oldest_requested_version, - const pal::string_t & dotnet_dir) + const pal::string_t & dotnet_dir, + const bool disable_multilevel_lookup) { #if defined(DEBUG) assert(!fx_ref.get_fx_name().empty()); @@ -205,7 +206,7 @@ namespace fx_ref.get_fx_name().c_str(), fx_ref.get_fx_version().c_str()); std::vector hive_dir; - get_framework_and_sdk_locations(dotnet_dir, &hive_dir); + get_framework_and_sdk_locations(dotnet_dir, disable_multilevel_lookup, &hive_dir); pal::string_t selected_fx_dir; pal::string_t selected_fx_version; @@ -286,7 +287,7 @@ namespace if (selected_fx_dir.empty()) { - trace::error(_X("It was not possible to find any compatible framework version")); + trace::verbose(_X("It was not possible to find any compatible framework version")); return nullptr; } @@ -394,10 +395,12 @@ void fx_resolver_t::update_newest_references( // InvalidConfigFile - reading of a runtime config for some of the processed frameworks has failed. StatusCode fx_resolver_t::read_framework( const host_startup_info_t & host_info, + bool disable_multilevel_lookup, const runtime_config_t::settings_t& override_settings, const runtime_config_t & config, const fx_reference_t * effective_parent_fx_ref, - fx_definition_vector_t & fx_definitions) + fx_definition_vector_t & fx_definitions, + const pal::char_t* app_display_name) { // This reconciles duplicate references to minimize the number of resolve retries. update_newest_references(config); @@ -439,10 +442,17 @@ StatusCode fx_resolver_t::read_framework( m_effective_fx_references[fx_name] = new_effective_fx_ref; // Resolve the effective framework reference against the the existing physical framework folders - fx_definition_t* fx = resolve_framework_reference(new_effective_fx_ref, m_oldest_fx_references[fx_name].get_fx_version(), host_info.dotnet_root); + fx_definition_t* fx = resolve_framework_reference(new_effective_fx_ref, m_oldest_fx_references[fx_name].get_fx_version(), host_info.dotnet_root, disable_multilevel_lookup); if (fx == nullptr) { - display_missing_framework_error(fx_name, new_effective_fx_ref.get_fx_version(), pal::string_t(), host_info.dotnet_root); + trace::error( + INSTALL_OR_UPDATE_NET_ERROR_MESSAGE + _X("\n\n") + _X("App: %s\n") + _X("Architecture: %s"), + app_display_name != nullptr ? app_display_name : host_info.host_path.c_str(), + get_arch()); + display_missing_framework_error(fx_name, new_effective_fx_ref.get_fx_version(), pal::string_t(), host_info.dotnet_root, disable_multilevel_lookup); return FrameworkMissingFailure; } @@ -471,7 +481,7 @@ StatusCode fx_resolver_t::read_framework( return StatusCode::InvalidConfigFile; } - rc = read_framework(host_info, override_settings, new_config, &new_effective_fx_ref, fx_definitions); + rc = read_framework(host_info, disable_multilevel_lookup, override_settings, new_config, &new_effective_fx_ref, fx_definitions, app_display_name); if (rc) { break; // Error case @@ -511,9 +521,11 @@ fx_resolver_t::fx_resolver_t() StatusCode fx_resolver_t::resolve_frameworks_for_app( const host_startup_info_t & host_info, + bool disable_multilevel_lookup, const runtime_config_t::settings_t& override_settings, const runtime_config_t & app_config, - fx_definition_vector_t & fx_definitions) + fx_definition_vector_t & fx_definitions, + const pal::char_t* app_display_name) { fx_resolver_t resolver; @@ -523,7 +535,7 @@ StatusCode fx_resolver_t::resolve_frameworks_for_app( do { fx_definitions.resize(1); // Erase any existing frameworks for re-try - rc = resolver.read_framework(host_info, override_settings, app_config, /*effective_parent_fx_ref*/ nullptr, fx_definitions); + rc = resolver.read_framework(host_info, disable_multilevel_lookup, override_settings, app_config, /*effective_parent_fx_ref*/ nullptr, fx_definitions, app_display_name); } while (rc == StatusCode::FrameworkCompatRetry && retry_count++ < Max_Framework_Resolve_Retries); assert(retry_count < Max_Framework_Resolve_Retries); diff --git a/src/native/corehost/fxr/fx_resolver.h b/src/native/corehost/fxr/fx_resolver.h index 1db5a7d1ebab3..35c6fd250af5a 100644 --- a/src/native/corehost/fxr/fx_resolver.h +++ b/src/native/corehost/fxr/fx_resolver.h @@ -16,9 +16,11 @@ class fx_resolver_t public: static StatusCode resolve_frameworks_for_app( const host_startup_info_t& host_info, + bool disable_multilevel_lookup, const runtime_config_t::settings_t& override_settings, const runtime_config_t& app_config, - fx_definition_vector_t& fx_definitions); + fx_definition_vector_t& fx_definitions, + const pal::char_t* app_display_name = nullptr); static bool is_config_compatible_with_frameworks( const runtime_config_t& config, @@ -31,10 +33,12 @@ class fx_resolver_t const runtime_config_t& config); StatusCode read_framework( const host_startup_info_t& host_info, + bool disable_multilevel_lookup, const runtime_config_t::settings_t& override_settings, const runtime_config_t& config, const fx_reference_t * effective_parent_fx_ref, - fx_definition_vector_t& fx_definitions); + fx_definition_vector_t& fx_definitions, + const pal::char_t* app_display_name); static StatusCode reconcile_fx_references_helper( const fx_reference_t& lower_fx_ref, @@ -49,7 +53,8 @@ class fx_resolver_t const pal::string_t& fx_name, const pal::string_t& fx_version, const pal::string_t& fx_dir, - const pal::string_t& dotnet_root); + const pal::string_t& dotnet_root, + bool disable_multilevel_lookup); static void display_incompatible_framework_error( const pal::string_t& higher, const fx_reference_t& lower); diff --git a/src/native/corehost/fxr/fx_resolver.messages.cpp b/src/native/corehost/fxr/fx_resolver.messages.cpp index 9f64a793c16cb..e67344d9449da 100644 --- a/src/native/corehost/fxr/fx_resolver.messages.cpp +++ b/src/native/corehost/fxr/fx_resolver.messages.cpp @@ -93,51 +93,57 @@ void fx_resolver_t::display_missing_framework_error( const pal::string_t& fx_name, const pal::string_t& fx_version, const pal::string_t& fx_dir, - const pal::string_t& dotnet_root) + const pal::string_t& dotnet_root, + bool disable_multilevel_lookup) { std::vector framework_infos; pal::string_t fx_ver_dirs; if (fx_dir.length()) { fx_ver_dirs = fx_dir; - framework_info::get_all_framework_infos(get_directory(fx_dir), fx_name, &framework_infos); + framework_info::get_all_framework_infos(get_directory(fx_dir), fx_name, disable_multilevel_lookup, &framework_infos); } else { fx_ver_dirs = dotnet_root; } - framework_info::get_all_framework_infos(dotnet_root, fx_name, &framework_infos); + framework_info::get_all_framework_infos(dotnet_root, fx_name, disable_multilevel_lookup, &framework_infos); // Display the error message about missing FX. if (fx_version.length()) { - trace::error(_X("The framework '%s', version '%s' (%s) was not found."), fx_name.c_str(), fx_version.c_str(), get_arch()); + trace::error(_X("Framework: '%s', version '%s' (%s)"), fx_name.c_str(), fx_version.c_str(), get_arch()); } else { - trace::error(_X("The framework '%s' (%s) was not found."), fx_name.c_str(), get_arch()); + trace::error(_X("Framework: '%s', (%s)"), fx_name.c_str(), get_arch()); } + trace::error(_X(".NET location: %s\n"), dotnet_root.c_str()); + if (framework_infos.size()) { - trace::error(_X(" - The following frameworks were found:")); + trace::error(_X("The following frameworks were found:")); for (const framework_info& info : framework_infos) { - trace::error(_X(" %s at [%s]"), info.version.as_str().c_str(), info.path.c_str()); + trace::error(_X(" %s at [%s]"), info.version.as_str().c_str(), info.path.c_str()); } } else { - trace::error(_X(" - No frameworks were found.")); + trace::error(_X("No frameworks were found.")); } pal::string_t url = get_download_url(fx_name.c_str(), fx_version.c_str()); - trace::error(_X("")); - trace::error(_X("You can resolve the problem by installing the specified framework and/or SDK.")); - trace::error(_X("")); - trace::error(_X("The specified framework can be found at:")); - trace::error(_X(" - %s"), url.c_str()); + trace::error( + _X("\n") + _X("Learn about framework resolution:\n") + DOTNET_APP_LAUNCH_FAILED_URL + _X("\n\n") + _X("To install missing framework, download:\n") + _X("%s"), + url.c_str()); } void fx_resolver_t::display_incompatible_loaded_framework_error( diff --git a/src/native/corehost/fxr/hostfxr.cpp b/src/native/corehost/fxr/hostfxr.cpp index 4011dbc439785..8790083e9885d 100644 --- a/src/native/corehost/fxr/hostfxr.cpp +++ b/src/native/corehost/fxr/hostfxr.cpp @@ -425,7 +425,7 @@ SHARED_API int32_t HOSTFXR_CALLTYPE hostfxr_get_dotnet_environment_info( } std::vector framework_infos; - framework_info::get_all_framework_infos(dotnet_dir, _X(""), &framework_infos); + framework_info::get_all_framework_infos(dotnet_dir, _X(""), /*disable_multilevel_lookup*/ true, &framework_infos); std::vector environment_framework_infos; std::vector framework_versions; diff --git a/src/native/corehost/fxr/sdk_info.cpp b/src/native/corehost/fxr/sdk_info.cpp index 1c2f37a7d079c..af52b41c5690b 100644 --- a/src/native/corehost/fxr/sdk_info.cpp +++ b/src/native/corehost/fxr/sdk_info.cpp @@ -45,7 +45,7 @@ void sdk_info::get_all_sdk_infos( std::vector* sdk_infos) { std::vector hive_dir; - get_framework_and_sdk_locations(own_dir, &hive_dir); + get_framework_and_sdk_locations(own_dir, /*disable_multilevel_lookup*/ true, &hive_dir); int32_t hive_depth = 0; diff --git a/src/native/corehost/fxr/sdk_resolver.cpp b/src/native/corehost/fxr/sdk_resolver.cpp index abc47c7f17c39..3a1f01105b0b7 100644 --- a/src/native/corehost/fxr/sdk_resolver.cpp +++ b/src/native/corehost/fxr/sdk_resolver.cpp @@ -61,7 +61,7 @@ pal::string_t sdk_resolver::resolve(const pal::string_t& dotnet_root, bool print fx_ver_t resolved_version; vector locations; - get_framework_and_sdk_locations(dotnet_root, &locations); + get_framework_and_sdk_locations(dotnet_root, /*disable_multilevel_lookup*/ true, &locations); for (auto&& dir : locations) { @@ -85,38 +85,65 @@ pal::string_t sdk_resolver::resolve(const pal::string_t& dotnet_root, bool print return {}; } -void sdk_resolver::print_resolution_error(const pal::string_t& dotnet_root, const pal::char_t *prefix) const +void sdk_resolver::print_global_file_path() +{ + trace::println( + _X("\n") + _X("global.json file:\n") + _X(" %s"), + global_file.empty() ? _X("Not found") : global_file.c_str()); +} + +void sdk_resolver::print_resolution_error(const pal::string_t& dotnet_root, const pal::char_t *main_error_prefix) const { bool sdk_exists = false; - const pal::char_t *no_sdk_message = _X("It was not possible to find any installed .NET SDKs."); + const pal::char_t *no_sdk_message = _X("No .NET SDKs were found."); if (!version.is_empty()) { pal::string_t requested = version.as_str(); - if (!global_file.empty()) + trace::error( + _X("%sA compatible .NET SDK was not found.\n") + _X("\n") + _X("Requested SDK version: %s"), + main_error_prefix, + requested.c_str()); + + bool has_global_file = !global_file.empty(); + if (has_global_file) + trace::error(_X("global.json file: %s"), global_file.c_str()); + + trace::error(_X("\nInstalled SDKs:")); + sdk_exists = sdk_info::print_all_sdks(dotnet_root, _X("")); + if (!sdk_exists) + trace::error(no_sdk_message); + + trace::error(_X("")); + if (has_global_file) { - trace::error(_X("%sA compatible installed .NET SDK for global.json version [%s] from [%s] was not found."), prefix, requested.c_str(), global_file.c_str()); - trace::error(_X("%sInstall the [%s] .NET SDK or update [%s] with an installed .NET SDK:"), prefix, requested.c_str(), global_file.c_str()); + trace::error(_X("Install the [%s] .NET SDK or update [%s] to match an installed SDK."), requested.c_str(), global_file.c_str()); } else { - trace::error(_X("%sA compatible installed .NET SDK version [%s] was not found."), prefix, requested.c_str()); - trace::error(_X("%sInstall the [%s] .NET SDK or create a global.json file with an installed .NET SDK:"), prefix, requested.c_str()); + trace::error(_X("Install the [%s] .NET SDK or create a global.json file matching an installed SDK."), requested.c_str()); } - - sdk_exists = sdk_info::print_all_sdks(dotnet_root, pal::string_t{prefix}.append(_X(" "))); - if (!sdk_exists) - trace::error(_X("%s %s"), prefix, no_sdk_message); } else { - trace::error(_X("%s%s"), prefix, no_sdk_message); + trace::error(_X("%s%s"), main_error_prefix, no_sdk_message); } if (!sdk_exists) { - trace::error(_X("%sInstall a .NET SDK from:"), prefix); - trace::error(_X("%s %s"), prefix, DOTNET_CORE_DOWNLOAD_URL); + trace::error( + _X("\n") + _X("Download a .NET SDK:\n") + DOTNET_CORE_DOWNLOAD_URL); } + + trace::error( + _X("\n") + _X("Learn about SDK resolution:\n") + DOTNET_SDK_NOT_FOUND_URL); } sdk_resolver sdk_resolver::from_nearest_global_file(bool allow_prerelease) diff --git a/src/native/corehost/fxr/sdk_resolver.h b/src/native/corehost/fxr/sdk_resolver.h index 40ac774d398e1..15342603ab764 100644 --- a/src/native/corehost/fxr/sdk_resolver.h +++ b/src/native/corehost/fxr/sdk_resolver.h @@ -41,6 +41,7 @@ class sdk_resolver pal::string_t resolve(const pal::string_t& dotnet_root, bool print_errors = true) const; + void print_global_file_path(); void print_resolution_error(const pal::string_t& dotnet_root, const pal::char_t *prefix) const; static sdk_resolver from_nearest_global_file(bool allow_prerelease = true); diff --git a/src/native/corehost/fxr_resolver.cpp b/src/native/corehost/fxr_resolver.cpp index b59c59f10fd80..ef92136e80bb5 100644 --- a/src/native/corehost/fxr_resolver.cpp +++ b/src/native/corehost/fxr_resolver.cpp @@ -112,8 +112,8 @@ bool fxr_resolver::try_get_path(const pal::string_t& root_path, pal::string_t* o dotnet_root_env_var_name.c_str(), self_registered_message.c_str()); trace::error(_X("")); - trace::error(_X("The .NET runtime can be found at:")); - trace::error(_X(" - %s&apphost_version=%s"), get_download_url().c_str(), _STRINGIFY(COMMON_HOST_PKG_VER)); + trace::error(_X("Download the .NET runtime:")); + trace::error(_X("%s&apphost_version=%s"), get_download_url().c_str(), _STRINGIFY(COMMON_HOST_PKG_VER)); return false; } diff --git a/src/native/corehost/host_startup_info.h b/src/native/corehost/host_startup_info.h index 9571d1b4902cc..98d22942f65e7 100644 --- a/src/native/corehost/host_startup_info.h +++ b/src/native/corehost/host_startup_info.h @@ -27,8 +27,8 @@ struct host_startup_info_t static int get_host_path(int argc, const pal::char_t* argv[], pal::string_t* host_path); pal::string_t host_path; // The path to the current hosting binary. - pal::string_t dotnet_root; // The path to the framework. - pal::string_t app_path; // For apphost, the path to the app dll; for muxer, not applicable as this information is not yet parsed. + pal::string_t dotnet_root; // The path to the .NET install. + pal::string_t app_path; // For apphost, the path to the app dll. For muxer, this is invalid and does not point to the app (the app path is not yet parsed). }; #endif // __HOST_STARTUP_INFO_H_ diff --git a/src/native/corehost/hostmisc/utils.cpp b/src/native/corehost/hostmisc/utils.cpp index 17d2660d9be7a..977d3d0e4d53d 100644 --- a/src/native/corehost/hostmisc/utils.cpp +++ b/src/native/corehost/hostmisc/utils.cpp @@ -29,23 +29,32 @@ bool coreclr_exists_in_dir(const pal::string_t& candidate) return pal::file_exists(test); } -bool ends_with(const pal::string_t& value, const pal::string_t& suffix, bool match_case) +bool utils::starts_with(const pal::string_t& value, const pal::char_t* prefix, size_t prefix_len, bool match_case) +{ + // Cannot start with an empty string. + if (prefix_len == 0) + return false; + + auto cmp = match_case ? pal::strncmp : pal::strncasecmp; + return (value.size() >= prefix_len) && + cmp(value.c_str(), prefix, prefix_len) == 0; +} + +bool utils::ends_with(const pal::string_t& value, const pal::char_t* suffix, size_t suffix_len, bool match_case) { auto cmp = match_case ? pal::strcmp : pal::strcasecmp; - return (value.size() >= suffix.size()) && - cmp(value.c_str() + value.size() - suffix.size(), suffix.c_str()) == 0; + return (value.size() >= suffix_len) && + cmp(value.c_str() + value.size() - suffix_len, suffix) == 0; +} + +bool ends_with(const pal::string_t& value, const pal::string_t& suffix, bool match_case) +{ + return utils::ends_with(value, suffix.c_str(), suffix.size(), match_case); } bool starts_with(const pal::string_t& value, const pal::string_t& prefix, bool match_case) { - if (prefix.empty()) - { - // Cannot start with an empty string. - return false; - } - auto cmp = match_case ? pal::strncmp : pal::strncasecmp; - return (value.size() >= prefix.size()) && - cmp(value.c_str(), prefix.c_str(), prefix.size()) == 0; + return utils::starts_with(value, prefix.c_str(), prefix.size(), match_case); } void append_path(pal::string_t* path1, const pal::char_t* path2) @@ -284,9 +293,9 @@ bool multilevel_lookup_enabled() return multilevel_lookup; } -void get_framework_and_sdk_locations(const pal::string_t& dotnet_dir, std::vector* locations) +void get_framework_and_sdk_locations(const pal::string_t& dotnet_dir, const bool disable_multilevel_lookup, std::vector* locations) { - bool multilevel_lookup = multilevel_lookup_enabled(); + bool multilevel_lookup = disable_multilevel_lookup ? false : multilevel_lookup_enabled(); // Multi-level lookup will look for the most appropriate version in several locations // by following the priority rank below: @@ -304,8 +313,11 @@ void get_framework_and_sdk_locations(const pal::string_t& dotnet_dir, std::vecto locations->push_back(dotnet_dir_temp); } + if (!multilevel_lookup) + return; + std::vector global_dirs; - if (multilevel_lookup && pal::get_global_dotnet_dirs(&global_dirs)) + if (pal::get_global_dotnet_dirs(&global_dirs)) { for (pal::string_t dir : global_dirs) { diff --git a/src/native/corehost/hostmisc/utils.h b/src/native/corehost/hostmisc/utils.h index b4e31b599fac1..9abc6f3fc4874 100644 --- a/src/native/corehost/hostmisc/utils.h +++ b/src/native/corehost/hostmisc/utils.h @@ -19,10 +19,42 @@ #define DOTNET_CORE_DOWNLOAD_URL _X("https://aka.ms/dotnet-download") #define DOTNET_CORE_APPLAUNCH_URL _X("https://aka.ms/dotnet-core-applaunch") +#define DOTNET_INFO_URL _X("https://aka.ms/dotnet/runtimes-sdk-info") +#define DOTNET_APP_LAUNCH_FAILED_URL _X("https://aka.ms/dotnet/app-launch-failed") +#define DOTNET_SDK_NOT_FOUND_URL _X("https://aka.ms/dotnet/sdk-not-found") + +// This message is defined here for consistency between errors on the command line and GUI (Windows apphost). +#define INSTALL_OR_UPDATE_NET_ERROR_MESSAGE _X("You must install or update .NET to run this application.") + #define RUNTIME_STORE_DIRECTORY_NAME _X("store") bool ends_with(const pal::string_t& value, const pal::string_t& suffix, bool match_case); bool starts_with(const pal::string_t& value, const pal::string_t& prefix, bool match_case); + +namespace utils +{ + template + inline constexpr size_t strlen(const pal::char_t(&)[L]) + { + return L - 1; + } + + bool ends_with(const pal::string_t& value, const pal::char_t *suffix, size_t suffix_len, bool match_case); + bool starts_with(const pal::string_t& value, const pal::char_t* prefix, size_t prefix_len, bool match_case); + + template + bool ends_with(const pal::string_t& value, const pal::char_t (&suffix)[L], bool match_case) + { + return ends_with(value, suffix, L - 1, match_case); + } + + template + bool starts_with(const pal::string_t& value, const pal::char_t (&prefix)[L], bool match_case) + { + return starts_with(value, prefix, L - 1, match_case); + } +} + pal::string_t strip_executable_ext(const pal::string_t& filename); pal::string_t get_directory(const pal::string_t& path); pal::string_t strip_file_ext(const pal::string_t& path); @@ -39,7 +71,7 @@ pal::string_t get_current_runtime_id(bool use_fallback); bool get_env_shared_store_dirs(std::vector* dirs, const pal::string_t& arch, const pal::string_t& tfm); bool get_global_shared_store_dirs(std::vector* dirs, const pal::string_t& arch, const pal::string_t& tfm); bool multilevel_lookup_enabled(); -void get_framework_and_sdk_locations(const pal::string_t& dotnet_dir, std::vector* locations); +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); size_t index_of_non_numeric(const pal::string_t& str, size_t i); bool try_stou(const pal::string_t& str, unsigned* num); diff --git a/src/native/corehost/runtime_config.cpp b/src/native/corehost/runtime_config.cpp index a6bb064ba7151..bbfd8f89653ae 100644 --- a/src/native/corehost/runtime_config.cpp +++ b/src/native/corehost/runtime_config.cpp @@ -429,6 +429,47 @@ const pal::string_t& runtime_config_t::get_tfm() const return m_tfm; } +const uint32_t runtime_config_t::get_compat_major_version_from_tfm() const +{ + assert(m_valid); + + // TFM is in form + // - netcoreapp#.# for <= 3.1 + // - net#.# for >= 5.0 + // In theory it could contain a suffix like `net6.0-windows` (or more than one) + // or it may lack the minor version like `net6`. SDK will normalize this, but the runtime should not 100% rely on it + + if (m_tfm.empty()) + return runtime_config_t::unknown_version; + + size_t majorVersionStartIndex; + const pal::char_t netcoreapp_prefix[] = _X("netcoreapp"); + if (utils::starts_with(m_tfm, netcoreapp_prefix, true)) + { + majorVersionStartIndex = utils::strlen(netcoreapp_prefix); + } + else + { + majorVersionStartIndex = utils::strlen(_X("net")); + } + + if (majorVersionStartIndex >= m_tfm.length()) + return runtime_config_t::unknown_version; + + size_t majorVersionEndIndex = index_of_non_numeric(m_tfm, majorVersionStartIndex); + if (majorVersionEndIndex == pal::string_t::npos || majorVersionEndIndex == majorVersionStartIndex) + return runtime_config_t::unknown_version; + + return static_cast(std::stoul(m_tfm.substr(majorVersionStartIndex, majorVersionEndIndex - majorVersionStartIndex))); +} + +bool runtime_config_t::get_is_multilevel_lookup_disabled() const +{ + // Starting with .NET 7, multi-level lookup is fully disabled + unsigned long compat_major_version = get_compat_major_version_from_tfm(); + return (compat_major_version >= 7 || compat_major_version == runtime_config_t::unknown_version); +} + bool runtime_config_t::get_is_framework_dependent() const { return m_is_framework_dependent; diff --git a/src/native/corehost/runtime_config.h b/src/native/corehost/runtime_config.h index f093dda3602e1..c7b01d40baa12 100644 --- a/src/native/corehost/runtime_config.h +++ b/src/native/corehost/runtime_config.h @@ -33,6 +33,7 @@ class runtime_config_t const pal::string_t& get_path() const { return m_path; } const pal::string_t& get_dev_path() const { return m_dev_path; } const pal::string_t& get_tfm() const; + bool get_is_multilevel_lookup_disabled() const; const std::list& get_probe_paths() const; bool get_is_framework_dependent() const; bool parse_opts(const json_parser_t::value_t& opts); @@ -41,7 +42,10 @@ class runtime_config_t const fx_reference_vector_t& get_included_frameworks() const { return m_included_frameworks; } void set_fx_version(pal::string_t version); + static constexpr int unknown_version = std::numeric_limits::max(); + private: + const uint32_t get_compat_major_version_from_tfm() const; bool ensure_parsed(); //todo: const runtime_config_t* defaults bool ensure_dev_config_parsed();