diff --git a/src/installer/tests/HostActivation.Tests/MultiArchInstallLocation.cs b/src/installer/tests/HostActivation.Tests/MultiArchInstallLocation.cs index a84ec8e3ef6d7b..fa7733f2c11e06 100644 --- a/src/installer/tests/HostActivation.Tests/MultiArchInstallLocation.cs +++ b/src/installer/tests/HostActivation.Tests/MultiArchInstallLocation.cs @@ -4,6 +4,7 @@ using System; using System.IO; using System.Runtime.InteropServices; +using Microsoft.DotNet.Cli.Build; using Microsoft.DotNet.Cli.Build.Framework; using Microsoft.DotNet.CoreSetup.Test; using Microsoft.DotNet.CoreSetup.Test.HostActivation; @@ -146,6 +147,44 @@ public void EnvironmentVariable_DotnetRootPathExistsButHasNoHost() } } + [Fact] + public void EnvironmentVariable_DotNetInfo_ListEnvironment() + { + var dotnet = new DotNetCli(sharedTestState.RepoDirectories.BuiltDotnet); + + var command = dotnet.Exec("--info") + .CaptureStdOut(); + + var envVars = new (string Architecture, string Path)[] { + ("arm64", "/arm64/dotnet/root"), + ("x64", "/x64/dotnet/root"), + ("x86", "/x86/dotnet/root") + }; + foreach(var envVar in envVars) + { + command = command.DotNetRoot(envVar.Path, envVar.Architecture); + } + + string dotnetRootNoArch = "/dotnet/root"; + command = command.DotNetRoot(dotnetRootNoArch); + + (string Architecture, string Path) unknownEnvVar = ("unknown", "/unknown/dotnet/root"); + command = command.DotNetRoot(unknownEnvVar.Path, unknownEnvVar.Architecture); + + var result = command.Execute(); + result.Should().Pass() + .And.HaveStdOutContaining("Environment variables:") + .And.HaveStdOutMatching($@"{Constants.DotnetRoot.EnvironmentVariable}\s*\[{dotnetRootNoArch}\]") + .And.NotHaveStdOutContaining($"{Constants.DotnetRoot.ArchitectureEnvironmentVariablePrefix}{unknownEnvVar.Architecture.ToUpper()}") + .And.NotHaveStdOutContaining($"[{unknownEnvVar.Path}]"); + + foreach ((string architecture, string path) in envVars) + { + result.Should() + .HaveStdOutMatching($@"{Constants.DotnetRoot.ArchitectureEnvironmentVariablePrefix}{architecture.ToUpper()}\s*\[{path}\]"); + } + } + [Fact] public void RegisteredInstallLocation_ArchSpecificLocationIsPickedFirst() { @@ -241,6 +280,49 @@ public void InstallLocationFile_MissingFile() } } + [Fact] + public void RegisteredInstallLocation_DotNetInfo_ListOtherArchitectures() + { + using (var testArtifact = new TestArtifact(SharedFramework.CalculateUniqueTestDirectory(Path.Combine(TestArtifact.TestArtifactsPath, "listOtherArchs")))) + { + var dotnet = new DotNetBuilder(testArtifact.Location, sharedTestState.RepoDirectories.BuiltDotnet, "exe").Build(); + using (var registeredInstallLocationOverride = new RegisteredInstallLocationOverride(dotnet.GreatestVersionHostFxrFilePath)) + { + var installLocations = new (string, string)[] { + ("arm64", "/arm64/install/path"), + ("x64", "/x64/install/path"), + ("x86", "/x86/install/path") + }; + (string Architecture, string Path) unknownArchInstall = ("unknown", "/unknown/install/path"); + registeredInstallLocationOverride.SetInstallLocation(installLocations); + registeredInstallLocationOverride.SetInstallLocation(unknownArchInstall); + + var result = dotnet.Exec("--info") + .CaptureStdOut() + .ApplyRegisteredInstallLocationOverride(registeredInstallLocationOverride) + .Execute(); + + result.Should().Pass() + .And.HaveStdOutContaining("Other architectures found:") + .And.NotHaveStdOutContaining(unknownArchInstall.Architecture) + .And.NotHaveStdOutContaining($"[{unknownArchInstall.Path}]"); + + string pathOverride = OperatingSystem.IsWindows() // Host uses short form of base key for Windows + ? registeredInstallLocationOverride.PathValueOverride.Replace(Microsoft.Win32.Registry.CurrentUser.Name, "HKCU") + : registeredInstallLocationOverride.PathValueOverride; + pathOverride = System.Text.RegularExpressions.Regex.Escape(pathOverride); + foreach ((string arch, string path) in installLocations) + { + if (arch == sharedTestState.RepoDirectories.BuildArchitecture) + continue; + + result.Should() + .HaveStdOutMatching($@"{arch}\s*\[{path}\]\r?$\s*registered at \[{pathOverride}.*{arch}.*\]", System.Text.RegularExpressions.RegexOptions.Multiline); + } + } + } + } + public class SharedTestState : IDisposable { public string BaseDirectory { get; } diff --git a/src/installer/tests/HostActivation.Tests/SDKLookup.cs b/src/installer/tests/HostActivation.Tests/SDKLookup.cs index 3828213e18ca59..7d28535c248a95 100644 --- a/src/installer/tests/HostActivation.Tests/SDKLookup.cs +++ b/src/installer/tests/HostActivation.Tests/SDKLookup.cs @@ -49,7 +49,7 @@ public void SdkLookup_Global_Json_Single_Digit_Patch_Rollup() .Should().Fail() .And.NotFindCompatibleSdk(globalJsonPath, requestedVersion) .And.FindAnySdk(false) - .And.HaveStdErrContaining("aka.ms/dotnet-download") + .And.HaveStdErrContaining("aka.ms/dotnet/download") .And.NotHaveStdErrContaining("Checking if resolved SDK dir"); // Add SDK versions diff --git a/src/installer/tests/TestUtils/DotNetCli.cs b/src/installer/tests/TestUtils/DotNetCli.cs index 513deea28b3b4d..08e40aaf25e6c1 100644 --- a/src/installer/tests/TestUtils/DotNetCli.cs +++ b/src/installer/tests/TestUtils/DotNetCli.cs @@ -12,7 +12,7 @@ public partial class DotNetCli { public string BinPath { get; } public string GreatestVersionSharedFxPath { get; } - public string GreatestVersionHostFxrPath { get; } + public string GreatestVersionHostFxrPath { get; } public string GreatestVersionHostFxrFilePath { get => Path.Combine( GreatestVersionHostFxrPath, RuntimeInformationExtensions.GetSharedLibraryFileNameForCurrentPlatform("hostfxr")); } @@ -29,30 +29,22 @@ public DotNetCli(string binPath) BinPath = binPath; var sharedFxBaseDirectory = Path.Combine(BinPath, "shared", "Microsoft.NETCore.App"); - if (!Directory.Exists(sharedFxBaseDirectory)) + if (Directory.Exists(sharedFxBaseDirectory)) { - GreatestVersionSharedFxPath = null; - return; + var sharedFxVersionDirectories = Directory.EnumerateDirectories(sharedFxBaseDirectory); + GreatestVersionSharedFxPath = sharedFxVersionDirectories + .OrderByDescending(p => p.ToLower()) + .First(); } var hostFxrBaseDirectory = Path.Combine(BinPath, "host", "fxr"); - - if (!Directory.Exists(hostFxrBaseDirectory)) + if (Directory.Exists(hostFxrBaseDirectory)) { - GreatestVersionHostFxrPath = null; - return; + var hostFxrVersionDirectories = Directory.EnumerateDirectories(hostFxrBaseDirectory); + GreatestVersionHostFxrPath = hostFxrVersionDirectories + .OrderByDescending(p => p.ToLower()) + .First(); } - - var sharedFxVersionDirectories = Directory.EnumerateDirectories(sharedFxBaseDirectory); - - GreatestVersionSharedFxPath = sharedFxVersionDirectories - .OrderByDescending(p => p.ToLower()) - .First(); - - var hostFxrVersionDirectories = Directory.EnumerateDirectories(hostFxrBaseDirectory); - GreatestVersionHostFxrPath = hostFxrVersionDirectories - .OrderByDescending(p => p.ToLower()) - .First(); } public Command Exec(string command, params string[] args) diff --git a/src/native/corehost/apphost/apphost.windows.cpp b/src/native/corehost/apphost/apphost.windows.cpp index c43f6105f59035..528ce95f859193 100644 --- a/src/native/corehost/apphost/apphost.windows.cpp +++ b/src/native/corehost/apphost/apphost.windows.cpp @@ -73,7 +73,7 @@ namespace pal::string_t get_apphost_details_message() { pal::string_t msg = _X("Architecture: "); - msg.append(get_arch()); + msg.append(get_current_arch_name()); msg.append(_X("\n") _X("App host version: ") _STRINGIFY(COMMON_HOST_PKG_VER) _X("\n\n")); return msg; diff --git a/src/native/corehost/corehost.cpp b/src/native/corehost/corehost.cpp index e8fa78647ef9c9..97a4e0753895cd 100644 --- a/src/native/corehost/corehost.cpp +++ b/src/native/corehost/corehost.cpp @@ -100,7 +100,7 @@ void need_newer_framework_error(const pal::string_t& dotnet_root, const pal::str _X("Download the .NET runtime:\n") _X("%s&apphost_version=%s"), host_path.c_str(), - get_arch(), + get_current_arch_name(), _STRINGIFY(COMMON_HOST_PKG_VER), dotnet_root.c_str(), get_download_url().c_str(), @@ -225,7 +225,7 @@ int exe_start(const int argc, const pal::char_t* argv[]) else { // An outdated hostfxr can only be found for framework-related apps. - trace::error(_X("The required library %s does not support single-file apps."), fxr.fxr_path().c_str()); + trace::error(_X("The required library %s does not support single-file apps."), fxr.fxr_path().c_str()); need_newer_framework_error(fxr.dotnet_root(), host_path); rc = StatusCode::FrameworkMissingFailure; } diff --git a/src/native/corehost/deps_format.cpp b/src/native/corehost/deps_format.cpp index 32f9d7c8df39ba..9abf6ae8f69223 100644 --- a/src/native/corehost/deps_format.cpp +++ b/src/native/corehost/deps_format.cpp @@ -130,7 +130,7 @@ pal::string_t deps_json_t::get_current_rid(const rid_fallback_graph_t& rid_fallb // We do the same even when the RID is empty. if (currentRid.empty() || (rid_fallback_graph.count(currentRid) == 0)) { - currentRid = pal::get_current_os_fallback_rid() + pal::string_t(_X("-")) + get_arch(); + currentRid = pal::get_current_os_fallback_rid() + pal::string_t(_X("-")) + get_current_arch_name(); trace::info(_X("Falling back to base HostRID: %s"), currentRid.c_str()); } diff --git a/src/native/corehost/fxr/command_line.cpp b/src/native/corehost/fxr/command_line.cpp index 7f6916273f9e38..ea04984877b572 100644 --- a/src/native/corehost/fxr/command_line.cpp +++ b/src/native/corehost/fxr/command_line.cpp @@ -4,6 +4,7 @@ #include "command_line.h" #include #include "framework_info.h" +#include "install_info.h" #include #include "sdk_info.h" #include @@ -279,37 +280,58 @@ int command_line::parse_args_for_sdk_command( return parse_args(host_info, 1, argc, argv, false, host_mode_t::muxer, new_argoff, app_candidate, opts); } -void command_line::print_muxer_info(const pal::string_t &dotnet_root) +void command_line::print_muxer_info(const pal::string_t &dotnet_root, const pal::string_t &global_json_path) { - trace::println(); - trace::println(_X("Host:")); - trace::println(_X(" Version: %s"), _STRINGIFY(HOST_FXR_PKG_VER)); - trace::println(_X(" Architecture: %s"), get_arch()); - pal::string_t commit = _STRINGIFY(REPO_COMMIT_HASH); - trace::println(_X(" Commit: %s"), commit.substr(0, 10).c_str()); - - trace::println(); - trace::println(_X(".NET SDKs installed:")); + trace::println(_X("\n") + _X("Host:\n") + _X(" Version: ") _STRINGIFY(HOST_FXR_PKG_VER) _X("\n") + _X(" Architecture: %s\n") + _X(" Commit: %s"), + get_current_arch_name(), + commit.substr(0, 10).c_str()); + + trace::println(_X("\n") + _X(".NET SDKs installed:")); if (!sdk_info::print_all_sdks(dotnet_root, _X(" "))) { trace::println(_X(" No SDKs were found.")); } - trace::println(); - trace::println(_X(".NET runtimes installed:")); + trace::println(_X("\n") + _X(".NET runtimes installed:")); if (!framework_info::print_all_frameworks(dotnet_root, _X(" "))) { trace::println(_X(" No runtimes were found.")); } - trace::println(); - trace::println(_X("Download .NET:")); - trace::println(_X(" %s"), DOTNET_CORE_DOWNLOAD_URL); + trace::println(_X("\n") + _X("Other architectures found:")); + if (!install_info::print_other_architectures(_X(" "))) + { + trace::println(_X(" None")); + } - trace::println(); - trace::println(_X("Learn about .NET Runtimes and SDKs:")); - trace::println(_X(" %s"), DOTNET_INFO_URL);} + trace::println(_X("\n") + _X("Environment variables:")); + if (!install_info::print_environment(_X(" "))) + { + trace::println(_X(" Not set")); + } + + trace::println(_X("\n") + _X("global.json file:\n") + _X(" %s"), + global_json_path.empty() ? _X("Not found") : global_json_path.c_str()); + + trace::println(_X("\n") + _X("Learn more:\n") + _X(" ") DOTNET_INFO_URL); + + trace::println(_X("\n") + _X("Download .NET:\n") + _X(" ") DOTNET_CORE_DOWNLOAD_URL); +} void command_line::print_muxer_usage(bool is_sdk_present) { diff --git a/src/native/corehost/fxr/command_line.h b/src/native/corehost/fxr/command_line.h index 118206b13a9a67..4880ea60f0e45c 100644 --- a/src/native/corehost/fxr/command_line.h +++ b/src/native/corehost/fxr/command_line.h @@ -57,7 +57,7 @@ namespace command_line /*out*/ pal::string_t &app_candidate, /*out*/ opt_map_t &opts); - void print_muxer_info(const pal::string_t &dotnet_root); + void print_muxer_info(const pal::string_t &dotnet_root, const pal::string_t &global_json_path); void print_muxer_usage(bool is_sdk_present); }; diff --git a/src/native/corehost/fxr/files.cmake b/src/native/corehost/fxr/files.cmake index 1d46688166aaa0..f2bea0e2c7fe01 100644 --- a/src/native/corehost/fxr/files.cmake +++ b/src/native/corehost/fxr/files.cmake @@ -15,6 +15,7 @@ list(APPEND SOURCES ${CMAKE_CURRENT_LIST_DIR}/fx_resolver.messages.cpp ${CMAKE_CURRENT_LIST_DIR}/framework_info.cpp ${CMAKE_CURRENT_LIST_DIR}/host_context.cpp + ${CMAKE_CURRENT_LIST_DIR}/install_info.cpp ${CMAKE_CURRENT_LIST_DIR}/sdk_info.cpp ${CMAKE_CURRENT_LIST_DIR}/sdk_resolver.cpp ) @@ -31,6 +32,7 @@ list(APPEND HEADERS ${CMAKE_CURRENT_LIST_DIR}/fx_resolver.h ${CMAKE_CURRENT_LIST_DIR}/framework_info.h ${CMAKE_CURRENT_LIST_DIR}/host_context.h + ${CMAKE_CURRENT_LIST_DIR}/install_info.h ${CMAKE_CURRENT_LIST_DIR}/sdk_info.h ${CMAKE_CURRENT_LIST_DIR}/sdk_resolver.h ) diff --git a/src/native/corehost/fxr/fx_muxer.cpp b/src/native/corehost/fxr/fx_muxer.cpp index 877dd03e454df1..e4a9f4139751ad 100644 --- a/src/native/corehost/fxr/fx_muxer.cpp +++ b/src/native/corehost/fxr/fx_muxer.cpp @@ -245,7 +245,7 @@ void append_probe_realpath(const pal::string_t& path, std::vector if (pos_placeholder != pal::string_t::npos) { - pal::string_t segment = get_arch(); + pal::string_t segment = get_current_arch_name(); segment.push_back(DIR_SEPARATOR); segment.append(tfm); probe_path.replace(pos_placeholder, placeholder.length(), segment); @@ -1066,8 +1066,7 @@ int fx_muxer_t::handle_cli( } else if (pal::strcasecmp(_X("--info"), argv[1]) == 0) { - resolver.print_global_file_path(); - command_line::print_muxer_info(host_info.dotnet_root); + command_line::print_muxer_info(host_info.dotnet_root, resolver.global_file_path()); return StatusCode::Success; } @@ -1124,8 +1123,7 @@ int fx_muxer_t::handle_cli( if (pal::strcasecmp(_X("--info"), argv[1]) == 0) { - resolver.print_global_file_path(); - command_line::print_muxer_info(host_info.dotnet_root); + command_line::print_muxer_info(host_info.dotnet_root, resolver.global_file_path()); } return result; diff --git a/src/native/corehost/fxr/fx_resolver.cpp b/src/native/corehost/fxr/fx_resolver.cpp index 14a40012c78439..e31e100b088fd8 100644 --- a/src/native/corehost/fxr/fx_resolver.cpp +++ b/src/native/corehost/fxr/fx_resolver.cpp @@ -448,7 +448,7 @@ StatusCode fx_resolver_t::read_framework( _X("App: %s\n") _X("Architecture: %s"), app_display_name != nullptr ? app_display_name : host_info.host_path.c_str(), - get_arch()); + get_current_arch_name()); display_missing_framework_error(fx_name, new_effective_fx_ref.get_fx_version(), pal::string_t(), host_info.dotnet_root, disable_multilevel_lookup); return FrameworkMissingFailure; } diff --git a/src/native/corehost/fxr/fx_resolver.messages.cpp b/src/native/corehost/fxr/fx_resolver.messages.cpp index e67344d9449dab..1113e9ced98223 100644 --- a/src/native/corehost/fxr/fx_resolver.messages.cpp +++ b/src/native/corehost/fxr/fx_resolver.messages.cpp @@ -113,11 +113,11 @@ void fx_resolver_t::display_missing_framework_error( // Display the error message about missing FX. if (fx_version.length()) { - trace::error(_X("Framework: '%s', version '%s' (%s)"), fx_name.c_str(), fx_version.c_str(), get_arch()); + trace::error(_X("Framework: '%s', version '%s' (%s)"), fx_name.c_str(), fx_version.c_str(), get_current_arch_name()); } else { - trace::error(_X("Framework: '%s', (%s)"), fx_name.c_str(), get_arch()); + trace::error(_X("Framework: '%s', (%s)"), fx_name.c_str(), get_current_arch_name()); } trace::error(_X(".NET location: %s\n"), dotnet_root.c_str()); diff --git a/src/native/corehost/fxr/install_info.cpp b/src/native/corehost/fxr/install_info.cpp new file mode 100644 index 00000000000000..ff231938db0d87 --- /dev/null +++ b/src/native/corehost/fxr/install_info.cpp @@ -0,0 +1,59 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#include "install_info.h" +#include "pal.h" +#include "trace.h" +#include "utils.h" + +bool install_info::print_environment(const pal::char_t* leading_whitespace) +{ + bool found_any = false; + + const pal::char_t* fmt = _X("%s%-17s [%s]"); + pal::string_t value; + if (pal::getenv(DOTNET_ROOT_ENV_VAR, &value)) + { + found_any = true; + trace::println(fmt, leading_whitespace, DOTNET_ROOT_ENV_VAR, value.c_str()); + } + + for (uint32_t i = 0; i < static_cast(pal::architecture::__last); ++i) + { + pal::string_t env_var = get_dotnet_root_env_var_for_arch(static_cast(i)); + if (pal::getenv(env_var.c_str(), &value)) + { + found_any = true; + trace::println(fmt, leading_whitespace, env_var.c_str(), value.c_str()); + } + } + + return found_any; +} + +bool install_info::print_other_architectures(const pal::char_t* leading_whitespace) +{ + bool found_any = false; + for (uint32_t i = 0; i < static_cast(pal::architecture::__last); ++i) + { + pal::architecture arch = static_cast(i); + if (arch == get_current_arch()) + continue; + + pal::string_t install_location; + bool is_registered = pal::get_dotnet_self_registered_dir_for_arch(arch, &install_location); + if (is_registered + || (pal::get_default_installation_dir_for_arch(arch, &install_location) && pal::directory_exists(install_location))) + { + found_any = true; + remove_trailing_dir_separator(&install_location); + trace::println(_X("%s%-5s [%s]"), leading_whitespace, get_arch_name(arch), install_location.c_str()); + if (is_registered) + { + trace::println(_X("%s registered at [%s]"), leading_whitespace, pal::get_dotnet_self_registered_config_location(arch).c_str()); + } + } + } + + return found_any; +} diff --git a/src/native/corehost/fxr/install_info.h b/src/native/corehost/fxr/install_info.h new file mode 100644 index 00000000000000..3a100486b8f3e5 --- /dev/null +++ b/src/native/corehost/fxr/install_info.h @@ -0,0 +1,15 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#ifndef __INSTALL_INFO_H__ +#define __INSTALL_INFO_H__ + +#include "pal.h" + +namespace install_info +{ + bool print_environment(const pal::char_t* leading_whitespace); + bool print_other_architectures(const pal::char_t* leading_whitespace); +}; + +#endif // __INSTALL_INFO_H__ diff --git a/src/native/corehost/fxr/sdk_resolver.cpp b/src/native/corehost/fxr/sdk_resolver.cpp index 6f532042ef44a9..a2c76a600665c1 100644 --- a/src/native/corehost/fxr/sdk_resolver.cpp +++ b/src/native/corehost/fxr/sdk_resolver.cpp @@ -90,15 +90,6 @@ pal::string_t sdk_resolver::resolve(const pal::string_t& dotnet_root, bool print return {}; } -void sdk_resolver::print_global_file_path() -{ - trace::println( - _X("\n") - _X("global.json file:\n") - _X(" %s"), - global_file.empty() ? _X("Not found") : global_file.c_str()); -} - void sdk_resolver::print_resolution_error(const pal::string_t& dotnet_root, const pal::char_t *main_error_prefix) const { bool sdk_exists = false; diff --git a/src/native/corehost/fxr/sdk_resolver.h b/src/native/corehost/fxr/sdk_resolver.h index 936a6d950c61b1..064f22b4567cf8 100644 --- a/src/native/corehost/fxr/sdk_resolver.h +++ b/src/native/corehost/fxr/sdk_resolver.h @@ -43,7 +43,6 @@ class sdk_resolver pal::string_t resolve(const pal::string_t& dotnet_root, bool print_errors = true) const; - void print_global_file_path(); void print_resolution_error(const pal::string_t& dotnet_root, const pal::char_t *prefix) const; static sdk_resolver from_nearest_global_file(bool allow_prerelease = true); diff --git a/src/native/corehost/fxr_resolver.cpp b/src/native/corehost/fxr_resolver.cpp index e70ec907a1393a..c2f8666f56be73 100644 --- a/src/native/corehost/fxr_resolver.cpp +++ b/src/native/corehost/fxr_resolver.cpp @@ -100,7 +100,7 @@ bool fxr_resolver::try_get_path(const pal::string_t& root_path, pal::string_t* o pal::get_default_installation_dir(&default_install_location); } - pal::string_t self_registered_config_location = pal::get_dotnet_self_registered_config_location(); + pal::string_t self_registered_config_location = pal::get_dotnet_self_registered_config_location(get_current_arch()); trace::verbose(_X("The required library %s could not be found. Searched with root path [%s], environment variable [%s], default install location [%s], self-registered config location [%s]"), LIBFXR_NAME, root_path.c_str(), @@ -124,7 +124,7 @@ bool fxr_resolver::try_get_path(const pal::string_t& root_path, pal::string_t* o _X("Download the .NET runtime:\n") _X("%s&apphost_version=%s"), host_path.c_str(), - get_arch(), + get_current_arch_name(), _STRINGIFY(COMMON_HOST_PKG_VER), get_download_url().c_str(), _STRINGIFY(COMMON_HOST_PKG_VER)); diff --git a/src/native/corehost/hostmisc/pal.h b/src/native/corehost/hostmisc/pal.h index 55d03f572299f3..1f91504a6d9b57 100644 --- a/src/native/corehost/hostmisc/pal.h +++ b/src/native/corehost/hostmisc/pal.h @@ -293,15 +293,35 @@ namespace pal bool getenv(const char_t* name, string_t* recv); bool get_default_servicing_directory(string_t* recv); - // Returns the globally registered install location (if any) + enum class architecture + { + arm, + arm64, + armv6, + loongarch64, + ppc64le, + s390X, + x64, + x86, + + __last // Sentinel value + }; + + // Returns the globally registered install location (if any) for the current architecture bool get_dotnet_self_registered_dir(string_t* recv); + // Returns the globally registered install location (if any) for the specified architecture + bool get_dotnet_self_registered_dir_for_arch(architecture arch, string_t* recv); + // Returns name of the config location for global install registration (for example, registry key or file path) - string_t get_dotnet_self_registered_config_location(); + string_t get_dotnet_self_registered_config_location(architecture arch); - // Returns the default install location for a given platform + // Returns the default install location for a given platform for the current architecture bool get_default_installation_dir(string_t* recv); + // 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); diff --git a/src/native/corehost/hostmisc/pal.unix.cpp b/src/native/corehost/hostmisc/pal.unix.cpp index a71af30832f470..a961a981e0f79a 100644 --- a/src/native/corehost/hostmisc/pal.unix.cpp +++ b/src/native/corehost/hostmisc/pal.unix.cpp @@ -414,16 +414,19 @@ bool pal::get_global_dotnet_dirs(std::vector* recv) return false; } -pal::string_t pal::get_dotnet_self_registered_config_location() +pal::string_t pal::get_dotnet_self_registered_config_location(pal::architecture arch) { + pal::string_t config_location = _X("/etc/dotnet"); + // ***Used only for testing*** pal::string_t environment_install_location_override; if (test_only_getenv(_X("_DOTNET_TEST_INSTALL_LOCATION_PATH"), &environment_install_location_override)) { - return environment_install_location_override; + config_location = environment_install_location_override; } - return _X("/etc/dotnet"); + append_path(&config_location, (_X("install_location_") + to_lower(get_arch_name(arch))).c_str()); + return config_location; } namespace @@ -487,8 +490,6 @@ bool get_install_location_from_file(const pal::string_t& file_path, bool& file_f bool pal::get_dotnet_self_registered_dir(pal::string_t* recv) { - recv->clear(); - // ***Used only for testing*** pal::string_t environment_override; if (test_only_getenv(_X("_DOTNET_TEST_GLOBALLY_REGISTERED_PATH"), &environment_override)) @@ -498,9 +499,14 @@ bool pal::get_dotnet_self_registered_dir(pal::string_t* recv) } // *************************** - pal::string_t install_location_path = get_dotnet_self_registered_config_location(); - pal::string_t arch_specific_install_location_file_path = install_location_path; - append_path(&arch_specific_install_location_file_path, (_X("install_location_") + to_lower(get_arch())).c_str()); + return pal::get_dotnet_self_registered_dir_for_arch(get_current_arch(), recv); +} + +bool pal::get_dotnet_self_registered_dir_for_arch(pal::architecture arch, pal::string_t* recv) +{ + recv->clear(); + + pal::string_t arch_specific_install_location_file_path = get_dotnet_self_registered_config_location(arch); trace::verbose(_X("Looking for architecture-specific install_location file in '%s'."), arch_specific_install_location_file_path.c_str()); pal::string_t install_location; @@ -512,11 +518,19 @@ bool pal::get_dotnet_self_registered_dir(pal::string_t* recv) return false; } - pal::string_t legacy_install_location_file_path = install_location_path; - append_path(&legacy_install_location_file_path, _X("install_location")); - trace::verbose(_X("Looking for install_location file in '%s'."), legacy_install_location_file_path.c_str()); + // If looking for the current architecture, also look for the non-architecture-specific file + if (arch == get_current_arch()) + { + pal::string_t legacy_install_location_file_path = get_directory(arch_specific_install_location_file_path); + append_path(&legacy_install_location_file_path, _X("install_location")); + trace::verbose(_X("Looking for install_location file in '%s'."), legacy_install_location_file_path.c_str()); - if (!get_install_location_from_file(legacy_install_location_file_path, file_found, install_location)) + if (!get_install_location_from_file(legacy_install_location_file_path, file_found, install_location)) + { + return false; + } + } + else { return false; } @@ -524,7 +538,21 @@ bool pal::get_dotnet_self_registered_dir(pal::string_t* recv) recv->assign(install_location); trace::verbose(_X("Found registered install location '%s'."), recv->c_str()); - return true; + return file_found; +} + +namespace +{ + bool is_supported_multi_arch_install(pal::architecture arch) + { +#if defined(TARGET_OSX) && defined(TARGET_ARM64) + // arm64, looking for x64 install + return arch == pal::architecture::x64; +#else + // Others do not support default install locations on a different architecture + return false; +#endif + } } bool pal::get_default_installation_dir(pal::string_t* recv) @@ -538,12 +566,31 @@ bool pal::get_default_installation_dir(pal::string_t* recv) } // *************************** + return get_default_installation_dir_for_arch(get_current_arch(), recv); +} + +bool pal::get_default_installation_dir_for_arch(pal::architecture arch, pal::string_t* recv) +{ + bool is_current_arch = arch == get_current_arch(); + + // Bail out early for unsupported requests for different architectures + if (!is_current_arch && !is_supported_multi_arch_install(arch)) + return false; + #if defined(TARGET_OSX) recv->assign(_X("/usr/local/share/dotnet")); - if (pal::is_emulating_x64()) + if (is_current_arch && pal::is_emulating_x64()) + { + append_path(recv, get_arch_name(arch)); + } +#if defined(TARGET_ARM64) + else if (!is_current_arch) { - append_path(recv, _X("x64")); + // Running arm64, looking for x64 install + assert(arch == pal::architecture::x64); + append_path(recv, get_arch_name(arch)); } +#endif #else recv->assign(_X("/usr/share/dotnet")); #endif diff --git a/src/native/corehost/hostmisc/pal.windows.cpp b/src/native/corehost/hostmisc/pal.windows.cpp index 0eacdd8f5bc1b0..bbb6fabb30b79f 100644 --- a/src/native/corehost/hostmisc/pal.windows.cpp +++ b/src/native/corehost/hostmisc/pal.windows.cpp @@ -265,6 +265,27 @@ bool pal::get_default_servicing_directory(string_t* recv) return true; } +namespace +{ + bool is_supported_multi_arch_install(pal::architecture arch) + { +#if defined(TARGET_AMD64) + // x64, looking for x86 install or emulating x64, looking for arm64 install + return arch == pal::architecture::x86 + || (arch == pal::architecture::arm64 && pal::is_emulating_x64()); +#elif defined(TARGET_ARM64) + // arm64, looking for x64 install + return arch == pal::architecture::x64; +#elif defined(TARGET_X86) + // x86 running in WoW64, looking for x64 install + return arch == pal::architecture::x64 && pal::is_running_in_wow64(); +#else + // Others do not support default install locations on a different architecture + return false; +#endif + } +} + bool pal::get_default_installation_dir(pal::string_t* recv) { // ***Used only for testing*** @@ -276,11 +297,30 @@ bool pal::get_default_installation_dir(pal::string_t* recv) } // *************************** + return get_default_installation_dir_for_arch(get_current_arch(), recv); +} + +bool pal::get_default_installation_dir_for_arch(pal::architecture arch, pal::string_t* recv) +{ + bool is_current_arch = arch == get_current_arch(); + + // Bail out early for unsupported requests for different architectures + if (!is_current_arch && !is_supported_multi_arch_install(arch)) + return false; + const pal::char_t* program_files_dir; - if (pal::is_running_in_wow64()) + if (is_current_arch && pal::is_running_in_wow64()) { + // Running x86 on x64, looking for x86 install program_files_dir = _X("ProgramFiles(x86)"); } +#if defined(TARGET_AMD64) + else if (!is_current_arch && arch == pal::architecture::x86) + { + // Running x64, looking for x86 install + program_files_dir = _X("ProgramFiles(x86)"); + } +#endif else { program_files_dir = _X("ProgramFiles"); @@ -292,18 +332,26 @@ bool pal::get_default_installation_dir(pal::string_t* recv) } append_path(recv, _X("dotnet")); - if (pal::is_emulating_x64()) + if (is_current_arch && pal::is_emulating_x64()) { // Install location for emulated x64 should be %ProgramFiles%\dotnet\x64. - append_path(recv, _X("x64")); + append_path(recv, get_arch_name(arch)); + } +#if defined(TARGET_ARM64) + else if (!is_current_arch) + { + // Running arm64, looking for x64 install + assert(arch == pal::architecture::x64); + append_path(recv, get_arch_name(arch)); } +#endif return true; } namespace { - void get_dotnet_install_location_registry_path(HKEY * key_hive, pal::string_t * sub_key, const pal::char_t ** value) + void get_dotnet_install_location_registry_path(pal::architecture arch, HKEY * key_hive, pal::string_t * sub_key, const pal::char_t ** value) { *key_hive = HKEY_LOCAL_MACHINE; // The registry search occurs in the 32-bit registry in all cases. @@ -322,7 +370,7 @@ namespace dotnet_key_path = environmentRegistryPathOverride; } - *sub_key = dotnet_key_path + pal::string_t(_X("\\Setup\\InstalledVersions\\")) + get_arch(); + *sub_key = dotnet_key_path + pal::string_t(_X("\\Setup\\InstalledVersions\\")) + get_arch_name(arch); *value = _X("InstallLocation"); } @@ -333,20 +381,18 @@ namespace } } -pal::string_t pal::get_dotnet_self_registered_config_location() +pal::string_t pal::get_dotnet_self_registered_config_location(pal::architecture arch) { HKEY key_hive; pal::string_t sub_key; const pal::char_t* value; - get_dotnet_install_location_registry_path(&key_hive, &sub_key, &value); + get_dotnet_install_location_registry_path(arch, &key_hive, &sub_key, &value); return registry_path_as_string(key_hive, sub_key, value); } bool pal::get_dotnet_self_registered_dir(pal::string_t* recv) { - recv->clear(); - // ***Used only for testing*** pal::string_t environmentOverride; if (test_only_getenv(_X("_DOTNET_TEST_GLOBALLY_REGISTERED_PATH"), &environmentOverride)) @@ -356,10 +402,17 @@ bool pal::get_dotnet_self_registered_dir(pal::string_t* recv) } // *************************** + return get_dotnet_self_registered_dir_for_arch(get_current_arch(), recv); +} + +bool pal::get_dotnet_self_registered_dir_for_arch(pal::architecture arch, pal::string_t* recv) +{ + recv->clear(); + HKEY hkeyHive; pal::string_t sub_key; const pal::char_t* value; - get_dotnet_install_location_registry_path(&hkeyHive, &sub_key, &value); + get_dotnet_install_location_registry_path(arch, &hkeyHive, &sub_key, &value); if (trace::is_enabled()) trace::verbose(_X("Looking for architecture-specific registry value in '%s'."), registry_path_as_string(hkeyHive, sub_key, value).c_str()); diff --git a/src/native/corehost/hostmisc/utils.cpp b/src/native/corehost/hostmisc/utils.cpp index 519b2ecc583114..8cd226d8e3e03c 100644 --- a/src/native/corehost/hostmisc/utils.cpp +++ b/src/native/corehost/hostmisc/utils.cpp @@ -194,29 +194,57 @@ pal::string_t get_replaced_char(const pal::string_t& path, pal::char_t match, pa return out; } -const pal::char_t* get_arch() +namespace +{ + const pal::char_t* s_all_architectures[] = + { + _X("arm"), + _X("arm64"), + _X("armv6"), + _X("loongarch64"), + _X("ppc64le"), + _X("s390x"), + _X("x64"), + _X("x86") + }; + static_assert((sizeof(s_all_architectures) / sizeof(*s_all_architectures)) == static_cast(pal::architecture::__last), "Invalid known architectures count"); +} + +pal::architecture get_current_arch() { #if defined(TARGET_AMD64) - return _X("x64"); + return pal::architecture::x64; #elif defined(TARGET_X86) - return _X("x86"); + return pal::architecture::x86; #elif defined(TARGET_ARMV6) - return _X("armv6"); + return pal::architecture::armv6; #elif defined(TARGET_ARM) - return _X("arm"); + return pal::architecture::arm; #elif defined(TARGET_ARM64) - return _X("arm64"); + return pal::architecture::arm64; #elif defined(TARGET_LOONGARCH64) - return _X("loongarch64"); + return pal::architecture::loongarch64; #elif defined(TARGET_S390X) - return _X("s390x"); + return pal::architecture::s390X; #elif defined(TARGET_POWERPC64) - return _X("ppc64le"); + return pal::architecture::ppc64le; #else #error "Unknown target" #endif } +const pal::char_t* get_arch_name(pal::architecture arch) +{ + int idx = static_cast(arch); + assert(0 <= idx && idx < static_cast(pal::architecture::__last)); + return s_all_architectures[idx]; +} + +const pal::char_t* get_current_arch_name() +{ + return get_arch_name(get_current_arch()); +} + pal::string_t get_current_runtime_id(bool use_fallback) { pal::string_t rid; @@ -230,7 +258,7 @@ pal::string_t get_current_runtime_id(bool use_fallback) if (!rid.empty()) { rid.append(_X("-")); - rid.append(get_arch()); + rid.append(get_current_arch_name()); } return rid; @@ -368,10 +396,14 @@ bool try_stou(const pal::string_t& str, unsigned* num) return true; } +pal::string_t get_dotnet_root_env_var_for_arch(pal::architecture arch) +{ + return DOTNET_ROOT_ENV_VAR _X("_") + to_upper(get_arch_name(arch)); +} + bool get_dotnet_root_from_env(pal::string_t* dotnet_root_env_var_name, pal::string_t* recv) { - *dotnet_root_env_var_name = _X("DOTNET_ROOT_"); - dotnet_root_env_var_name->append(to_upper(get_arch())); + *dotnet_root_env_var_name = get_dotnet_root_env_var_for_arch(get_current_arch()); if (get_file_path_from_env(dotnet_root_env_var_name->c_str(), recv)) return true; @@ -386,7 +418,7 @@ bool get_dotnet_root_from_env(pal::string_t* dotnet_root_env_var_name, pal::stri // If no architecture-specific environment variable was set // fallback to the default DOTNET_ROOT. - *dotnet_root_env_var_name = _X("DOTNET_ROOT"); + *dotnet_root_env_var_name = DOTNET_ROOT_ENV_VAR; return get_file_path_from_env(dotnet_root_env_var_name->c_str(), recv); } @@ -465,7 +497,7 @@ pal::string_t get_download_url(const pal::char_t* framework_name, const pal::cha } url.append(_X("&arch=")); - url.append(get_arch()); + url.append(get_current_arch_name()); pal::string_t rid = get_current_runtime_id(true /*use_fallback*/); url.append(_X("&rid=")); url.append(rid); diff --git a/src/native/corehost/hostmisc/utils.h b/src/native/corehost/hostmisc/utils.h index 0260f52f599b8c..97d49763eb3617 100644 --- a/src/native/corehost/hostmisc/utils.h +++ b/src/native/corehost/hostmisc/utils.h @@ -16,10 +16,10 @@ #else #define DOTNET_CORE_INSTALL_PREREQUISITES_URL _X("https://go.microsoft.com/fwlink/?linkid=2063370") #endif -#define DOTNET_CORE_DOWNLOAD_URL _X("https://aka.ms/dotnet-download") +#define DOTNET_CORE_DOWNLOAD_URL _X("https://aka.ms/dotnet/download") #define DOTNET_CORE_APPLAUNCH_URL _X("https://aka.ms/dotnet-core-applaunch") -#define DOTNET_INFO_URL _X("https://aka.ms/dotnet/runtimes-sdk-info") +#define DOTNET_INFO_URL _X("https://aka.ms/dotnet/info") #define DOTNET_APP_LAUNCH_FAILED_URL _X("https://aka.ms/dotnet/app-launch-failed") #define DOTNET_SDK_NOT_FOUND_URL _X("https://aka.ms/dotnet/sdk-not-found") @@ -31,6 +31,8 @@ #define RUNTIME_STORE_DIRECTORY_NAME _X("store") +#define DOTNET_ROOT_ENV_VAR _X("DOTNET_ROOT") + bool ends_with(const pal::string_t& value, const pal::string_t& suffix, bool match_case); bool starts_with(const pal::string_t& value, const pal::string_t& prefix, bool match_case); @@ -69,7 +71,11 @@ bool coreclr_exists_in_dir(const pal::string_t& candidate); void remove_trailing_dir_separator(pal::string_t* dir); void replace_char(pal::string_t* path, pal::char_t match, pal::char_t repl); pal::string_t get_replaced_char(const pal::string_t& path, pal::char_t match, pal::char_t repl); -const pal::char_t* get_arch(); + +pal::architecture get_current_arch(); +const pal::char_t* get_arch_name(pal::architecture arch); +const pal::char_t* get_current_arch_name(); + pal::string_t get_current_runtime_id(bool use_fallback); bool get_env_shared_store_dirs(std::vector* dirs, const pal::string_t& arch, const pal::string_t& tfm); bool get_global_shared_store_dirs(std::vector* dirs, const pal::string_t& arch, const pal::string_t& tfm); @@ -78,7 +84,10 @@ void get_framework_and_sdk_locations(const pal::string_t& dotnet_dir, const bool 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); + +pal::string_t get_dotnet_root_env_var_for_arch(pal::architecture arch); bool get_dotnet_root_from_env(pal::string_t* used_dotnet_root_env_var_name, pal::string_t* recv); + pal::string_t get_deps_from_app_binary(const pal::string_t& app_base, const pal::string_t& app); pal::string_t get_runtime_config_path(const pal::string_t& path, const pal::string_t& name); pal::string_t get_runtime_config_dev_path(const pal::string_t& path, const pal::string_t& name); diff --git a/src/native/corehost/hostpolicy/args.cpp b/src/native/corehost/hostpolicy/args.cpp index 01c5b48c40deed..1e80ddceba5e3a 100644 --- a/src/native/corehost/hostpolicy/args.cpp +++ b/src/native/corehost/hostpolicy/args.cpp @@ -36,14 +36,14 @@ void setup_shared_store_paths(const pal::string_t& tfm, host_mode_t host_mode,co } // Environment variable DOTNET_SHARED_STORE - (void) get_env_shared_store_dirs(&args->env_shared_store, get_arch(), tfm); + (void) get_env_shared_store_dirs(&args->env_shared_store, get_current_arch_name(), tfm); // "dotnet.exe" relative shared store folder if (host_mode == host_mode_t::muxer) { args->dotnet_shared_store = own_dir; append_path(&args->dotnet_shared_store, RUNTIME_STORE_DIRECTORY_NAME); - append_path(&args->dotnet_shared_store, get_arch()); + append_path(&args->dotnet_shared_store, get_current_arch_name()); append_path(&args->dotnet_shared_store, tfm.c_str()); } @@ -51,7 +51,7 @@ void setup_shared_store_paths(const pal::string_t& tfm, host_mode_t host_mode,co bool multilevel_lookup = multilevel_lookup_enabled(); if (multilevel_lookup) { - get_global_shared_store_dirs(&args->global_shared_stores, get_arch(), tfm); + get_global_shared_store_dirs(&args->global_shared_stores, get_current_arch_name(), tfm); } } @@ -114,7 +114,7 @@ bool set_root_from_app(const pal::string_t& managed_application_path, if (args.managed_application.empty()) { - // Managed app being empty by itself is not a failure. Host may be initialized from a config file. + // Managed app being empty by itself is not a failure. Host may be initialized from a config file. assert(args.host_mode != host_mode_t::apphost); return true; } diff --git a/src/native/corehost/hostpolicy/deps_resolver.cpp b/src/native/corehost/hostpolicy/deps_resolver.cpp index 8a0afa34c3d2a9..bc4fbbadc7a87e 100644 --- a/src/native/corehost/hostpolicy/deps_resolver.cpp +++ b/src/native/corehost/hostpolicy/deps_resolver.cpp @@ -229,7 +229,7 @@ void deps_resolver_t::setup_probe_config( if (pal::directory_exists(args.core_servicing)) { pal::string_t ext_ni = args.core_servicing; - append_path(&ext_ni, get_arch()); + append_path(&ext_ni, get_current_arch_name()); if (pal::directory_exists(ext_ni)) { // Servicing NI probe. diff --git a/src/native/corehost/hostpolicy/hostpolicy.cpp b/src/native/corehost/hostpolicy/hostpolicy.cpp index 9e74ab13f1eeb2..b3e608a1d09d8a 100644 --- a/src/native/corehost/hostpolicy/hostpolicy.cpp +++ b/src/native/corehost/hostpolicy/hostpolicy.cpp @@ -291,7 +291,7 @@ void trace_hostpolicy_entrypoint_invocation(const pal::string_t& entryPointName) _STRINGIFY(HOST_POLICY_PKG_NAME), _STRINGIFY(HOST_POLICY_PKG_VER), _STRINGIFY(HOST_POLICY_PKG_REL_DIR), - get_arch(), + get_current_arch_name(), entryPointName.c_str()); }