From 90fcb774aed66ece655a6bdb52eec77eda185d56 Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Thu, 9 Jun 2022 16:09:22 -0700 Subject: [PATCH] [release/6.0] Host error message updates (#70059) * Update error messages for framework and SDK resolution failure Partial backport of #67022 * Update error message for runtime not found Backport of #67564 --- .../DotnetArgValidation.cs | 2 +- ...meworkResolutionCommandResultExtensions.cs | 20 +++-- ...rdAndRollForwardOnNoCandidateFxSettings.cs | 5 +- .../RollForwardOnNoCandidateFx.cs | 46 ++++++----- .../RollForwardOnNoCandidateFxSettings.cs | 16 ++-- .../RollForwardPreReleaseOnly.cs | 20 +++-- .../RollForwardReleaseOnly.cs | 15 ++-- .../RollForwardSettings.cs | 8 +- .../MultiArchInstallLocation.cs | 22 ++--- .../MultilevelSDKLookup.cs | 25 +++--- .../PortableAppActivation.cs | 10 ++- .../tests/HostActivation.Tests/SDKLookup.cs | 45 +++++------ .../SDKResolutionCommandResultExtensions.cs | 43 ++++++++++ .../StandaloneAppActivation.cs | 26 +++--- .../corehost/apphost/apphost.windows.cpp | 80 ++++++++++++++----- src/native/corehost/fxr/command_line.cpp | 14 ++-- src/native/corehost/fxr/fx_muxer.cpp | 15 ++-- src/native/corehost/fxr/fx_resolver.cpp | 19 +++-- src/native/corehost/fxr/fx_resolver.h | 6 +- .../corehost/fxr/fx_resolver.messages.cpp | 25 +++--- src/native/corehost/fxr/sdk_resolver.cpp | 55 +++++++++---- src/native/corehost/fxr/sdk_resolver.h | 1 + src/native/corehost/fxr_resolver.cpp | 34 +++++--- src/native/corehost/host_startup_info.h | 4 +- src/native/corehost/hostmisc/utils.cpp | 31 ++++--- src/native/corehost/hostmisc/utils.h | 35 ++++++++ 26 files changed, 413 insertions(+), 209 deletions(-) create mode 100644 src/installer/tests/HostActivation.Tests/SDKResolutionCommandResultExtensions.cs 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 a0b6feda95774..01c613d1e35ba 100644 --- a/src/installer/tests/HostActivation.Tests/FrameworkResolution/FrameworkResolutionCommandResultExtensions.cs +++ b/src/installer/tests/HostActivation.Tests/FrameworkResolution/FrameworkResolutionCommandResultExtensions.cs @@ -30,10 +30,10 @@ public static AndConstraint ShouldHaveResolvedFramework /// Constraint public static AndConstraint ShouldHaveResolvedFrameworkOrFailToFind(this CommandResult result, string resolvedFrameworkName, string resolvedFrameworkVersion) { - if (resolvedFrameworkName == null || resolvedFrameworkVersion == null || + if (resolvedFrameworkName == null || resolvedFrameworkVersion == null || resolvedFrameworkVersion == FrameworkResolutionBase.ResolvedFramework.NotFound) { - return result.ShouldFailToFindCompatibleFrameworkVersion(); + return result.ShouldFailToFindCompatibleFrameworkVersion(resolvedFrameworkName); } else { @@ -41,15 +41,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( @@ -91,7 +97,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/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/MultiArchInstallLocation.cs b/src/installer/tests/HostActivation.Tests/MultiArchInstallLocation.cs index 3829a30e682e0..784b0930abed8 100644 --- a/src/installer/tests/HostActivation.Tests/MultiArchInstallLocation.cs +++ b/src/installer/tests/HostActivation.Tests/MultiArchInstallLocation.cs @@ -132,17 +132,17 @@ public void EnvironmentVariable_DotnetRootPathExistsButHasNoHost() using (TestOnlyProductBehavior.Enable(appExe)) { Command.Create(appExe) - .EnableTracingAndCaptureOutputs() - .DotNetRoot(projDir) - .MultilevelLookup(false) - .EnvironmentVariable( - Constants.TestOnlyEnvironmentVariables.GloballyRegisteredPath, - sharedTestState.InstallLocation) - .Execute() - .Should().Fail() - .And.HaveUsedDotNetRootInstallLocation(projDir, fixture.CurrentRid) - // If DOTNET_ROOT points to a folder that exists we assume that there's a dotnet installation in it - .And.HaveStdErrContaining($"A fatal error occurred. The required library {RuntimeInformationExtensions.GetSharedLibraryFileNameForCurrentPlatform ("hostfxr")} could not be found."); + .EnableTracingAndCaptureOutputs() + .DotNetRoot(projDir) + .MultilevelLookup(false) + .EnvironmentVariable( + Constants.TestOnlyEnvironmentVariables.GloballyRegisteredPath, + sharedTestState.InstallLocation) + .Execute() + .Should().Fail() + .And.HaveUsedDotNetRootInstallLocation(projDir, fixture.CurrentRid) + // If DOTNET_ROOT points to a folder that exists we assume that there's a dotnet installation in it + .And.HaveStdErrContaining($"The required library {RuntimeInformationExtensions.GetSharedLibraryFileNameForCurrentPlatform("hostfxr")} could not be found."); } } diff --git a/src/installer/tests/HostActivation.Tests/MultilevelSDKLookup.cs b/src/installer/tests/HostActivation.Tests/MultilevelSDKLookup.cs index 76d11337c03ab..05dc79548a5c7 100644 --- a/src/installer/tests/HostActivation.Tests/MultilevelSDKLookup.cs +++ b/src/installer/tests/HostActivation.Tests/MultilevelSDKLookup.cs @@ -93,7 +93,8 @@ public void Dispose() public void SdkMultilevelLookup_Global_Json_Single_Digit_Patch_Rollup() { // 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 @@ -111,7 +112,7 @@ public void SdkMultilevelLookup_Global_Json_Single_Digit_Patch_Rollup() .CaptureStdErr() .Execute(fExpectedToFail: true) .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"); @@ -132,7 +133,7 @@ public void SdkMultilevelLookup_Global_Json_Single_Digit_Patch_Rollup() .CaptureStdErr() .Execute(fExpectedToFail: true) .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"); @@ -153,7 +154,7 @@ public void SdkMultilevelLookup_Global_Json_Single_Digit_Patch_Rollup() .CaptureStdErr() .Execute(fExpectedToFail: true) .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"); @@ -263,7 +264,8 @@ public void SdkMultilevelLookup_Global_Json_Single_Digit_Patch_Rollup() public void SdkMultilevelLookup_Global_Json_Two_Part_Patch_Rollup() { // 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 @@ -281,7 +283,7 @@ public void SdkMultilevelLookup_Global_Json_Two_Part_Patch_Rollup() .CaptureStdErr() .Execute(fExpectedToFail: true) .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"); @@ -302,7 +304,7 @@ public void SdkMultilevelLookup_Global_Json_Two_Part_Patch_Rollup() .CaptureStdErr() .Execute(fExpectedToFail: true) .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"); @@ -323,7 +325,7 @@ public void SdkMultilevelLookup_Global_Json_Two_Part_Patch_Rollup() .CaptureStdErr() .Execute(fExpectedToFail: true) .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"); @@ -488,12 +490,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. @@ -702,13 +704,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/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 b587a019edabd..15d02939c25de 100644 --- a/src/installer/tests/HostActivation.Tests/SDKLookup.cs +++ b/src/installer/tests/HostActivation.Tests/SDKLookup.cs @@ -78,7 +78,8 @@ public void Dispose() 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 @@ -92,8 +93,8 @@ public void SdkLookup_Global_Json_Single_Digit_Patch_Rollup() .CaptureStdErr() .Execute(fExpectedToFail: true) .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"); @@ -112,8 +113,8 @@ public void SdkLookup_Global_Json_Single_Digit_Patch_Rollup() .CaptureStdErr() .Execute(fExpectedToFail: true) .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(_exeSdkBaseDir, "9999.3.3"); @@ -130,8 +131,8 @@ public void SdkLookup_Global_Json_Single_Digit_Patch_Rollup() .CaptureStdErr() .Execute(fExpectedToFail: true) .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(_exeSdkBaseDir, "9999.3.4"); @@ -223,7 +224,8 @@ 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 @@ -237,8 +239,8 @@ public void SdkLookup_Global_Json_Two_Part_Patch_Rollup() .CaptureStdErr() .Execute(fExpectedToFail: true) .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(_exeSdkBaseDir, "9999.3.57", "9999.3.4-dummy"); @@ -255,8 +257,8 @@ public void SdkLookup_Global_Json_Two_Part_Patch_Rollup() .CaptureStdErr() .Execute(fExpectedToFail: true) .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(_exeSdkBaseDir, "9999.3.300", "9999.7.304-global-dummy"); @@ -273,8 +275,8 @@ public void SdkLookup_Global_Json_Two_Part_Patch_Rollup() .CaptureStdErr() .Execute(fExpectedToFail: true) .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(_exeSdkBaseDir, "9999.3.304"); @@ -386,8 +388,7 @@ public void SdkLookup_Negative_Version() .CaptureStdErr() .Execute(fExpectedToFail: true) .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(_exeSdkBaseDir, "9999.0.4"); @@ -639,16 +640,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(_exeSdkBaseDir, expected)}]"); } } @@ -1268,13 +1266,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 CopyGlobalJson(string globalJsonFileName) + private string CopyGlobalJson(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 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/HostActivation.Tests/StandaloneAppActivation.cs b/src/installer/tests/HostActivation.Tests/StandaloneAppActivation.cs index 352abb04f0dc1..cc561c618800c 100644 --- a/src/installer/tests/HostActivation.Tests/StandaloneAppActivation.cs +++ b/src/installer/tests/HostActivation.Tests/StandaloneAppActivation.cs @@ -1,20 +1,17 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System; +using System.IO; +using System.Text; + using FluentAssertions; using Microsoft.DotNet.Cli.Build.Framework; using Microsoft.DotNet.CoreSetup.Test; using Microsoft.NET.HostModel.AppHost; -using System; -using System.Diagnostics; -using System.IO; -using System.Runtime.InteropServices; -using System.Security.Cryptography; -using System.Text; -using System.Threading; using Xunit; -namespace Microsoft.DotNet.CoreSetup.Test.HostActivation +namespace HostActivation.Tests { public class StandaloneAppActivation : IClassFixture { @@ -203,15 +200,12 @@ public void Running_Publish_Output_Standalone_EXE_With_DOTNET_ROOT_Fails() // This verifies a self-contained apphost cannot use DOTNET_ROOT to reference a flat // self-contained layout since a flat layout of the shared framework is not supported. Command.Create(appExe) - .EnvironmentVariable("COREHOST_TRACE", "1") + .EnableTracingAndCaptureOutputs() .DotNetRoot(newOutDir) - .CaptureStdErr() - .CaptureStdOut() .Execute(fExpectedToFail: true) .Should().Fail() - .And.HaveStdErrContaining($"Using environment variable DOTNET_ROOT") // use the first part avoiding "(x86)" if present - .And.HaveStdErrContaining($"=[{Path.GetFullPath(newOutDir)}] as runtime location.") // use the last part - .And.HaveStdErrContaining("A fatal error occurred"); + .And.HaveUsedDotNetRootInstallLocation(Path.GetFullPath(newOutDir), fixture.CurrentRid) + .And.HaveStdErrContaining($"The required library {RuntimeInformationExtensions.GetSharedLibraryFileNameForCurrentPlatform("hostfxr")} could not be found."); } [Fact] @@ -226,9 +220,7 @@ public void Running_Publish_Output_Standalone_EXE_with_Bound_AppHost_Succeeds() AppHostExtensions.BindAppHost(appExe); Command.Create(appExe) - .EnvironmentVariable("COREHOST_TRACE", "1") - .CaptureStdErr() - .CaptureStdOut() + .EnableTracingAndCaptureOutputs() .Execute() .Should().Pass() .And.HaveStdOutContaining("Hello World") diff --git a/src/native/corehost/apphost/apphost.windows.cpp b/src/native/corehost/apphost/apphost.windows.cpp index 7a3a94a960b39..a4fd29084d53f 100644 --- a/src/native/corehost/apphost/apphost.windows.cpp +++ b/src/native/corehost/apphost/apphost.windows.cpp @@ -50,6 +50,36 @@ 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; + } + + pal::string_t get_runtime_not_found_message() + { + pal::string_t msg = INSTALL_NET_DESKTOP_ERROR_MESSAGE _X("\n\n") + _X("Architecture: "); + msg.append(get_arch()); + msg.append(_X("\n") + _X("App host version: ") _STRINGIFY(COMMON_HOST_PKG_VER) _X("\n\n")); + return msg; + } + void show_error_dialog(const pal::char_t *executable_name, int error_code) { pal::string_t gui_errors_disabled; @@ -58,17 +88,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"); + dialogMsg = get_runtime_not_found_message(); 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 +105,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,10 +136,11 @@ 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"); + dialogMsg = get_runtime_not_found_message(); url = get_download_url(); url.append(_X("&apphost_version=")); url.append(_STRINGIFY(COMMON_HOST_PKG_VER)); @@ -121,15 +151,21 @@ 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 ")); + dialogMsg.append(error_code == StatusCode::FrameworkMissingFailure ? _X("framework resolution:") : _X("runtime installation:")); + dialogMsg.append(_X("\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/fx_muxer.cpp b/src/native/corehost/fxr/fx_muxer.cpp index 8647130c18dc1..0626721f8f9d4 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, override_settings, app_config, fx_definitions, mode == host_mode_t::muxer ? app_candidate.c_str() : nullptr); 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..6091c9a5d4b55 100644 --- a/src/native/corehost/fxr/fx_resolver.cpp +++ b/src/native/corehost/fxr/fx_resolver.cpp @@ -286,7 +286,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; } @@ -397,7 +397,8 @@ StatusCode fx_resolver_t::read_framework( 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); @@ -442,6 +443,13 @@ StatusCode fx_resolver_t::read_framework( fx_definition_t* fx = resolve_framework_reference(new_effective_fx_ref, m_oldest_fx_references[fx_name].get_fx_version(), host_info.dotnet_root); if (fx == nullptr) { + 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); return FrameworkMissingFailure; } @@ -471,7 +479,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, override_settings, new_config, &new_effective_fx_ref, fx_definitions, app_display_name); if (rc) { break; // Error case @@ -513,7 +521,8 @@ StatusCode fx_resolver_t::resolve_frameworks_for_app( const host_startup_info_t & host_info, 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 +532,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, 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..e32507f6ae520 100644 --- a/src/native/corehost/fxr/fx_resolver.h +++ b/src/native/corehost/fxr/fx_resolver.h @@ -18,7 +18,8 @@ class fx_resolver_t const host_startup_info_t& host_info, 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, @@ -34,7 +35,8 @@ class fx_resolver_t 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, diff --git a/src/native/corehost/fxr/fx_resolver.messages.cpp b/src/native/corehost/fxr/fx_resolver.messages.cpp index 9f64a793c16cb..960a1c42c5c37 100644 --- a/src/native/corehost/fxr/fx_resolver.messages.cpp +++ b/src/native/corehost/fxr/fx_resolver.messages.cpp @@ -112,32 +112,37 @@ void fx_resolver_t::display_missing_framework_error( // 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/sdk_resolver.cpp b/src/native/corehost/fxr/sdk_resolver.cpp index abc47c7f17c39..a004e7f0db95a 100644 --- a/src/native/corehost/fxr/sdk_resolver.cpp +++ b/src/native/corehost/fxr/sdk_resolver.cpp @@ -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..e70ec907a1393 100644 --- a/src/native/corehost/fxr_resolver.cpp +++ b/src/native/corehost/fxr_resolver.cpp @@ -101,19 +101,33 @@ bool fxr_resolver::try_get_path(const pal::string_t& root_path, pal::string_t* o } pal::string_t self_registered_config_location = pal::get_dotnet_self_registered_config_location(); - pal::string_t self_registered_message = _X(" or register the runtime location in [") + self_registered_config_location + _X("]"); - - trace::error(_X("A fatal error occurred. The required library %s could not be found.\n" - "If this is a self-contained application, that library should exist in [%s].\n" - "If this is a framework-dependent application, install the runtime in the global location [%s] or use the %s environment variable to specify the runtime location%s."), + trace::verbose(_X("The required library %s could not be found. Searched with root path [%s], environment variable [%s], default install location [%s], self-registered config location [%s]"), LIBFXR_NAME, root_path.c_str(), - default_install_location.c_str(), 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)); + default_install_location.c_str(), + self_registered_config_location.c_str()); + + pal::string_t host_path; + pal::get_own_executable_path(&host_path); + trace::error( + INSTALL_NET_ERROR_MESSAGE + _X("\n\n") + _X("App: %s\n") + _X("Architecture: %s\n") + _X("App host version: %s\n") + _X(".NET location: Not found\n") + _X("\n") + _X("Learn about runtime installation:\n") + DOTNET_APP_LAUNCH_FAILED_URL + _X("\n\n") + _X("Download the .NET runtime:\n") + _X("%s&apphost_version=%s"), + host_path.c_str(), + get_arch(), + _STRINGIFY(COMMON_HOST_PKG_VER), + 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 21cc7e9c9b49e..bf9d39c7dfef2 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) diff --git a/src/native/corehost/hostmisc/utils.h b/src/native/corehost/hostmisc/utils.h index b4e31b599fac1..76eeaebedde6a 100644 --- a/src/native/corehost/hostmisc/utils.h +++ b/src/native/corehost/hostmisc/utils.h @@ -19,10 +19,45 @@ #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 INSTALL_NET_ERROR_MESSAGE _X("You must install .NET to run this application.") +#define INSTALL_NET_DESKTOP_ERROR_MESSAGE _X("You must install .NET Desktop Runtime 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);