From d2f7edbcc317bf2e37720a96734ad4c133c75dbf Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Wed, 3 Sep 2025 17:35:54 -0700 Subject: [PATCH 1/4] Allow disabling runtime versions via an environment variable --- .../FrameworkResolution.cs | 45 ++++++++++++++++ src/installer/tests/TestUtils/Constants.cs | 5 ++ src/native/corehost/fxr/fx_resolver.cpp | 52 ++++++++++++++++++- src/native/corehost/fxr/fx_resolver.h | 8 +-- 4 files changed, 104 insertions(+), 6 deletions(-) diff --git a/src/installer/tests/HostActivation.Tests/FrameworkResolution/FrameworkResolution.cs b/src/installer/tests/HostActivation.Tests/FrameworkResolution/FrameworkResolution.cs index 42e724c8ad5356..2530d40435eaa4 100644 --- a/src/installer/tests/HostActivation.Tests/FrameworkResolution/FrameworkResolution.cs +++ b/src/installer/tests/HostActivation.Tests/FrameworkResolution/FrameworkResolution.cs @@ -103,6 +103,51 @@ public void FrameworkResolutionError_ListOtherArchitectures() } } + [Theory] + [InlineData("6.1.0", "6.1.2", "6.1.3")] // Roll forward to 6.1.3 when 6.1.2 is disabled + [InlineData("6.1.0", "6.1.3", "6.1.2")] // Roll forward to 6.1.2 when 6.1.3 is disabled + [InlineData("6.1.3", "6.1.3", ResolvedFramework.NotFound)] // Fail when the only matching version is disabled + [InlineData("7.2.0", "7.2.3", ResolvedFramework.NotFound)] // Fail when the only matching version is disabled + public void DisabledVersions_Single(string requestedVersion, string disabledVersion, string expectedResolution) + { + CommandResult result = RunTest( + new TestSettings() + .WithRuntimeConfigCustomizer(rc => rc.WithFramework(MicrosoftNETCoreApp, requestedVersion)) + .WithEnvironment(Constants.DisableRuntimeVersions.EnvironmentVariable, disabledVersion)); + + result.ShouldHaveResolvedFrameworkOrFailToFind(MicrosoftNETCoreApp, expectedResolution) + .And.HaveStdErrContaining($"Ignoring disabled version [{disabledVersion}]"); + } + + [Fact] + public void DisabledVersions_Multiple() + { + // Disable both 6.1.2 and 6.1.3, request 6.1.0 which should roll forward but find nothing + CommandResult result = RunTest( + new TestSettings() + .WithRuntimeConfigCustomizer(rc => rc.WithFramework(MicrosoftNETCoreApp, "6.1.0")) + .WithEnvironment(Constants.DisableRuntimeVersions.EnvironmentVariable, "6.1.2;6.1.3")); + + result.ShouldFailToFindCompatibleFrameworkVersion(MicrosoftNETCoreApp, "6.1.0") + .And.HaveStdErrContaining("Ignoring disabled version [6.1.2]") + .And.HaveStdErrContaining("Ignoring disabled version [6.1.3]"); + } + + [Fact] + public void DisabledVersions_NoRollForward() + { + string disabledVersion = "6.1.2"; + CommandResult result = RunTest( + new TestSettings() + .WithRuntimeConfigCustomizer(rc => rc + .WithFramework(MicrosoftNETCoreApp, disabledVersion) + .WithRollForward(Constants.RollForwardSetting.Disable)) + .WithEnvironment(Constants.DisableRuntimeVersions.EnvironmentVariable, disabledVersion)); + + result.ShouldFailToFindCompatibleFrameworkVersion(MicrosoftNETCoreApp, disabledVersion) + .And.HaveStdErrContaining($"Ignoring disabled version [{disabledVersion}]"); + } + private CommandResult RunTest(TestSettings testSettings, [CallerMemberName] string caller = "") { return RunTest( diff --git a/src/installer/tests/TestUtils/Constants.cs b/src/installer/tests/TestUtils/Constants.cs index 4d3be90319ad15..e965aa55e7c03a 100644 --- a/src/installer/tests/TestUtils/Constants.cs +++ b/src/installer/tests/TestUtils/Constants.cs @@ -35,6 +35,11 @@ public static class RollForwardSetting public const string Disable = "Disable"; } + public static class DisableRuntimeVersions + { + public const string EnvironmentVariable = "DOTNET_DISABLE_RUNTIME_VERSIONS"; + } + public static class FxVersion { public const string CommandLineArgument = "--fx-version"; diff --git a/src/native/corehost/fxr/fx_resolver.cpp b/src/native/corehost/fxr/fx_resolver.cpp index af970d7c56b204..04de18ff30e94b 100644 --- a/src/native/corehost/fxr/fx_resolver.cpp +++ b/src/native/corehost/fxr/fx_resolver.cpp @@ -188,7 +188,8 @@ namespace const fx_reference_t & fx_ref, const pal::string_t & oldest_requested_version, const pal::string_t & dotnet_dir, - const bool disable_multilevel_lookup) + const bool disable_multilevel_lookup, + const std::vector& disabled_versions) { #if defined(DEBUG) assert(!fx_ref.get_fx_name().empty()); @@ -236,6 +237,12 @@ namespace append_path(&fx_dir, fx_ref.get_fx_version().c_str()); if (file_exists_in_dir(fx_dir, deps_file_name.c_str(), nullptr)) { + if (std::find(disabled_versions.begin(), disabled_versions.end(), fx_ref.get_fx_version()) != disabled_versions.end()) + { + trace::verbose(_X("Ignoring disabled version [%s]"), fx_ref.get_fx_version().c_str()); + continue; + } + selected_fx_dir = fx_dir; selected_fx_version = fx_ref.get_fx_version(); break; @@ -252,6 +259,12 @@ namespace fx_ver_t ver; if (fx_ver_t::parse(version, &ver, false)) { + if (std::find(disabled_versions.begin(), disabled_versions.end(), version) != disabled_versions.end()) + { + trace::verbose(_X("Ignoring disabled version [%s]"), version.c_str()); + continue; + } + version_list.push_back(ver); } } @@ -305,8 +318,43 @@ namespace return std::unique_ptr(new fx_definition_t(fx_ref.get_fx_name(), selected_fx_dir, oldest_requested_version, selected_fx_version)); } + + std::vector get_disabled_versions() + { + pal::string_t env_var; + if (!pal::getenv(_X("DOTNET_DISABLE_RUNTIME_VERSIONS"), &env_var) || env_var.empty()) + return {}; + + std::vector disabled_versions; + size_t start = 0; + size_t pos = 0; + while ((pos = env_var.find(_X(';'), start)) != pal::string_t::npos) + { + pal::string_t version = env_var.substr(start, pos - start); + if (!version.empty()) + { + disabled_versions.push_back(version); + } + start = pos + 1; + } + + // Add the last version (after the last semicolon or the only version if no semicolons) + pal::string_t last_version = env_var.substr(start); + if (!last_version.empty()) + { + disabled_versions.push_back(last_version); + } + + return disabled_versions; + } } +fx_resolver_t::fx_resolver_t(bool disable_multilevel_lookup, const runtime_config_t::settings_t& override_settings) + : m_disable_multilevel_lookup{disable_multilevel_lookup} + , m_override_settings{override_settings} + , m_disabled_versions{get_disabled_versions()} +{ } + // Reconciles two framework references into a new effective framework reference // This process is sometimes also called "soft roll forward" (soft as in no IO) // - fx_ref_a - one of the framework references to reconcile @@ -438,7 +486,7 @@ StatusCode fx_resolver_t::read_framework( m_effective_fx_references[fx_name] = new_effective_fx_ref; // Resolve the effective framework reference against the existing physical framework folders - std::unique_ptr fx = resolve_framework_reference(new_effective_fx_ref, m_oldest_fx_references[fx_name].get_fx_version(), dotnet_root, m_disable_multilevel_lookup); + std::unique_ptr fx = resolve_framework_reference(new_effective_fx_ref, m_oldest_fx_references[fx_name].get_fx_version(), dotnet_root, m_disable_multilevel_lookup, m_disabled_versions); if (fx == nullptr) { resolution_failure.missing = std::move(new_effective_fx_ref); diff --git a/src/native/corehost/fxr/fx_resolver.h b/src/native/corehost/fxr/fx_resolver.h index 9abfd706baadc1..e7f9fce6033a44 100644 --- a/src/native/corehost/fxr/fx_resolver.h +++ b/src/native/corehost/fxr/fx_resolver.h @@ -41,10 +41,7 @@ class fx_resolver_t const std::unordered_map &existing_framework_versions_by_name); private: - fx_resolver_t(bool disable_multilevel_lookup, const runtime_config_t::settings_t& override_settings) - : m_disable_multilevel_lookup{disable_multilevel_lookup} - , m_override_settings{override_settings} - { } + fx_resolver_t(bool disable_multilevel_lookup, const runtime_config_t::settings_t& override_settings); void update_newest_references( const runtime_config_t& config); @@ -97,6 +94,9 @@ class fx_resolver_t bool m_disable_multilevel_lookup; const runtime_config_t::settings_t& m_override_settings; + + // Disabled runtime versions + std::vector m_disabled_versions; }; #endif // __FX_RESOLVER_H__ From e485a70cb1ead71411449e8931257d3b5bfdb552 Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Thu, 4 Sep 2025 13:28:33 -0700 Subject: [PATCH 2/4] Update get_all_framework_infos, add test cases --- .../FrameworkResolution.cs | 13 ++++- .../HostActivation.Tests/HostCommands.cs | 16 +++++ .../HostActivation.Tests/NativeHostApis.cs | 58 ++++++++++++------- src/native/corehost/fxr/framework_info.cpp | 10 +++- src/native/corehost/fxr/fx_resolver.cpp | 47 +++++++-------- src/native/corehost/fxr/fx_resolver.h | 2 + 6 files changed, 100 insertions(+), 46 deletions(-) diff --git a/src/installer/tests/HostActivation.Tests/FrameworkResolution/FrameworkResolution.cs b/src/installer/tests/HostActivation.Tests/FrameworkResolution/FrameworkResolution.cs index 2530d40435eaa4..b163bf722e7129 100644 --- a/src/installer/tests/HostActivation.Tests/FrameworkResolution/FrameworkResolution.cs +++ b/src/installer/tests/HostActivation.Tests/FrameworkResolution/FrameworkResolution.cs @@ -104,6 +104,8 @@ public void FrameworkResolutionError_ListOtherArchitectures() } [Theory] + [InlineData("6.1.0", "", "6.1.3")] // Roll forward to 6.1.3 - empty value has no effect on resolution + [InlineData("6.1.0", " ", "6.1.3")] // Roll forward to 6.1.3 - whitespace value has no effect on resolution [InlineData("6.1.0", "6.1.2", "6.1.3")] // Roll forward to 6.1.3 when 6.1.2 is disabled [InlineData("6.1.0", "6.1.3", "6.1.2")] // Roll forward to 6.1.2 when 6.1.3 is disabled [InlineData("6.1.3", "6.1.3", ResolvedFramework.NotFound)] // Fail when the only matching version is disabled @@ -115,8 +117,15 @@ public void DisabledVersions_Single(string requestedVersion, string disabledVers .WithRuntimeConfigCustomizer(rc => rc.WithFramework(MicrosoftNETCoreApp, requestedVersion)) .WithEnvironment(Constants.DisableRuntimeVersions.EnvironmentVariable, disabledVersion)); - result.ShouldHaveResolvedFrameworkOrFailToFind(MicrosoftNETCoreApp, expectedResolution) - .And.HaveStdErrContaining($"Ignoring disabled version [{disabledVersion}]"); + result.ShouldHaveResolvedFrameworkOrFailToFind(MicrosoftNETCoreApp, expectedResolution); + if (string.IsNullOrWhiteSpace(disabledVersion)) + { + result.Should().NotHaveStdErrContaining($"Ignoring disabled version"); + } + else + { + result.Should().HaveStdErrContaining($"Ignoring disabled version [{disabledVersion}]"); + } } [Fact] diff --git a/src/installer/tests/HostActivation.Tests/HostCommands.cs b/src/installer/tests/HostActivation.Tests/HostCommands.cs index d7fb6d30cfd83a..5e5d03ba7f2282 100644 --- a/src/installer/tests/HostActivation.Tests/HostCommands.cs +++ b/src/installer/tests/HostActivation.Tests/HostCommands.cs @@ -241,6 +241,22 @@ public void ListRuntimes() .And.HaveStdOut(expectedOutput); } + [Fact] + public void ListRuntimes_DisabledVersions() + { + // Verify exact match of command output. The output of --list-runtimes is intended to be machine-readable + // and must not change in a way that breaks existing parsing. + string disabledVersion = SharedState.InstalledVersions[0]; + string[] expectedVersions = SharedState.InstalledVersions[1..]; + string expectedOutput = GetListRuntimesOutput(SharedState.DotNet.BinPath, expectedVersions); + SharedState.DotNet.Exec("--list-runtimes") + .CaptureStdOut() + .EnvironmentVariable(Constants.DisableRuntimeVersions.EnvironmentVariable, disabledVersion) + .Execute() + .Should().Pass() + .And.HaveStdOut(expectedOutput); + } + [Theory] [InlineData(true)] [InlineData(false)] diff --git a/src/installer/tests/HostActivation.Tests/NativeHostApis.cs b/src/installer/tests/HostActivation.Tests/NativeHostApis.cs index 35b73c5fc1dd2e..a7b4d553781dc8 100644 --- a/src/installer/tests/HostActivation.Tests/NativeHostApis.cs +++ b/src/installer/tests/HostActivation.Tests/NativeHostApis.cs @@ -317,26 +317,10 @@ public void Hostfxr_get_dotnet_environment_info_dotnet_root_only() string expectedSdkVersions = string.Join(";", f.LocalSdks); string expectedSdkPaths = string.Join(';', f.LocalSdkPaths); - string expectedFrameworkNames = string.Join(';', new[] - { - "HostFxr.Test.B", - "HostFxr.Test.B", - "HostFxr.Test.C" - }); - - string expectedFrameworkVersions = string.Join(';', new[] - { - "4.0.0", - "5.6.7-A", - "3.0.0" - }); - - string expectedFrameworkPaths = string.Join(';', new[] - { - Path.Combine(f.LocalFrameworksDir, "HostFxr.Test.B"), - Path.Combine(f.LocalFrameworksDir, "HostFxr.Test.B"), - Path.Combine(f.LocalFrameworksDir, "HostFxr.Test.C") - }); + IEnumerable<(string Name, string Version)> frameworks = f.LocalFrameworks.SelectMany(fw => fw.fwVersions.Select(v => (fw.fwName, v))); + string expectedFrameworkNames = string.Join(';', frameworks.Select(fw => fw.Name)); + string expectedFrameworkVersions = string.Join(';', frameworks.Select(fw => fw.Version)); + string expectedFrameworkPaths = string.Join(';', frameworks.Select(fw => Path.Combine(f.LocalFrameworksDir, fw.Name))); string api = ApiNames.hostfxr_get_dotnet_environment_info; TestContext.BuiltDotNet.Exec(sharedTestState.HostApiInvokerApp.AppDll, api, f.ExeDir) @@ -351,6 +335,40 @@ public void Hostfxr_get_dotnet_environment_info_dotnet_root_only() .And.HaveStdOutContaining($"{api} framework paths:[{expectedFrameworkPaths}]"); } + [Fact] + public void Hostfxr_get_dotnet_environment_info_DisabledVersions() + { + var f = sharedTestState.SdkAndFrameworkFixture; + string expectedSdkVersions = string.Join(";", f.LocalSdks); + string expectedSdkPaths = string.Join(';', f.LocalSdkPaths); + + string[] disabledVersions = [ "4.0.0", "3.0.0"]; + IEnumerable<(string Name, string Version)> frameworks = f.LocalFrameworks + .SelectMany(fw => fw.fwVersions + .Where(v => !disabledVersions.Contains(v)) + .Select(v => (fw.fwName, v))); + string expectedFrameworkNames = string.Join(';', frameworks.Select(fw => fw.Name)); + string expectedFrameworkVersions = string.Join(';', frameworks.Select(fw => fw.Version)); + string expectedFrameworkPaths = string.Join(';', frameworks.Select(fw => Path.Combine(f.LocalFrameworksDir, fw.Name))); + + string api = ApiNames.hostfxr_get_dotnet_environment_info; + var result = TestContext.BuiltDotNet.Exec(sharedTestState.HostApiInvokerApp.AppDll, api, f.ExeDir) + .EnableTracingAndCaptureOutputs() + .EnvironmentVariable(Constants.DisableRuntimeVersions.EnvironmentVariable, string.Join(';', disabledVersions)) + .Execute(); + result.Should().Pass() + .And.ReturnStatusCode(api, Constants.ErrorCode.Success) + .And.HaveStdOutContaining($"{api} sdk versions:[{expectedSdkVersions}]") + .And.HaveStdOutContaining($"{api} sdk paths:[{expectedSdkPaths}]") + .And.HaveStdOutContaining($"{api} framework names:[{expectedFrameworkNames}]") + .And.HaveStdOutContaining($"{api} framework versions:[{expectedFrameworkVersions}]") + .And.HaveStdOutContaining($"{api} framework paths:[{expectedFrameworkPaths}]"); + foreach (string version in disabledVersions) + { + result.Should().HaveStdErrContaining($"Ignoring disabled version [{version}]"); + } + } + [Fact] public void Hostfxr_get_dotnet_environment_info_global_install_path() { diff --git a/src/native/corehost/fxr/framework_info.cpp b/src/native/corehost/fxr/framework_info.cpp index a7912c2b18627f..71e8d762de88f7 100644 --- a/src/native/corehost/fxr/framework_info.cpp +++ b/src/native/corehost/fxr/framework_info.cpp @@ -3,6 +3,7 @@ #include #include "framework_info.h" +#include "fx_resolver.h" #include "pal.h" #include "trace.h" #include "utils.h" @@ -41,8 +42,9 @@ bool compare_by_name_and_version(const framework_info &a, const framework_info & std::vector hive_dir; get_framework_locations(dotnet_dir, disable_multilevel_lookup, &hive_dir); - int32_t hive_depth = 0; + std::vector disabled_versions = fx_resolver_t::get_disabled_versions(); + int32_t hive_depth = 0; for (const pal::string_t& dir : hive_dir) { auto fx_shared_dir = dir; @@ -92,6 +94,12 @@ bool compare_by_name_and_version(const framework_info &a, const framework_info & continue; } + if (std::find(disabled_versions.begin(), disabled_versions.end(), ver) != disabled_versions.end()) + { + trace::verbose(_X("Ignoring disabled version [%s]"), ver.c_str()); + continue; + } + trace::verbose(_X("Found FX version [%s]"), ver.c_str()); framework_info info(fx_name_local, fx_dir, parsed, hive_depth); diff --git a/src/native/corehost/fxr/fx_resolver.cpp b/src/native/corehost/fxr/fx_resolver.cpp index 04de18ff30e94b..2778dc62417aee 100644 --- a/src/native/corehost/fxr/fx_resolver.cpp +++ b/src/native/corehost/fxr/fx_resolver.cpp @@ -318,35 +318,36 @@ namespace return std::unique_ptr(new fx_definition_t(fx_ref.get_fx_name(), selected_fx_dir, oldest_requested_version, selected_fx_version)); } +} - std::vector get_disabled_versions() +// static +std::vector fx_resolver_t::get_disabled_versions() +{ + pal::string_t env_var; + if (!pal::getenv(_X("DOTNET_DISABLE_RUNTIME_VERSIONS"), &env_var) || env_var.empty()) + return {}; + + std::vector disabled_versions; + size_t start = 0; + size_t pos = 0; + while ((pos = env_var.find(_X(';'), start)) != pal::string_t::npos) { - pal::string_t env_var; - if (!pal::getenv(_X("DOTNET_DISABLE_RUNTIME_VERSIONS"), &env_var) || env_var.empty()) - return {}; - - std::vector disabled_versions; - size_t start = 0; - size_t pos = 0; - while ((pos = env_var.find(_X(';'), start)) != pal::string_t::npos) - { - pal::string_t version = env_var.substr(start, pos - start); - if (!version.empty()) - { - disabled_versions.push_back(version); - } - start = pos + 1; - } - - // Add the last version (after the last semicolon or the only version if no semicolons) - pal::string_t last_version = env_var.substr(start); - if (!last_version.empty()) + pal::string_t version = env_var.substr(start, pos - start); + if (!version.empty()) { - disabled_versions.push_back(last_version); + disabled_versions.push_back(version); } + start = pos + 1; + } - return disabled_versions; + // Add the last version (after the last semicolon or the only version if no semicolons) + pal::string_t last_version = env_var.substr(start); + if (!last_version.empty()) + { + disabled_versions.push_back(last_version); } + + return disabled_versions; } fx_resolver_t::fx_resolver_t(bool disable_multilevel_lookup, const runtime_config_t::settings_t& override_settings) diff --git a/src/native/corehost/fxr/fx_resolver.h b/src/native/corehost/fxr/fx_resolver.h index e7f9fce6033a44..dd0eec70c91c72 100644 --- a/src/native/corehost/fxr/fx_resolver.h +++ b/src/native/corehost/fxr/fx_resolver.h @@ -40,6 +40,8 @@ class fx_resolver_t const runtime_config_t& config, const std::unordered_map &existing_framework_versions_by_name); + static std::vector get_disabled_versions(); + private: fx_resolver_t(bool disable_multilevel_lookup, const runtime_config_t::settings_t& override_settings); From c9b6c0e71140fdb3f2780315946b0b8167c0bf13 Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Thu, 4 Sep 2025 14:12:03 -0700 Subject: [PATCH 3/4] Update parsing of env var --- src/native/corehost/fxr/fx_resolver.cpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/native/corehost/fxr/fx_resolver.cpp b/src/native/corehost/fxr/fx_resolver.cpp index 2778dc62417aee..bdd3e17833e578 100644 --- a/src/native/corehost/fxr/fx_resolver.cpp +++ b/src/native/corehost/fxr/fx_resolver.cpp @@ -332,19 +332,17 @@ std::vector fx_resolver_t::get_disabled_versions() size_t pos = 0; while ((pos = env_var.find(_X(';'), start)) != pal::string_t::npos) { - pal::string_t version = env_var.substr(start, pos - start); - if (!version.empty()) + if (pos > start) { - disabled_versions.push_back(version); + disabled_versions.emplace_back(env_var, start, pos - start); } start = pos + 1; } // Add the last version (after the last semicolon or the only version if no semicolons) - pal::string_t last_version = env_var.substr(start); - if (!last_version.empty()) + if (start < env_var.size()) { - disabled_versions.push_back(last_version); + disabled_versions.emplace_back(env_var, start, env_var.size() - start); } return disabled_versions; From b175b98c6084b9332ca05a682e38c8de8e6dbdee Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Tue, 9 Sep 2025 15:24:50 -0700 Subject: [PATCH 4/4] More test cases, update resolution error message listing frameworks to indicate disabled --- .../FrameworkResolution.cs | 42 ++++++++++--------- src/native/corehost/fxr/framework_info.cpp | 8 ++-- src/native/corehost/fxr/framework_info.h | 8 +++- .../corehost/fxr/fx_resolver.messages.cpp | 12 +++++- src/native/corehost/fxr/hostfxr.cpp | 2 +- 5 files changed, 45 insertions(+), 27 deletions(-) diff --git a/src/installer/tests/HostActivation.Tests/FrameworkResolution/FrameworkResolution.cs b/src/installer/tests/HostActivation.Tests/FrameworkResolution/FrameworkResolution.cs index b163bf722e7129..b5e993a100a31d 100644 --- a/src/installer/tests/HostActivation.Tests/FrameworkResolution/FrameworkResolution.cs +++ b/src/installer/tests/HostActivation.Tests/FrameworkResolution/FrameworkResolution.cs @@ -110,38 +110,42 @@ public void FrameworkResolutionError_ListOtherArchitectures() [InlineData("6.1.0", "6.1.3", "6.1.2")] // Roll forward to 6.1.2 when 6.1.3 is disabled [InlineData("6.1.3", "6.1.3", ResolvedFramework.NotFound)] // Fail when the only matching version is disabled [InlineData("7.2.0", "7.2.3", ResolvedFramework.NotFound)] // Fail when the only matching version is disabled - public void DisabledVersions_Single(string requestedVersion, string disabledVersion, string expectedResolution) + [InlineData("6.1.0", "6.1.2;6.1.3", ResolvedFramework.NotFound)] // Fail when all matching versions are disabled + [InlineData("6.1.0", "invalid;6.1.3", "6.1.2")] // Roll forward to 6.1.2 - invalid value ignored, 6.1.3 disabled + [InlineData("6.1.0", "v6.1.3;;6.1.0", "6.1.3")] // Roll forward to 6.1.3 - invalid or non-existent versions have no effect on resolution + public void DisabledVersions(string requestedVersion, string disabledVersions, string expectedResolution) { CommandResult result = RunTest( new TestSettings() .WithRuntimeConfigCustomizer(rc => rc.WithFramework(MicrosoftNETCoreApp, requestedVersion)) - .WithEnvironment(Constants.DisableRuntimeVersions.EnvironmentVariable, disabledVersion)); + .WithEnvironment(Constants.DisableRuntimeVersions.EnvironmentVariable, disabledVersions)); result.ShouldHaveResolvedFrameworkOrFailToFind(MicrosoftNETCoreApp, expectedResolution); - if (string.IsNullOrWhiteSpace(disabledVersion)) + if (string.IsNullOrWhiteSpace(disabledVersions)) { result.Should().NotHaveStdErrContaining($"Ignoring disabled version"); } else - { - result.Should().HaveStdErrContaining($"Ignoring disabled version [{disabledVersion}]"); + { + foreach (string value in disabledVersions.Split(';')) + { + if (SharedState.InstalledVersions.Contains(value)) + { + result.Should().HaveStdErrContaining($"Ignoring disabled version [{value}]"); + if (expectedResolution == ResolvedFramework.NotFound) + { + result.Should().HaveStdErrContaining( + $""" + {value} at [{SharedState.InstalledDotNet.SharedFxPath}] + Disabled via {Constants.DisableRuntimeVersions.EnvironmentVariable} environment variable + """); + + } + } + } } } - [Fact] - public void DisabledVersions_Multiple() - { - // Disable both 6.1.2 and 6.1.3, request 6.1.0 which should roll forward but find nothing - CommandResult result = RunTest( - new TestSettings() - .WithRuntimeConfigCustomizer(rc => rc.WithFramework(MicrosoftNETCoreApp, "6.1.0")) - .WithEnvironment(Constants.DisableRuntimeVersions.EnvironmentVariable, "6.1.2;6.1.3")); - - result.ShouldFailToFindCompatibleFrameworkVersion(MicrosoftNETCoreApp, "6.1.0") - .And.HaveStdErrContaining("Ignoring disabled version [6.1.2]") - .And.HaveStdErrContaining("Ignoring disabled version [6.1.3]"); - } - [Fact] public void DisabledVersions_NoRollForward() { diff --git a/src/native/corehost/fxr/framework_info.cpp b/src/native/corehost/fxr/framework_info.cpp index 71e8d762de88f7..4e19a036643b08 100644 --- a/src/native/corehost/fxr/framework_info.cpp +++ b/src/native/corehost/fxr/framework_info.cpp @@ -37,6 +37,7 @@ bool compare_by_name_and_version(const framework_info &a, const framework_info & const pal::string_t& dotnet_dir, const pal::char_t* fx_name, bool disable_multilevel_lookup, + bool include_disabled_versions, std::vector* framework_infos) { std::vector hive_dir; @@ -94,7 +95,8 @@ bool compare_by_name_and_version(const framework_info &a, const framework_info & continue; } - if (std::find(disabled_versions.begin(), disabled_versions.end(), ver) != disabled_versions.end()) + bool is_disabled = std::find(disabled_versions.begin(), disabled_versions.end(), ver) != disabled_versions.end(); + if (is_disabled && !include_disabled_versions) { trace::verbose(_X("Ignoring disabled version [%s]"), ver.c_str()); continue; @@ -102,7 +104,7 @@ bool compare_by_name_and_version(const framework_info &a, const framework_info & trace::verbose(_X("Found FX version [%s]"), ver.c_str()); - framework_info info(fx_name_local, fx_dir, parsed, hive_depth); + framework_info info(fx_name_local, fx_dir, parsed, hive_depth, is_disabled); framework_infos->push_back(info); } } @@ -118,7 +120,7 @@ bool compare_by_name_and_version(const framework_info &a, const framework_info & assert(leading_whitespace != nullptr); std::vector framework_infos; - get_all_framework_infos(dotnet_dir, nullptr, /*disable_multilevel_lookup*/ true, &framework_infos); + get_all_framework_infos(dotnet_dir, nullptr, /*disable_multilevel_lookup*/ true, /*include_disabled_versions*/ false, &framework_infos); for (framework_info info : framework_infos) { trace::println(_X("%s%s %s [%s]"), leading_whitespace, 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 736b6debbea2f4..3c63759e78fd72 100644 --- a/src/native/corehost/fxr/framework_info.h +++ b/src/native/corehost/fxr/framework_info.h @@ -9,16 +9,19 @@ struct framework_info { - framework_info(pal::string_t name, pal::string_t path, fx_ver_t version, int32_t hive_depth) + framework_info(pal::string_t name, pal::string_t path, fx_ver_t version, int32_t hive_depth, bool disabled) : name(name) , path(path) , version(version) - , hive_depth(hive_depth) { } + , hive_depth(hive_depth) + , disabled(disabled) + { } static void get_all_framework_infos( const pal::string_t& dotnet_dir, const pal::char_t* fx_name, bool disable_multilevel_lookup, + bool include_disabled_versions, std::vector* framework_infos); static bool print_all_frameworks(const pal::string_t& dotnet_dir, const pal::char_t* leading_whitespace); @@ -27,6 +30,7 @@ struct framework_info pal::string_t path; fx_ver_t version; int32_t hive_depth; + bool disabled; }; #endif // __FRAMEWORK_INFO_H_ diff --git a/src/native/corehost/fxr/fx_resolver.messages.cpp b/src/native/corehost/fxr/fx_resolver.messages.cpp index a922858ae5b390..3b98fa145bac4b 100644 --- a/src/native/corehost/fxr/fx_resolver.messages.cpp +++ b/src/native/corehost/fxr/fx_resolver.messages.cpp @@ -110,13 +110,17 @@ void fx_resolver_t::display_missing_framework_error( trace::error(_X(".NET location: %s\n"), dotnet_root.c_str()); std::vector framework_infos; - framework_info::get_all_framework_infos(dotnet_root, fx_name.c_str(), disable_multilevel_lookup, &framework_infos); + framework_info::get_all_framework_infos(dotnet_root, fx_name.c_str(), disable_multilevel_lookup, /*include_disabled_versions*/ true, &framework_infos); if (framework_infos.size()) { 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()); + if (info.disabled) + { + trace::error(_X(" Disabled via DOTNET_DISABLE_RUNTIME_VERSIONS environment variable")); + } } } else @@ -129,7 +133,7 @@ void fx_resolver_t::display_missing_framework_error( [&](pal::architecture arch, const pal::string_t& install_location, bool is_registered) { std::vector other_arch_infos; - framework_info::get_all_framework_infos(install_location, fx_name.c_str(), disable_multilevel_lookup, &other_arch_infos); + framework_info::get_all_framework_infos(install_location, fx_name.c_str(), disable_multilevel_lookup, /*include_disabled_versions*/ true, &other_arch_infos); if (!other_arch_infos.empty()) { other_arch_framework_infos.push_back(std::make_pair(arch, std::move(other_arch_infos))); @@ -144,6 +148,10 @@ void fx_resolver_t::display_missing_framework_error( for (const framework_info& info : arch_info_pair.second) { trace::error(_X(" %s at [%s]"), info.version.as_str().c_str(), info.path.c_str()); + if (info.disabled) + { + trace::error(_X(" Disabled via DOTNET_DISABLE_RUNTIME_VERSIONS environment variable")); + } } } } diff --git a/src/native/corehost/fxr/hostfxr.cpp b/src/native/corehost/fxr/hostfxr.cpp index 1b3438c05b4d28..e6464fc478e6d3 100644 --- a/src/native/corehost/fxr/hostfxr.cpp +++ b/src/native/corehost/fxr/hostfxr.cpp @@ -437,7 +437,7 @@ SHARED_API int32_t HOSTFXR_CALLTYPE hostfxr_get_dotnet_environment_info( } std::vector framework_infos; - framework_info::get_all_framework_infos(dotnet_dir, nullptr, /*disable_multilevel_lookup*/ true, &framework_infos); + framework_info::get_all_framework_infos(dotnet_dir, nullptr, /*disable_multilevel_lookup*/ true, /*include_disabled_versions*/ false, &framework_infos); std::vector environment_framework_infos; std::vector framework_versions;