From 47c4e4b1498c104d1293df603f9004a79dbc2eea Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Fri, 14 Aug 2026 11:42:27 -0700 Subject: [PATCH 1/3] Remove multi-level lookup --- src/native/corehost/fxr/framework_info.cpp | 112 ++++++------ src/native/corehost/fxr/framework_info.h | 5 +- src/native/corehost/fxr/fx_resolver.cpp | 159 ++++++++---------- src/native/corehost/fxr/fx_resolver.h | 6 +- .../corehost/fxr/fx_resolver.messages.cpp | 7 +- src/native/corehost/fxr/hostfxr.cpp | 22 +-- src/native/corehost/fxr/sdk_info.cpp | 24 +-- src/native/corehost/hostfxr.h | 10 +- src/native/corehost/hostmisc/pal.h | 3 - src/native/corehost/hostmisc/pal.unix.cpp | 6 - src/native/corehost/hostmisc/pal.windows.cpp | 25 --- src/native/corehost/hostmisc/utils.cpp | 56 ------ src/native/corehost/hostmisc/utils.h | 2 - .../corehost/hostpolicy/shared_store.cpp | 32 +--- src/native/corehost/runtime_config.cpp | 41 ----- src/native/corehost/runtime_config.h | 4 - 16 files changed, 145 insertions(+), 369 deletions(-) diff --git a/src/native/corehost/fxr/framework_info.cpp b/src/native/corehost/fxr/framework_info.cpp index 2a680992a05bb9..f5e1c71019626d 100644 --- a/src/native/corehost/fxr/framework_info.cpp +++ b/src/native/corehost/fxr/framework_info.cpp @@ -25,91 +25,79 @@ bool compare_by_name_and_version(const framework_info &a, const framework_info & return true; } - if (a.version == b.version) - { - return a.hive_depth > b.hive_depth; - } - return false; } /*static*/ void framework_info::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) { - std::vector hive_dir; - get_framework_locations(dotnet_dir, disable_multilevel_lookup, &hive_dir); + if (dotnet_dir.empty()) + return; std::vector disabled_versions = fx_resolver_t::get_disabled_versions(); - int32_t hive_depth = 0; - for (const pal::string_t& dir : hive_dir) + pal::string_t fx_shared_dir = dotnet_dir; + append_path(&fx_shared_dir, _X("shared")); + + if (!pal::directory_exists(fx_shared_dir)) + return; + + std::vector fx_names; + if (fx_name != nullptr) { - auto fx_shared_dir = dir; - append_path(&fx_shared_dir, _X("shared")); + // Use the provided framework name + fx_names.push_back(fx_name); + } + else + { + // Read all frameworks, including "Microsoft.NETCore.App" + pal::readdir_onlydirectories(fx_shared_dir, &fx_names); + } - if (!pal::directory_exists(fx_shared_dir)) + for (const pal::string_t& fx_name_local : fx_names) + { + auto fx_dir = fx_shared_dir; + append_path(&fx_dir, fx_name_local.c_str()); + + if (!pal::directory_exists(fx_dir)) continue; - std::vector fx_names; - if (fx_name != nullptr) - { - // Use the provided framework name - fx_names.push_back(fx_name); - } - else - { - // Read all frameworks, including "Microsoft.NETCore.App" - pal::readdir_onlydirectories(fx_shared_dir, &fx_names); - } + trace::verbose(_X("Gathering FX locations in [%s]"), fx_dir.c_str()); - for (const pal::string_t& fx_name_local : fx_names) + const pal::string_t deps_file_name = fx_name_local + _X(".deps.json"); + std::vector versions; + pal::readdir_onlydirectories(fx_dir, &versions); + for (const pal::string_t& ver : versions) { - auto fx_dir = fx_shared_dir; - append_path(&fx_dir, fx_name_local.c_str()); - - if (!pal::directory_exists(fx_dir)) + // Make sure we filter out any non-version folders. + fx_ver_t parsed; + if (!fx_ver_t::parse(ver, &parsed)) continue; - trace::verbose(_X("Gathering FX locations in [%s]"), fx_dir.c_str()); + // Check that the framework's .deps.json exists. + pal::string_t fx_version_dir = fx_dir; + append_path(&fx_version_dir, ver.c_str()); + if (!file_exists_in_dir(fx_version_dir, deps_file_name.c_str(), nullptr)) + { + trace::verbose(_X("Ignoring FX version [%s] without .deps.json"), ver.c_str()); + continue; + } - const pal::string_t deps_file_name = fx_name_local + _X(".deps.json"); - std::vector versions; - pal::readdir_onlydirectories(fx_dir, &versions); - for (const pal::string_t& ver : versions) + bool is_disabled = std::find(disabled_versions.begin(), disabled_versions.end(), ver) != disabled_versions.end(); + if (is_disabled && !include_disabled_versions) { - // Make sure we filter out any non-version folders. - fx_ver_t parsed; - if (!fx_ver_t::parse(ver, &parsed)) - continue; - - // Check that the framework's .deps.json exists. - pal::string_t fx_version_dir = fx_dir; - append_path(&fx_version_dir, ver.c_str()); - if (!file_exists_in_dir(fx_version_dir, deps_file_name.c_str(), nullptr)) - { - trace::verbose(_X("Ignoring FX version [%s] without .deps.json"), ver.c_str()); - 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, is_disabled); - framework_infos->push_back(info); + trace::verbose(_X("Ignoring disabled version [%s]"), ver.c_str()); + continue; } - } - hive_depth++; + trace::verbose(_X("Found FX version [%s]"), ver.c_str()); + + framework_info info(fx_name_local, fx_dir, parsed, is_disabled); + framework_infos->push_back(info); + } } std::sort(framework_infos->begin(), framework_infos->end(), compare_by_name_and_version); @@ -120,7 +108,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, /*include_disabled_versions*/ false, &framework_infos); + get_all_framework_infos(dotnet_dir, nullptr, /*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 3c63759e78fd72..67d760019dfaf9 100644 --- a/src/native/corehost/fxr/framework_info.h +++ b/src/native/corehost/fxr/framework_info.h @@ -9,18 +9,16 @@ struct framework_info { - framework_info(pal::string_t name, pal::string_t path, fx_ver_t version, int32_t hive_depth, bool disabled) + framework_info(pal::string_t name, pal::string_t path, fx_ver_t version, bool disabled) : name(name) , path(path) , version(version) - , 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); @@ -29,7 +27,6 @@ struct framework_info pal::string_t name; pal::string_t path; fx_ver_t version; - int32_t hive_depth; bool disabled; }; diff --git a/src/native/corehost/fxr/fx_resolver.cpp b/src/native/corehost/fxr/fx_resolver.cpp index 0cc68ba7130d25..a2464374eab265 100644 --- a/src/native/corehost/fxr/fx_resolver.cpp +++ b/src/native/corehost/fxr/fx_resolver.cpp @@ -188,7 +188,6 @@ 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 std::vector& disabled_versions) { #if defined(DEBUG) @@ -203,107 +202,88 @@ namespace trace::verbose(_X("--- Resolving FX directory, name '%s' version '%s'"), fx_ref.get_fx_name().c_str(), fx_ref.get_fx_version().c_str()); - std::vector hive_dir; - get_framework_locations(dotnet_dir, disable_multilevel_lookup, &hive_dir); - pal::string_t selected_fx_dir; pal::string_t selected_fx_version; - fx_ver_t selected_ver; pal::string_t deps_file_name = fx_ref.get_fx_name() + _X(".deps.json"); - for (pal::string_t& dir : hive_dir) + pal::string_t fx_dir = dotnet_dir; + trace::verbose(_X("Searching FX directory in [%s]"), fx_dir.c_str()); + + append_path(&fx_dir, _X("shared")); + append_path(&fx_dir, fx_ref.get_fx_name().c_str()); + + // Roll forward is disabled when: + // roll_forward is set to Disable + // roll_forward is set to LatestPatch AND + // apply_patches is false AND + // release framework reference (this is for backward compat with pre-release rolling over pre-release portion of version ignoring apply_patches) + // use exact version is set (this is when --fx-version was used on the command line) + if ((fx_ref.get_version_compatibility_range() == version_compatibility_range_t::exact) || + ((fx_ref.get_version_compatibility_range() == version_compatibility_range_t::patch) && (!fx_ref.get_apply_patches() && !fx_ref.get_fx_version_number().is_prerelease()))) { - auto fx_dir = dir; - trace::verbose(_X("Searching FX directory in [%s]"), fx_dir.c_str()); - - append_path(&fx_dir, _X("shared")); - append_path(&fx_dir, fx_ref.get_fx_name().c_str()); - - // Roll forward is disabled when: - // roll_forward is set to Disable - // roll_forward is set to LatestPatch AND - // apply_patches is false AND - // release framework reference (this is for backward compat with pre-release rolling over pre-release portion of version ignoring apply_patches) - // use exact version is set (this is when --fx-version was used on the command line) - if ((fx_ref.get_version_compatibility_range() == version_compatibility_range_t::exact) || - ((fx_ref.get_version_compatibility_range() == version_compatibility_range_t::patch) && (!fx_ref.get_apply_patches() && !fx_ref.get_fx_version_number().is_prerelease()))) - { - trace::verbose( - _X("Did not roll forward because apply_patches=%d, version_compatibility_range=%s chose [%s]"), - fx_ref.get_apply_patches(), - version_compatibility_range_to_string(fx_ref.get_version_compatibility_range()).c_str(), - fx_ref.get_fx_version().c_str()); + trace::verbose( + _X("Did not roll forward because apply_patches=%d, version_compatibility_range=%s chose [%s]"), + fx_ref.get_apply_patches(), + version_compatibility_range_to_string(fx_ref.get_version_compatibility_range()).c_str(), + fx_ref.get_fx_version().c_str()); - append_path(&fx_dir, fx_ref.get_fx_version().c_str()); - if (file_exists_in_dir(fx_dir, deps_file_name.c_str(), nullptr)) + 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()) { - 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; + } + else + { + trace::verbose(_X("Ignoring disabled version [%s]"), fx_ref.get_fx_version().c_str()); } } - else - { - std::vector list; - std::vector version_list; - pal::readdir_onlydirectories(fx_dir, &list); + } + else + { + std::vector list; + std::vector version_list; + pal::readdir_onlydirectories(fx_dir, &list); - for (const auto& version : list) + for (const auto& version : list) + { + fx_ver_t ver; + if (fx_ver_t::parse(version, &ver)) { - fx_ver_t ver; - if (fx_ver_t::parse(version, &ver)) + if (std::find(disabled_versions.begin(), disabled_versions.end(), version) != disabled_versions.end()) { - 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); + trace::verbose(_X("Ignoring disabled version [%s]"), version.c_str()); + continue; } + + version_list.push_back(ver); } + } - fx_ver_t resolved_ver = resolve_framework_reference_from_version_list(version_list, fx_ref); - while (resolved_ver != fx_ver_t()) + fx_ver_t resolved_ver = resolve_framework_reference_from_version_list(version_list, fx_ref); + while (resolved_ver != fx_ver_t()) + { + pal::string_t resolved_ver_str = resolved_ver.as_str(); + pal::string_t resolved_fx_dir = fx_dir; + append_path(&resolved_fx_dir, resolved_ver_str.c_str()); + + // Check that the framework's .deps.json exists. To minimize the file checks done in the most common + // scenario (.deps.json exists), only check after resolving the version and if the .deps.json doesn't + // exist, attempt resolving again without that version. + if (!file_exists_in_dir(resolved_fx_dir, deps_file_name.c_str(), nullptr)) { - pal::string_t resolved_ver_str = resolved_ver.as_str(); - pal::string_t resolved_fx_dir = fx_dir; - append_path(&resolved_fx_dir, resolved_ver_str.c_str()); - - // Check that the framework's .deps.json exists. To minimize the file checks done in the most common - // scenario (.deps.json exists), only check after resolving the version and if the .deps.json doesn't - // exist, attempt resolving again without that version. - if (!file_exists_in_dir(resolved_fx_dir, deps_file_name.c_str(), nullptr)) - { - // Remove the version and try resolving again - trace::verbose(_X("Ignoring FX version [%s] without .deps.json"), resolved_ver_str.c_str()); - version_list.erase(std::find(version_list.cbegin(), version_list.cend(), resolved_ver)); - resolved_ver = resolve_framework_reference_from_version_list(version_list, fx_ref); - } - else - { - if (selected_ver != fx_ver_t()) - { - // Compare the previous hive_dir selection with the current hive_dir to see which one is the better match - resolved_ver = resolve_framework_reference_from_version_list({ resolved_ver, selected_ver }, fx_ref); - } - - if (resolved_ver != selected_ver) - { - trace::verbose(_X("Changing Selected FX version from [%s] to [%s]"), selected_fx_dir.c_str(), resolved_fx_dir.c_str()); - selected_ver = resolved_ver; - selected_fx_dir = resolved_fx_dir; - selected_fx_version = resolved_ver_str; - } - - break; - } + // Remove the version and try resolving again + trace::verbose(_X("Ignoring FX version [%s] without .deps.json"), resolved_ver_str.c_str()); + version_list.erase(std::find(version_list.cbegin(), version_list.cend(), resolved_ver)); + resolved_ver = resolve_framework_reference_from_version_list(version_list, fx_ref); + } + else + { + selected_fx_dir = resolved_fx_dir; + selected_fx_version = resolved_ver_str; + break; } } } @@ -348,9 +328,8 @@ std::vector fx_resolver_t::get_disabled_versions() 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} +fx_resolver_t::fx_resolver_t(const runtime_config_t::settings_t& override_settings) + : m_override_settings{override_settings} , m_disabled_versions{get_disabled_versions()} { } @@ -485,7 +464,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, m_disabled_versions); + std::unique_ptr fx = resolve_framework_reference(new_effective_fx_ref, m_oldest_fx_references[fx_name].get_fx_version(), dotnet_root, m_disabled_versions); if (fx == nullptr) { resolution_failure.missing = std::move(new_effective_fx_ref); @@ -547,7 +526,7 @@ StatusCode fx_resolver_t::resolve_frameworks( fx_definition_vector_t& fx_definitions, resolution_failure_info& resolution_failure) { - fx_resolver_t resolver{ app_config.get_is_multilevel_lookup_disabled(), override_settings }; + fx_resolver_t resolver{ override_settings }; // Read the shared frameworks; retry is necessary when a framework is already resolved, but then a newer compatible version is processed. StatusCode rc = StatusCode::Success; @@ -587,7 +566,7 @@ StatusCode fx_resolver_t::resolve_frameworks_for_app( _X("Architecture: %s"), app_display_name, get_current_arch_name()); - display_missing_framework_error(resolution_failure.missing.get_fx_name(), resolution_failure.missing.get_fx_version(), dotnet_root, app_config.get_is_multilevel_lookup_disabled()); + display_missing_framework_error(resolution_failure.missing.get_fx_name(), resolution_failure.missing.get_fx_version(), dotnet_root); break; case StatusCode::FrameworkCompatFailure: display_incompatible_framework_error(resolution_failure.incompatible_higher.get_fx_version(), resolution_failure.incompatible_lower); diff --git a/src/native/corehost/fxr/fx_resolver.h b/src/native/corehost/fxr/fx_resolver.h index dd0eec70c91c72..9981fe5b6b766b 100644 --- a/src/native/corehost/fxr/fx_resolver.h +++ b/src/native/corehost/fxr/fx_resolver.h @@ -43,7 +43,7 @@ class fx_resolver_t static std::vector get_disabled_versions(); private: - fx_resolver_t(bool disable_multilevel_lookup, const runtime_config_t::settings_t& override_settings); + fx_resolver_t(const runtime_config_t::settings_t& override_settings); void update_newest_references( const runtime_config_t& config); @@ -63,8 +63,7 @@ class fx_resolver_t static void display_missing_framework_error( const pal::string_t& fx_name, const pal::string_t& fx_version, - const pal::string_t& dotnet_root, - bool disable_multilevel_lookup); + const pal::string_t& dotnet_root); static void display_incompatible_framework_error( const pal::string_t& higher, const fx_reference_t& lower); @@ -94,7 +93,6 @@ class fx_resolver_t // of the algorithm. fx_name_to_fx_reference_map_t m_oldest_fx_references; - bool m_disable_multilevel_lookup; const runtime_config_t::settings_t& m_override_settings; // Disabled runtime versions diff --git a/src/native/corehost/fxr/fx_resolver.messages.cpp b/src/native/corehost/fxr/fx_resolver.messages.cpp index 3b98fa145bac4b..89967afb753fe0 100644 --- a/src/native/corehost/fxr/fx_resolver.messages.cpp +++ b/src/native/corehost/fxr/fx_resolver.messages.cpp @@ -93,8 +93,7 @@ void fx_resolver_t::display_summary_of_frameworks( void fx_resolver_t::display_missing_framework_error( const pal::string_t& fx_name, const pal::string_t& fx_version, - const pal::string_t& dotnet_root, - bool disable_multilevel_lookup) + const pal::string_t& dotnet_root) { // Display the error message about missing FX. @@ -110,7 +109,7 @@ 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, /*include_disabled_versions*/ true, &framework_infos); + framework_info::get_all_framework_infos(dotnet_root, fx_name.c_str(), /*include_disabled_versions*/ true, &framework_infos); if (framework_infos.size()) { trace::error(_X("The following frameworks were found:")); @@ -133,7 +132,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, /*include_disabled_versions*/ true, &other_arch_infos); + framework_info::get_all_framework_infos(install_location, fx_name.c_str(), /*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))); diff --git a/src/native/corehost/fxr/hostfxr.cpp b/src/native/corehost/fxr/hostfxr.cpp index e6464fc478e6d3..01288db8c0f1ad 100644 --- a/src/native/corehost/fxr/hostfxr.cpp +++ b/src/native/corehost/fxr/hostfxr.cpp @@ -31,6 +31,12 @@ SHARED_API int HOSTFXR_CALLTYPE hostfxr_main_bundle_startupinfo(const int argc, { trace_hostfxr_entry_point(_X("hostfxr_main_bundle_startupinfo")); + if (host_path == nullptr || dotnet_root == nullptr || app_path == nullptr || dotnet_root[0] == _X('\0')) + { + trace::error(_X("Invalid startup info: host_path, dotnet_root, and app_path should not be null.")); + return StatusCode::InvalidArgFailure; + } + StatusCode bundleStatus = bundle::info_t::process_bundle(host_path, app_path, bundle_header_offset); if (bundleStatus != StatusCode::Success) { @@ -38,12 +44,6 @@ SHARED_API int HOSTFXR_CALLTYPE hostfxr_main_bundle_startupinfo(const int argc, return bundleStatus; } - if (host_path == nullptr || dotnet_root == nullptr || app_path == nullptr) - { - trace::error(_X("Invalid startup info: host_path, dotnet_root, and app_path should not be null.")); - return StatusCode::InvalidArgFailure; - } - host_startup_info_t startup_info(host_path, dotnet_root, app_path); return fx_muxer_t::execute(pal::string_t(), argc, argv, startup_info, nullptr, 0, nullptr); } @@ -53,7 +53,7 @@ SHARED_API int HOSTFXR_CALLTYPE hostfxr_main_startupinfo(const int argc, const p { trace_hostfxr_entry_point(_X("hostfxr_main_startupinfo")); - if (host_path == nullptr || dotnet_root == nullptr || app_path == nullptr) + if (host_path == nullptr || dotnet_root == nullptr || app_path == nullptr || dotnet_root[0] == _X('\0')) { trace::error(_X("Invalid startup info: host_path, dotnet_root, and app_path should not be null.")); return StatusCode::InvalidArgFailure; @@ -75,8 +75,7 @@ SHARED_API int HOSTFXR_CALLTYPE hostfxr_main(const int argc, const pal::char_t* // [OBSOLETE] Replaced by hostfxr_resolve_sdk2 // -// Determines the directory location of the SDK accounting for -// global.json and multi-level lookup policy. +// Determines the directory location of the SDK accounting for global.json. // // Invoked via MSBuild SDK resolver to locate SDK props and targets // from an msbuild other than the one bundled by the CLI. @@ -86,9 +85,6 @@ SHARED_API int HOSTFXR_CALLTYPE hostfxr_main(const int argc, const pal::char_t* // The main directory where SDKs are located in sdk\[version] // sub-folders. Pass the directory of a dotnet executable to // mimic how that executable would search in its own directory. -// It is also valid to pass nullptr or empty, in which case -// multi-level lookup can still search other locations if -// it has not been disabled by the user's environment. // // working_dir // The directory where the search for global.json (which can @@ -437,7 +433,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, /*include_disabled_versions*/ false, &framework_infos); + framework_info::get_all_framework_infos(dotnet_dir, nullptr, /*include_disabled_versions*/ false, &framework_infos); std::vector environment_framework_infos; std::vector framework_versions; diff --git a/src/native/corehost/fxr/sdk_info.cpp b/src/native/corehost/fxr/sdk_info.cpp index 18ba09a00ee3e8..8d1c56b11c1508 100644 --- a/src/native/corehost/fxr/sdk_info.cpp +++ b/src/native/corehost/fxr/sdk_info.cpp @@ -15,24 +15,14 @@ bool compare_by_version_ascending_then_hive_depth_descending(const sdk_info &a, return true; } - // With multi-level lookup enabled, it is possible to find two SDKs with - // the same version. For that edge case, we make the ordering put SDKs - // from farther away (global location) hives earlier than closer ones - // (current dotnet exe location). Without this tie-breaker, the ordering - // would be non-deterministic. + // When global.json specifies custom SDK paths, it is possible to find two + // SDKs with the same version in different search locations. Without a + // tie-breaker, the ordering would be non-deterministic. // - // Furthermore, nearer earlier than farther is so that the MSBuild resolver - // can do a linear search from the end of the list to the front to find the - // best compatible SDK. - // - // Example: - // * dotnet dir has version 4.0, 5.0, 6.0 - // * global dir has 5.0 - // * 6.0 is incompatible with calling msbuild - // * 5.0 is compatible with calling msbuild - // - // MSBuild should select 5.0 from dotnet dir (matching probe order) in muxer - // and not 5.0 from global dir. + // Consumers of this list (the MSBuild resolver via hostfxr_get_available_sdks) + // scan backwards from the end to find the best compatible SDK, so the entry + // that should win has to come last. Search locations are in priority order - + // the first match wins - so the lowest hive depth sorts last. if (a.version == b.version) { return a.hive_depth > b.hive_depth; diff --git a/src/native/corehost/hostfxr.h b/src/native/corehost/hostfxr.h index 3b203a894a4a2f..825cea54aba986 100644 --- a/src/native/corehost/hostfxr.h +++ b/src/native/corehost/hostfxr.h @@ -331,15 +331,11 @@ typedef void(HOSTFXR_CALLTYPE* hostfxr_get_dotnet_environment_info_result_fn)( // Returns available SDKs and frameworks. // // Resolves the existing SDKs and frameworks from a dotnet root directory (if -// any), or the global default location. If multi-level lookup is enabled and -// the dotnet root location is different than the global location, the SDKs and -// frameworks will be enumerated from both locations. +// any), or the global default location. // -// The SDKs are sorted in ascending order by version, multi-level lookup -// locations are put before private ones. +// The SDKs are sorted in ascending order by version. // -// The frameworks are sorted in ascending order by name followed by version, -// multi-level lookup locations are put before private ones. +// The frameworks are sorted in ascending order by name followed by version. // // Parameters: // dotnet_root diff --git a/src/native/corehost/hostmisc/pal.h b/src/native/corehost/hostmisc/pal.h index 405b8d63073450..9c6c4814811cf8 100644 --- a/src/native/corehost/hostmisc/pal.h +++ b/src/native/corehost/hostmisc/pal.h @@ -559,9 +559,6 @@ namespace pal // Returns the default install location for a given platform for the specified architecture bool get_default_installation_dir_for_arch(architecture arch, string_t* recv); - // Returns the global locations to search for SDK/Frameworks - used when multi-level lookup is enabled - bool get_global_dotnet_dirs(std::vector* recv); - bool get_default_breadcrumb_store(string_t* recv); bool is_path_rooted(const string_t& path); bool is_path_fully_qualified(const string_t& path); diff --git a/src/native/corehost/hostmisc/pal.unix.cpp b/src/native/corehost/hostmisc/pal.unix.cpp index 3663716c232fcd..967517fd53238e 100644 --- a/src/native/corehost/hostmisc/pal.unix.cpp +++ b/src/native/corehost/hostmisc/pal.unix.cpp @@ -316,12 +316,6 @@ bool pal::get_default_bundle_extraction_base_dir(pal::string_t& extraction_dir) return is_read_write_able_directory(extraction_dir); } -bool pal::get_global_dotnet_dirs(std::vector* recv) -{ - // No support for global directories in Unix. - return false; -} - pal::string_t pal::get_dotnet_self_registered_config_location(pal::architecture arch) { pal::string_t config_location = _X("/etc/dotnet"); diff --git a/src/native/corehost/hostmisc/pal.windows.cpp b/src/native/corehost/hostmisc/pal.windows.cpp index d21ae324783c5e..9be59a1f6c1456 100644 --- a/src/native/corehost/hostmisc/pal.windows.cpp +++ b/src/native/corehost/hostmisc/pal.windows.cpp @@ -448,31 +448,6 @@ bool pal::get_dotnet_self_registered_dir_for_arch(pal::architecture arch, pal::s return true; } -bool pal::get_global_dotnet_dirs(std::vector* dirs) -{ - pal::string_t default_dir; - pal::string_t custom_dir; - bool dir_found = false; - if (pal::get_dotnet_self_registered_dir(&custom_dir)) - { - remove_trailing_dir_separator(&custom_dir); - dirs->push_back(custom_dir); - dir_found = true; - } - if (get_default_installation_dir(&default_dir)) - { - remove_trailing_dir_separator(&default_dir); - - // Avoid duplicate global dirs. - if (!dir_found || !are_paths_equal_with_normalized_casing(custom_dir, default_dir)) - { - dirs->push_back(default_dir); - dir_found = true; - } - } - return dir_found; -} - // To determine the OS version, we are going to use RtlGetVersion API // since GetVersion call can be shimmed on Win8.1+. typedef LONG (WINAPI *pFuncRtlGetVersion)(RTL_OSVERSIONINFOW *); diff --git a/src/native/corehost/hostmisc/utils.cpp b/src/native/corehost/hostmisc/utils.cpp index f98721db91a75a..65f35e86261fd0 100644 --- a/src/native/corehost/hostmisc/utils.cpp +++ b/src/native/corehost/hostmisc/utils.cpp @@ -246,62 +246,6 @@ bool try_get_runtime_id_from_env(pal::string_t& out_rid) return pal::getenv(_X("DOTNET_RUNTIME_ID"), &out_rid); } -/** -* Multilevel Lookup is enabled by default -* It can be disabled by setting DOTNET_MULTILEVEL_LOOKUP env var to a value that is not 1 -*/ -bool multilevel_lookup_enabled() -{ - pal::string_t env_lookup; - bool multilevel_lookup = true; - - if (pal::getenv(_X("DOTNET_MULTILEVEL_LOOKUP"), &env_lookup)) - { - auto env_val = pal::xtoi(env_lookup.c_str()); - multilevel_lookup = (env_val == 1); - trace::verbose(_X("DOTNET_MULTILEVEL_LOOKUP is set to %s"), env_lookup.c_str()); - } - trace::info(_X("Multilevel lookup is %s"), multilevel_lookup ? _X("true") : _X("false")); - return multilevel_lookup; -} - -void get_framework_locations(const pal::string_t& dotnet_dir, const bool disable_multilevel_lookup, std::vector* locations) -{ - bool multilevel_lookup = disable_multilevel_lookup ? false : multilevel_lookup_enabled(); - - // Multi-level lookup will look for the most appropriate version in several locations - // by following the priority rank below: - // .exe directory - // Global .NET directories - // If it is not activated, then only .exe directory will be considered - - pal::string_t dotnet_dir_temp; - if (!dotnet_dir.empty()) - { - // own_dir contains DIR_SEPARATOR appended that we need to remove. - dotnet_dir_temp = dotnet_dir; - remove_trailing_dir_separator(&dotnet_dir_temp); - - locations->push_back(dotnet_dir_temp); - } - - if (!multilevel_lookup) - return; - - std::vector global_dirs; - if (pal::get_global_dotnet_dirs(&global_dirs)) - { - for (pal::string_t dir : global_dirs) - { - // avoid duplicate paths - if (!pal::are_paths_equal_with_normalized_casing(dir, dotnet_dir_temp)) - { - locations->push_back(dir); - } - } - } -} - bool get_file_path_from_env(const pal::char_t* env_key, pal::string_t* recv) { recv->clear(); diff --git a/src/native/corehost/hostmisc/utils.h b/src/native/corehost/hostmisc/utils.h index 2fd0d4a1346956..4384c0699d8189 100644 --- a/src/native/corehost/hostmisc/utils.h +++ b/src/native/corehost/hostmisc/utils.h @@ -109,8 +109,6 @@ const pal::char_t* get_current_arch_name(); pal::string_t get_runtime_id(); bool try_get_runtime_id_from_env(pal::string_t& out_rid); -bool multilevel_lookup_enabled(); -void get_framework_locations(const pal::string_t& dotnet_dir, const bool disable_multilevel_lookup, std::vector* locations); bool get_file_path_from_env(const pal::char_t* env_key, pal::string_t* recv); size_t index_of_non_numeric(const pal::string_t& str, size_t i); bool try_stou(const pal::string_t& str, unsigned* num); diff --git a/src/native/corehost/hostpolicy/shared_store.cpp b/src/native/corehost/hostpolicy/shared_store.cpp index 0279bde549e248..5728a8ff554f39 100644 --- a/src/native/corehost/hostpolicy/shared_store.cpp +++ b/src/native/corehost/hostpolicy/shared_store.cpp @@ -30,25 +30,6 @@ namespace } } } - - void get_global_dirs(std::vector& dirs, const pal::char_t* arch, const pal::string_t& tfm, const pal::string_t& dir_to_skip) - { - std::vector global_dirs; - if (!pal::get_global_dotnet_dirs(&global_dirs)) - return; - - for (pal::string_t dir : global_dirs) - { - append_path(&dir, RUNTIME_STORE_DIRECTORY_NAME); - append_path(&dir, arch); - append_path(&dir, tfm.c_str()); - if (!dir_to_skip.empty() && pal::are_paths_equal_with_normalized_casing(dir, dir_to_skip)) - continue; - - dirs.push_back(dir); - trace::verbose(_X("Shared store (%s): '%s'"), _X("global"), dir.c_str()); - } - } } /** @@ -56,9 +37,6 @@ namespace * * - DOTNET_SHARED_STORE environment variable - multiple delimited paths + \ * - dotnet.exe relative shared store\\ - * - Global location - * Windows: global default location (Program Files) or globally registered location (registry) + store\\ - * Linux/macOS: none (no global locations are considered) */ std::vector shared_store::get_paths(const pal::string_t& tfm, host_mode_t host_mode, const pal::string_t& host_path) { @@ -74,10 +52,9 @@ std::vector shared_store::get_paths(const pal::string_t& tfm, hos get_env_dirs(shared_stores, arch, tfm); // "dotnet.exe" relative shared store folder - pal::string_t dotnet_shared_store; if (host_mode == host_mode_t::muxer) { - dotnet_shared_store = get_directory(host_path); + pal::string_t dotnet_shared_store = get_directory(host_path); append_path(&dotnet_shared_store, RUNTIME_STORE_DIRECTORY_NAME); append_path(&dotnet_shared_store, arch); append_path(&dotnet_shared_store, tfm.c_str()); @@ -85,12 +62,5 @@ std::vector shared_store::get_paths(const pal::string_t& tfm, hos trace::verbose(_X("Shared store (%s): '%s'"), _X("dotnet"), dotnet_shared_store.c_str()); } - // Global shared store dir - bool multilevel_lookup = multilevel_lookup_enabled(); - if (multilevel_lookup) - { - get_global_dirs(shared_stores, arch, tfm, dotnet_shared_store); - } - return shared_stores; } diff --git a/src/native/corehost/runtime_config.cpp b/src/native/corehost/runtime_config.cpp index 1e5fa17e2437d0..b7368ef09a4b01 100644 --- a/src/native/corehost/runtime_config.cpp +++ b/src/native/corehost/runtime_config.cpp @@ -432,47 +432,6 @@ const pal::string_t& runtime_config_t::get_tfm() const return m_tfm; } -const uint32_t runtime_config_t::get_compat_major_version_from_tfm() const -{ - assert(m_valid); - - // TFM is in form - // - netcoreapp#.# for <= 3.1 - // - net#.# for >= 5.0 - // In theory it could contain a suffix like `net10.0-windows` (or more than one) - // or it may lack the minor version like `net10`. SDK will normalize this, but the runtime should not 100% rely on it - - if (m_tfm.empty()) - return runtime_config_t::unknown_version; - - size_t majorVersionStartIndex; - const pal::char_t netcoreapp_prefix[] = _X("netcoreapp"); - if (utils::starts_with(m_tfm, netcoreapp_prefix, true)) - { - majorVersionStartIndex = utils::strlen(netcoreapp_prefix); - } - else - { - majorVersionStartIndex = utils::strlen(_X("net")); - } - - if (majorVersionStartIndex >= m_tfm.length()) - return runtime_config_t::unknown_version; - - size_t majorVersionEndIndex = index_of_non_numeric(m_tfm, majorVersionStartIndex); - if (majorVersionEndIndex == pal::string_t::npos || majorVersionEndIndex == majorVersionStartIndex) - return runtime_config_t::unknown_version; - - return static_cast(std::stoul(m_tfm.substr(majorVersionStartIndex, majorVersionEndIndex - majorVersionStartIndex))); -} - -bool runtime_config_t::get_is_multilevel_lookup_disabled() const -{ - // Starting with .NET 7, multi-level lookup is fully disabled - unsigned long compat_major_version = get_compat_major_version_from_tfm(); - return (compat_major_version >= 7 || compat_major_version == runtime_config_t::unknown_version); -} - bool runtime_config_t::get_is_framework_dependent() const { return m_is_framework_dependent; diff --git a/src/native/corehost/runtime_config.h b/src/native/corehost/runtime_config.h index 464c46234e4dee..a1593a68ac594a 100644 --- a/src/native/corehost/runtime_config.h +++ b/src/native/corehost/runtime_config.h @@ -33,7 +33,6 @@ class runtime_config_t const pal::string_t& get_path() const { return m_path; } const pal::string_t& get_dev_path() const { return m_dev_path; } const pal::string_t& get_tfm() const; - bool get_is_multilevel_lookup_disabled() const; const std::list& get_probe_paths() const; bool get_is_framework_dependent() const; bool parse_opts(const json_parser_t::value_t& opts); @@ -42,10 +41,7 @@ class runtime_config_t const fx_reference_vector_t& get_included_frameworks() const { return m_included_frameworks; } void set_fx_version(pal::string_t version); - static constexpr int unknown_version = std::numeric_limits::max(); - private: - const uint32_t get_compat_major_version_from_tfm() const; bool ensure_parsed(); //todo: const runtime_config_t* defaults bool ensure_dev_config_parsed(); From fe0bb626ae40dbf3b14b95ff5ae2ca7e9fd810c8 Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Fri, 14 Aug 2026 11:43:38 -0700 Subject: [PATCH 2/3] Update tests and doc --- .../host-component-dependencies-resolution.md | 3 +- docs/design/features/host-probing.md | 5 +- docs/design/features/hosting-layer-apis.md | 2 +- .../tests/AppHost.Bundle.Tests/AppLaunch.cs | 1 - .../BundledAppWithSubDirs.cs | 1 - .../ComponentSharedTestStateBase.cs | 4 +- .../FrameworkDependentAppLaunch.cs | 9 - .../FrameworkResolution/MultilevelLookup.cs | 178 ------------------ .../HostVersionCompatibility.cs | 5 +- .../HostActivation.Tests/InstallLocation.cs | 2 - .../NativeHosting/ComhostSideBySide.cs | 3 - .../NativeHosting/Ijwhost.cs | 1 - .../NativeHosting/Nethost.cs | 1 - .../NativeHosting/SharedTestStateBase.cs | 3 +- .../tests/HostActivation.Tests/SDKLookup.cs | 1 - .../tests/TestUtils/CommandExtensions.cs | 8 - src/installer/tests/TestUtils/Constants.cs | 5 - src/installer/tests/TestUtils/DotNetCli.cs | 3 +- 18 files changed, 7 insertions(+), 228 deletions(-) delete mode 100644 src/installer/tests/HostActivation.Tests/FrameworkResolution/MultilevelLookup.cs diff --git a/docs/design/features/host-component-dependencies-resolution.md b/docs/design/features/host-component-dependencies-resolution.md index 379335251be2ff..7b0145a3466293 100644 --- a/docs/design/features/host-component-dependencies-resolution.md +++ b/docs/design/features/host-component-dependencies-resolution.md @@ -28,9 +28,8 @@ This feature certainly provides a somewhat duplicate functionality to the existi * RID fallback graph from the root framework - reused * Just like settings, there's a set of environment variables which are used in the same way as the app would use them * `DOTNET_ROLL_FORWARD_ON_NO_CANDIDATE_FX` - used just like in the app - * `ProgramFiles` and `ProgramFiles(x86")` - used to find servicing root and the shared store + * `ProgramFiles` and `ProgramFiles(x86")` - used to find servicing root * `DOTNET_SHARED_STORE` - used to find the shared store - just like the app - * `DOTNET_MULTILEVEL_LOOKUP` - used to enable multi-level lookup - used just like in the app * Right now this feature doesn't process `.runtimeconfig.json` or `.runtimeconfig.dev.json`. Most dynamically loaded components don't have these anyway, since SDK doesn't generate these for the `classlib` project type. The only meaningful piece of info from these which could be used is the set of probing paths to use. Currently the same set ofprobing paths as for the app is used. With the changes in .NET Core 3 where `dotnet build` will copy all static dependencies locally, the importance of additional probing paths should be very low anyway. ## Open questions diff --git a/docs/design/features/host-probing.md b/docs/design/features/host-probing.md index febdf24932a1b0..1e22098f678056 100644 --- a/docs/design/features/host-probing.md +++ b/docs/design/features/host-probing.md @@ -48,14 +48,11 @@ The list of probing paths ordered according to their priority. First path in the If the app (or framework) has dependencies on frameworks, these frameworks are used as probing paths. The order is from the higher level framework to lower level framework. The app is considered the highest level, it direct dependencies are next and so on. For assets from frameworks, only that framework and lower level frameworks are considered. - Note: These directories come directly out of the framework resolution process. Special note on Windows where global locations are always considered even if the app is not executed via the shared `dotnet.exe`. More details can be found in [Shared FX Lookup](sharedfx-lookup.md). + Note: These directories come directly out of the framework resolution process. More details can be found in [Shared FX Lookup](sharedfx-lookup.md). * Shared store paths * `$DOTNET_SHARED_STORE/|arch|/|tfm|` - The environment variable `DOTNET_SHARED_STORE` can contain multiple paths, in which case each is appended with `|arch|/|tfm|` and used as a probing path. * If the app is executed through `dotnet.exe` then path relative to the directory with the `dotnet.exe` is used * `/store/|arch|/|tfm|` - * On Windows, the global shared store is used - * If running in WOW64 mode - `%ProgramFiles(x86)%\dotnet\store\|arch|\|tfm|` - * Otherwise - `%ProgramFiles%\dotnet\store\|arch|\|tfm|` * Additional probing paths In these paths the `|arch|/|tfm|` string can be used and will be replaced with the actual values before using the path. * `--additionalprobingpath` command line arguments diff --git a/docs/design/features/hosting-layer-apis.md b/docs/design/features/hosting-layer-apis.md index 0eab1666429bb8..c0b6b7d2b85f34 100644 --- a/docs/design/features/hosting-layer-apis.md +++ b/docs/design/features/hosting-layer-apis.md @@ -80,7 +80,7 @@ int32_t hostfxr_resolve_sdk2( hostfxr_resolve_sdk2_result_fn result) ``` -Determine the directory location of the SDK, accounting for global.json and multi-level lookup policy. +Determine the directory location of the SDK, accounting for global.json. * `exe_dir` - main directory where SDKs are located in `sdk\[version]` sub-folders. * `working_dir` - directory where the search for `global.json` will start and proceed upwards * `flags` - flags that influence resolution diff --git a/src/installer/tests/AppHost.Bundle.Tests/AppLaunch.cs b/src/installer/tests/AppHost.Bundle.Tests/AppLaunch.cs index 7f3e26b9ac56f6..509d5306af72a0 100644 --- a/src/installer/tests/AppHost.Bundle.Tests/AppLaunch.cs +++ b/src/installer/tests/AppHost.Bundle.Tests/AppLaunch.cs @@ -146,7 +146,6 @@ public void DisableCetCompat(bool selfContained) .CaptureStdErr() .CaptureStdOut() .DotNetRoot(HostTestContext.BuiltDotNet.BinPath, HostTestContext.BuildArchitecture) - .MultilevelLookup(false) .Execute() .Should().Pass() .And.HaveStdOutContaining("Hello World") diff --git a/src/installer/tests/AppHost.Bundle.Tests/BundledAppWithSubDirs.cs b/src/installer/tests/AppHost.Bundle.Tests/BundledAppWithSubDirs.cs index f6d2ece4feefc0..6f20384803e142 100644 --- a/src/installer/tests/AppHost.Bundle.Tests/BundledAppWithSubDirs.cs +++ b/src/installer/tests/AppHost.Bundle.Tests/BundledAppWithSubDirs.cs @@ -25,7 +25,6 @@ private FluentAssertions.AndConstraint RunTheApp(string CommandResult result = Command.Create(path) .EnableTracingAndCaptureOutputs() .DotNetRoot(selfContained ? null : HostTestContext.BuiltDotNet.BinPath) - .MultilevelLookup(false) .Execute(); if (deleteApp) { diff --git a/src/installer/tests/HostActivation.Tests/DependencyResolution/ComponentSharedTestStateBase.cs b/src/installer/tests/HostActivation.Tests/DependencyResolution/ComponentSharedTestStateBase.cs index d48ab759ac544e..1c8c83f3511420 100644 --- a/src/installer/tests/HostActivation.Tests/DependencyResolution/ComponentSharedTestStateBase.cs +++ b/src/installer/tests/HostActivation.Tests/DependencyResolution/ComponentSharedTestStateBase.cs @@ -62,8 +62,7 @@ public CommandResult RunComponentResolutionTest(string componentPath, TestApp ho }; Command command = Command.Create(NativeHostPath, args) - .EnableTracingAndCaptureOutputs() - .MultilevelLookup(false); + .EnableTracingAndCaptureOutputs(); commandCustomizer?.Invoke(command); return command.Execute(caller) @@ -89,7 +88,6 @@ public CommandResult RunComponentResolutionMultiThreadedTest(string componentOne return Command.Create(NativeHostPath, args) .EnableTracingAndCaptureOutputs() - .MultilevelLookup(false) .Execute(caller); } diff --git a/src/installer/tests/HostActivation.Tests/FrameworkDependentAppLaunch.cs b/src/installer/tests/HostActivation.Tests/FrameworkDependentAppLaunch.cs index f721ffc00916e2..1d47391edc81bb 100644 --- a/src/installer/tests/HostActivation.Tests/FrameworkDependentAppLaunch.cs +++ b/src/installer/tests/HostActivation.Tests/FrameworkDependentAppLaunch.cs @@ -192,7 +192,6 @@ public void AppHost() .CaptureStdErr() .CaptureStdOut() .DotNetRoot(HostTestContext.BuiltDotNet.BinPath, HostTestContext.BuildArchitecture) - .MultilevelLookup(false) .Execute() .Should().Pass() .And.HaveStdOutContaining("Hello World") @@ -210,7 +209,6 @@ public void AppHost_DisableCetCompat() .CaptureStdErr() .CaptureStdOut() .DotNetRoot(HostTestContext.BuiltDotNet.BinPath, HostTestContext.BuildArchitecture) - .MultilevelLookup(false) .Execute() .Should().Pass() .And.HaveStdOutContaining("Hello World") @@ -265,7 +263,6 @@ public void RuntimeConfig_FilePath_Breaks_MAX_PATH_Threshold() Command.Create(appExe) .DotNetRoot(HostTestContext.BuiltDotNet.BinPath) .EnableTracingAndCaptureOutputs() - .MultilevelLookup(false) .Execute() .Should().Pass() .And.HaveStdOutContaining("Hello World"); @@ -298,7 +295,6 @@ public void MissingRuntimeConfig_Fails(bool useAppHost) } command.EnableTracingAndCaptureOutputs() - .MultilevelLookup(false) .Execute() .Should().Fail() .And.HaveStdErrContaining($"The library '{Binaries.HostPolicy.FileName}' required to execute the application was not found") @@ -402,7 +398,6 @@ public void AppHost_CLI_MissingRuntimeFramework_ErrorReportedInStdErr(bool missi CommandResult result = Command.Create(sharedTestState.App.AppExe) .EnableTracingAndCaptureOutputs() .DotNetRoot(invalidDotNet.Location) - .MultilevelLookup(false) .Execute(); result.Should().Fail() @@ -433,7 +428,6 @@ public void AppHost_GUI_MissingRuntimeFramework_ErrorReportedInDialog() Command command = Command.Create(appExe) .EnableTracingAndCaptureOutputs() .DotNetRoot(invalidDotNet.Location) - .MultilevelLookup(false) .Start(); WindowsUtils.WaitForPopupFromProcess(command.Process); @@ -463,7 +457,6 @@ public void AppHost_GUI_MissingRuntime_ErrorReportedInDialog() var command = Command.Create(appExe) .EnableTracingAndCaptureOutputs() .DotNetRoot(invalidDotNet.Location) - .MultilevelLookup(false) .Start(); WindowsUtils.WaitForPopupFromProcess(command.Process); @@ -498,7 +491,6 @@ public void AppHost_GUI_NoCustomErrorWriter_FrameworkMissing_ErrorReportedInDial Command command = Command.Create(appExe) .EnableTracingAndCaptureOutputs() .DotNetRoot(dotnet.BinPath, HostTestContext.BuildArchitecture) - .MultilevelLookup(false) .Start(); WindowsUtils.WaitForPopupFromProcess(command.Process); @@ -527,7 +519,6 @@ public void AppHost_GUI_DisabledGUIErrors_DialogNotShown() Command.Create(appExe) .EnableTracingAndCaptureOutputs() .DotNetRoot(invalidDotNet.Location) - .MultilevelLookup(false) .EnvironmentVariable(Constants.DisableGuiErrors.EnvironmentVariable, "1") .Execute() .Should().Fail() diff --git a/src/installer/tests/HostActivation.Tests/FrameworkResolution/MultilevelLookup.cs b/src/installer/tests/HostActivation.Tests/FrameworkResolution/MultilevelLookup.cs deleted file mode 100644 index b1fb135e67b62a..00000000000000 --- a/src/installer/tests/HostActivation.Tests/FrameworkResolution/MultilevelLookup.cs +++ /dev/null @@ -1,178 +0,0 @@ -// 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.Collections.Generic; -using System.IO; -using System.Linq; -using System.Runtime.CompilerServices; -using Microsoft.DotNet.Cli.Build; -using Microsoft.DotNet.Cli.Build.Framework; -using Xunit; -using static Microsoft.DotNet.CoreSetup.Test.Constants; - -namespace Microsoft.DotNet.CoreSetup.Test.HostActivation.FrameworkResolution -{ - public class MultilevelLookup : - FrameworkResolutionBase, - IClassFixture - { - private SharedTestState SharedState { get; } - - public MultilevelLookup(SharedTestState sharedState) - { - Assert.SkipUnless(OperatingSystem.IsWindows(), "Multi-level lookup is only supported on Windows"); - - SharedState = sharedState; - } - - [Theory] - // MLL was enabled by default before 7.0 - // Global hive with better match (higher patch) - [InlineData("6.0.0", "netcoreapp3.1", true, "6.1.4")] - [InlineData("6.0.0", "netcoreapp3.1", null, "6.1.4")] // MLL is on by default, so same as true - [InlineData("6.0.0", "netcoreapp3.1", false, "6.1.3")] // No global hive, so the main hive version is picked - // Main hive with better match (higher patch) - [InlineData("6.2.0", "net6.0", true, "6.2.1")] - [InlineData("6.2.0", "net6.0", null, "6.2.1")] - [InlineData("6.2.0", "net6.0", false, "6.2.1")] - // MLL is disabled for 7.0+ - [InlineData("7.0.0", "net8.0", true, "7.1.2")] // MLL disabled for 7.0+ - setting it doesn't change anything - [InlineData("7.0.0", "net8.0", null, "7.1.2")] - [InlineData("7.0.0", "net8.0", false, "7.1.2")] - public void FrameworkHiveSelection(string requestedVersion, string tfm, bool? multiLevelLookup, string resolvedVersion) - { - RunTest( - runtimeConfig => runtimeConfig - .WithTfm(tfm) - .WithFramework(MicrosoftNETCoreApp, requestedVersion), - multiLevelLookup) - .ShouldHaveResolvedFrameworkOrFailToFind(MicrosoftNETCoreApp, resolvedVersion); - } - - [Fact] - public void FrameworkHiveSelection_CurrentDirectoryIsIgnored() - { - RunTest(new TestSettings() - .WithRuntimeConfigCustomizer(runtimeConfig => runtimeConfig - .WithTfm("net6.0") - .WithFramework(MicrosoftNETCoreApp, "6.0.0")) - .WithWorkingDirectory(SharedState.DotNetCurrentHive.BinPath), - multiLevelLookup: true) - .ShouldHaveResolvedFramework(MicrosoftNETCoreApp, "6.1.4"); - } - - private record struct FrameworkInfo(string Name, string Version, int Level, string Path); - - private List GetExpectedFrameworks() - { - // The runtimes should be ordered by version number - List expectedList = new(); - expectedList.AddRange( - SharedState.MainHiveVersions.Select( - v => new FrameworkInfo(MicrosoftNETCoreApp, v, 1, SharedState.DotNetMainHive.BinPath))); - expectedList.AddRange( - SharedState.GlobalHiveVersions.Select( - v => new FrameworkInfo(MicrosoftNETCoreApp, v, 2, SharedState.DotNetGlobalHive.BinPath))); - - expectedList.Sort((a, b) => { - int result = a.Name.CompareTo(b.Name); - if (result != 0) - return result; - - if (!Version.TryParse(a.Version, out var aVersion)) - return -1; - - if (!Version.TryParse(b.Version, out var bVersion)) - return 1; - - result = aVersion.CompareTo(bVersion); - if (result != 0) - return result; - - return b.Level.CompareTo(a.Level); - }); - return expectedList; - } - - [Fact] - public void FrameworkResolutionError() - { - string expectedOutput = - $"The following frameworks were found:{Environment.NewLine}" + - string.Join(string.Empty, - GetExpectedFrameworks() - .Select(t => $" {t.Version} at [{Path.Combine(t.Path, "shared", MicrosoftNETCoreApp)}]{Environment.NewLine}")); - - RunTest( - runtimeConfig => runtimeConfig - .WithTfm("net6.0") // MLL can only be enabled before 7.0 - .WithFramework(MicrosoftNETCoreApp, "9999.9.9"), - multiLevelLookup: true) - .Should().Fail() - .And.HaveStdErrContaining(expectedOutput) - .And.HaveStdErrContaining("https://aka.ms/dotnet/app-launch-failed"); - } - - private CommandResult RunTest(Func runtimeConfig, bool? multiLevelLookup, [CallerMemberName] string caller = "") - => RunTest(new TestSettings().WithRuntimeConfigCustomizer(runtimeConfig), multiLevelLookup, caller); - - private CommandResult RunTest(TestSettings testSettings, bool? multiLevelLookup, [CallerMemberName] string caller = "") - { - Command command = GetTestCommand( - SharedState.DotNetMainHive, - SharedState.App, - testSettings - .WithEnvironment(Constants.TestOnlyEnvironmentVariables.GloballyRegisteredPath, SharedState.DotNetGlobalHive.BinPath) - .WithEnvironment( // Redirect the default install location to an invalid location so that a machine-wide install is not used - Constants.TestOnlyEnvironmentVariables.DefaultInstallPath, - System.IO.Path.Combine(SharedState.DotNetMainHive.BinPath, "invalid"))); - return command.MultilevelLookup(multiLevelLookup) - .Execute(caller); - } - - public class SharedTestState : SharedTestStateBase - { - public TestApp App { get; } - - public DotNetCli DotNetMainHive { get; } - public string[] MainHiveVersions { get; } = ["6.1.3", "6.2.1", "7.1.2" ]; - - public DotNetCli DotNetGlobalHive { get; } - public string[] GlobalHiveVersions { get; } = [ "6.1.4", "6.2.0", "7.1.2" ]; - - public DotNetCli DotNetCurrentHive { get; } - - public SharedTestState() - { - DotNetBuilder mainHive = DotNet("MainHive"); - foreach (string version in MainHiveVersions) - mainHive.AddMicrosoftNETCoreAppFrameworkMockHostPolicy(version); - - DotNetMainHive = mainHive.Build(); - - DotNetBuilder globalHive = DotNet("GlobalHive"); - foreach (string version in GlobalHiveVersions) - globalHive.AddMicrosoftNETCoreAppFrameworkMockHostPolicy(version); - - DotNetGlobalHive = globalHive.Build(); - - DotNetCurrentHive = DotNet("CurrentHive") - .AddMicrosoftNETCoreAppFrameworkMockHostPolicy("6.3.0") - .AddMicrosoftNETCoreAppFrameworkMockHostPolicy("7.3.0") - .Build(); - - App = CreateFrameworkReferenceApp(); - - // Enable test-only behaviour. We don't bother disabling the behaviour later, - // as we just delete the entire copy after the tests run. - _ = TestOnlyProductBehavior.Enable(DotNetMainHive.GreatestVersionHostFxrFilePath); - } - - protected override void Dispose(bool disposing) - { - base.Dispose(disposing); - } - } - } -} diff --git a/src/installer/tests/HostActivation.Tests/HostVersionCompatibility.cs b/src/installer/tests/HostActivation.Tests/HostVersionCompatibility.cs index 693d456de0fea5..bfabfb70cd372b 100644 --- a/src/installer/tests/HostActivation.Tests/HostVersionCompatibility.cs +++ b/src/installer/tests/HostActivation.Tests/HostVersionCompatibility.cs @@ -88,10 +88,7 @@ private void OldHost_LatestRuntime_ForwardCompatible(TestApp previousVersionApp) .And.HaveStdErrContaining($"--- Invoked apphost [version: {previousVersion}"); // Use the older apphost and hostfxr - // This emulates the case when: - // 1) One-off deployment of older runtime (not in global location) - // 2) Older apphost executed, but found newer runtime because of multi-level lookup on Windows - // Note that we don't have multi-level on hostfxr so we will always find the older\one-off hostfxr + // This emulates the case when an older runtime is deployed one-off (not in the global location) if (OperatingSystem.IsWindows()) { File.Copy(previousVersionApp.HostFxrDll, app.HostFxrDll, true); diff --git a/src/installer/tests/HostActivation.Tests/InstallLocation.cs b/src/installer/tests/HostActivation.Tests/InstallLocation.cs index 8d71c05aec56ae..7fd17c8e093736 100644 --- a/src/installer/tests/HostActivation.Tests/InstallLocation.cs +++ b/src/installer/tests/HostActivation.Tests/InstallLocation.cs @@ -91,7 +91,6 @@ public void EnvironmentVariable_DotnetRootPathDoesNotExist() Command.Create(app.AppExe) .EnableTracingAndCaptureOutputs() .DotNetRoot("non_existent_path") - .MultilevelLookup(false) .EnvironmentVariable( Constants.TestOnlyEnvironmentVariables.GloballyRegisteredPath, HostTestContext.BuiltDotNet.BinPath) @@ -110,7 +109,6 @@ public void EnvironmentVariable_DotnetRootPathExistsButHasNoHost() Command.Create(app.AppExe) .EnableTracingAndCaptureOutputs() .DotNetRoot(app.Location) - .MultilevelLookup(false) .EnvironmentVariable( Constants.TestOnlyEnvironmentVariables.GloballyRegisteredPath, HostTestContext.BuiltDotNet.BinPath) diff --git a/src/installer/tests/HostActivation.Tests/NativeHosting/ComhostSideBySide.cs b/src/installer/tests/HostActivation.Tests/NativeHosting/ComhostSideBySide.cs index 5388f593996e9a..f4f1503c40a820 100644 --- a/src/installer/tests/HostActivation.Tests/NativeHosting/ComhostSideBySide.cs +++ b/src/installer/tests/HostActivation.Tests/NativeHosting/ComhostSideBySide.cs @@ -33,7 +33,6 @@ public void ActivateClass() CommandResult result = Command.Create(sharedState.ComSxsPath, args) .EnableTracingAndCaptureOutputs() .DotNetRoot(HostTestContext.BuiltDotNet.BinPath) - .MultilevelLookup(false) .Execute(); result.Should().Pass() @@ -51,7 +50,6 @@ public void LocateEmbeddedTlb() CommandResult result = Command.Create(sharedState.ComSxsPath, args) .EnableTracingAndCaptureOutputs() .DotNetRoot(HostTestContext.BuiltDotNet.BinPath) - .MultilevelLookup(false) .Execute(); result.Should().Pass() @@ -71,7 +69,6 @@ public void ManagedHost(bool selfContained) CommandResult result = Command.Create(app.AppExe, args) .EnableTracingAndCaptureOutputs() .DotNetRoot(HostTestContext.BuiltDotNet.BinPath) - .MultilevelLookup(false) .Execute(); result.Should().Pass() diff --git a/src/installer/tests/HostActivation.Tests/NativeHosting/Ijwhost.cs b/src/installer/tests/HostActivation.Tests/NativeHosting/Ijwhost.cs index 30e6e6c1ceb14c..c6e62a07750167 100644 --- a/src/installer/tests/HostActivation.Tests/NativeHosting/Ijwhost.cs +++ b/src/installer/tests/HostActivation.Tests/NativeHosting/Ijwhost.cs @@ -159,7 +159,6 @@ public void ManagedHost(bool selfContained) CommandResult result = Command.Create(app.AppExe, args) .EnableTracingAndCaptureOutputs() .DotNetRoot(HostTestContext.BuiltDotNet.BinPath) - .MultilevelLookup(false) .Execute(); result.Should().Pass() diff --git a/src/installer/tests/HostActivation.Tests/NativeHosting/Nethost.cs b/src/installer/tests/HostActivation.Tests/NativeHosting/Nethost.cs index a12150b6af6b72..4a175b90726b9b 100644 --- a/src/installer/tests/HostActivation.Tests/NativeHosting/Nethost.cs +++ b/src/installer/tests/HostActivation.Tests/NativeHosting/Nethost.cs @@ -324,7 +324,6 @@ public void TracingNotBufferedByDefault() string traceFilePath; CommandResult result = Command.Create(sharedState.NativeHostPath, $"{GetHostFxrPath} false nullptr x") .EnableHostTracingToFile(out traceFilePath) - .MultilevelLookup(true) .DotNetRoot(null) .Execute(); diff --git a/src/installer/tests/HostActivation.Tests/NativeHosting/SharedTestStateBase.cs b/src/installer/tests/HostActivation.Tests/NativeHosting/SharedTestStateBase.cs index 903a7950e49bcc..223773b1c9dea2 100644 --- a/src/installer/tests/HostActivation.Tests/NativeHosting/SharedTestStateBase.cs +++ b/src/installer/tests/HostActivation.Tests/NativeHosting/SharedTestStateBase.cs @@ -45,8 +45,7 @@ public Command CreateNativeHostCommand(IEnumerable args, string dotNetRo { return Command.Create(NativeHostPath, args) .EnableTracingAndCaptureOutputs() - .DotNetRoot(dotNetRoot) - .MultilevelLookup(false); + .DotNetRoot(dotNetRoot); } public void Dispose() diff --git a/src/installer/tests/HostActivation.Tests/SDKLookup.cs b/src/installer/tests/HostActivation.Tests/SDKLookup.cs index f84ead16406926..c87aafc3f70691 100644 --- a/src/installer/tests/HostActivation.Tests/SDKLookup.cs +++ b/src/installer/tests/HostActivation.Tests/SDKLookup.cs @@ -1229,7 +1229,6 @@ private CommandResult RunTest(string command = "help", [CallerMemberName] string return ExecutableDotNet.Exec(command) .WorkingDirectory(SharedState.CurrentWorkingDir) .EnableTracingAndCaptureOutputs() - .MultilevelLookup(false) .Execute(caller); } diff --git a/src/installer/tests/TestUtils/CommandExtensions.cs b/src/installer/tests/TestUtils/CommandExtensions.cs index e5d7be4b81bb1c..221c1f60340c4c 100644 --- a/src/installer/tests/TestUtils/CommandExtensions.cs +++ b/src/installer/tests/TestUtils/CommandExtensions.cs @@ -55,14 +55,6 @@ public static Command DotNetRoot(this Command command, string dotNetRoot, string .EnvironmentVariable(Constants.DotnetRoot.WindowsX86EnvironmentVariable, dotNetRoot); } - public static Command MultilevelLookup(this Command command, bool? enable) - { - if (enable.HasValue) - return command.EnvironmentVariable(Constants.MultilevelLookup.EnvironmentVariable, enable.Value ? "1" : "0"); - - return command.RemoveEnvironmentVariable(Constants.MultilevelLookup.EnvironmentVariable); - } - public static Command RuntimeId(this Command command, string rid) { return command.EnvironmentVariable(Constants.RuntimeId.EnvironmentVariable, rid); diff --git a/src/installer/tests/TestUtils/Constants.cs b/src/installer/tests/TestUtils/Constants.cs index e965aa55e7c03a..2c713dd53d7219 100644 --- a/src/installer/tests/TestUtils/Constants.cs +++ b/src/installer/tests/TestUtils/Constants.cs @@ -101,11 +101,6 @@ public static class RuntimeId public const string EnvironmentVariable = "DOTNET_RUNTIME_ID"; } - public static class MultilevelLookup - { - public const string EnvironmentVariable = "DOTNET_MULTILEVEL_LOOKUP"; - } - public static class HostTracing { public const string TraceLevelEnvironmentVariable = "DOTNET_HOST_TRACE"; diff --git a/src/installer/tests/TestUtils/DotNetCli.cs b/src/installer/tests/TestUtils/DotNetCli.cs index b35f4e09a2bc86..e91bc90b5129e5 100644 --- a/src/installer/tests/TestUtils/DotNetCli.cs +++ b/src/installer/tests/TestUtils/DotNetCli.cs @@ -46,8 +46,7 @@ public Command Exec(string command, params string[] args) newArgs.Insert(0, command); return Command.Create(DotnetExecutablePath, newArgs) - .EnvironmentVariable("DOTNET_NOLOGO", "1") - .MultilevelLookup(false); // Avoid looking at machine state by default + .EnvironmentVariable("DOTNET_NOLOGO", "1"); } } } From b1651994c282271512df3fc43b706d86cba35347 Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Fri, 14 Aug 2026 21:03:53 -0700 Subject: [PATCH 3/3] Remove test --- .../NativeHosting/Nethost.cs | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/src/installer/tests/HostActivation.Tests/NativeHosting/Nethost.cs b/src/installer/tests/HostActivation.Tests/NativeHosting/Nethost.cs index 4a175b90726b9b..a049b347e24273 100644 --- a/src/installer/tests/HostActivation.Tests/NativeHosting/Nethost.cs +++ b/src/installer/tests/HostActivation.Tests/NativeHosting/Nethost.cs @@ -318,22 +318,6 @@ public void GetHostFxrPath_InvalidParameters() .And.HaveStdErrContaining("Invalid size for get_hostfxr_parameters"); } - [Fact] - public void TracingNotBufferedByDefault() - { - string traceFilePath; - CommandResult result = Command.Create(sharedState.NativeHostPath, $"{GetHostFxrPath} false nullptr x") - .EnableHostTracingToFile(out traceFilePath) - .DotNetRoot(null) - .Execute(); - - result.Should().Fail() - .And.FileExists(traceFilePath) - .And.FileContains(traceFilePath, "Tracing enabled"); - - FileUtils.DeleteFileIfPossible(traceFilePath); - } - [Fact] public void TestOnlyDisabledByDefault() {