Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,64 @@ 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
[InlineData("7.2.0", "7.2.3", ResolvedFramework.NotFound)] // Fail when the only matching version is disabled
[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, disabledVersions));

result.ShouldHaveResolvedFrameworkOrFailToFind(MicrosoftNETCoreApp, expectedResolution);
if (string.IsNullOrWhiteSpace(disabledVersions))
{
result.Should().NotHaveStdErrContaining($"Ignoring disabled version");
}
else
{
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_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(
Expand Down
16 changes: 16 additions & 0 deletions src/installer/tests/HostActivation.Tests/HostCommands.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down
59 changes: 39 additions & 20 deletions src/installer/tests/HostActivation.Tests/NativeHostApis.cs
Original file line number Diff line number Diff line change
Expand Up @@ -353,26 +353,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)
Expand Down Expand Up @@ -431,6 +415,41 @@ public void Hostfxr_get_dotnet_environment_info_with_multilevel_lookup_with_dotn
.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]
[PlatformSpecific(TestPlatforms.Windows)] // The test setup only works on Windows (and MLL was Windows-only anyway)
public void Hostfxr_get_dotnet_environment_info_with_multilevel_lookup_only()
Expand Down
5 changes: 5 additions & 0 deletions src/installer/tests/TestUtils/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
16 changes: 13 additions & 3 deletions src/native/corehost/fxr/framework_info.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

#include <cassert>
#include "framework_info.h"
#include "fx_resolver.h"
#include "pal.h"
#include "trace.h"
#include "utils.h"
Expand Down Expand Up @@ -36,13 +37,15 @@ 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_info>* framework_infos)
{
std::vector<pal::string_t> hive_dir;
get_framework_locations(dotnet_dir, disable_multilevel_lookup, &hive_dir);

int32_t hive_depth = 0;
std::vector<pal::string_t> 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;
Expand Down Expand Up @@ -92,9 +95,16 @@ bool compare_by_name_and_version(const framework_info &a, const framework_info &
continue;
}

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;
}

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);
}
}
Expand All @@ -110,7 +120,7 @@ bool compare_by_name_and_version(const framework_info &a, const framework_info &
assert(leading_whitespace != nullptr);

std::vector<framework_info> 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());
Expand Down
8 changes: 6 additions & 2 deletions src/native/corehost/fxr/framework_info.h
Original file line number Diff line number Diff line change
Expand Up @@ -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_info>* framework_infos);

static bool print_all_frameworks(const pal::string_t& dotnet_dir, const pal::char_t* leading_whitespace);
Expand All @@ -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_
51 changes: 49 additions & 2 deletions src/native/corehost/fxr/fx_resolver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<pal::string_t>& disabled_versions)
{
#if defined(DEBUG)
assert(!fx_ref.get_fx_name().empty());
Expand Down Expand Up @@ -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;
Expand All @@ -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);
}
}
Expand Down Expand Up @@ -307,6 +320,40 @@ namespace
}
}

// static
std::vector<pal::string_t> 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<pal::string_t> disabled_versions;
size_t start = 0;
size_t pos = 0;
while ((pos = env_var.find(_X(';'), start)) != pal::string_t::npos)
{
if (pos > start)
{
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)
if (start < env_var.size())
{
disabled_versions.emplace_back(env_var, start, env_var.size() - start);
}

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
Expand Down Expand Up @@ -438,7 +485,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_definition_t> 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_definition_t> 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);
Expand Down
10 changes: 6 additions & 4 deletions src/native/corehost/fxr/fx_resolver.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,10 @@ class fx_resolver_t
const runtime_config_t& config,
const std::unordered_map<pal::string_t, const fx_ver_t> &existing_framework_versions_by_name);

static std::vector<pal::string_t> get_disabled_versions();

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);
Expand Down Expand Up @@ -97,6 +96,9 @@ class fx_resolver_t

bool m_disable_multilevel_lookup;
const runtime_config_t::settings_t& m_override_settings;

// Disabled runtime versions
std::vector<pal::string_t> m_disabled_versions;
};

#endif // __FX_RESOLVER_H__
Loading
Loading