Skip to content

Add searched .NET install locations to error message #117796

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jul 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions src/installer/tests/HostActivation.Tests/InstallLocation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,51 @@ public void RegisteredInstallLocation_DotNetInfo_ListOtherArchitectures()
}
}

[Fact]
public void NotFound()
{
TestApp app = sharedTestState.TestBehaviourEnabledApp;

// Ensure no install locations are registered
using (var registeredInstallLocationOverride = new RegisteredInstallLocationOverride(app.AppExe))
{
string defaultLocation = Path.GetTempPath();
string registeredLocationOverride = OperatingSystem.IsWindows() // Host uses short form of base key for Windows
? registeredInstallLocationOverride.PathValueOverride.Replace(Microsoft.Win32.Registry.CurrentUser.Name, "HKCU")
: registeredInstallLocationOverride.PathValueOverride;
Command.Create(app.AppExe)
.CaptureStdOut()
.CaptureStdErr()
.ApplyRegisteredInstallLocationOverride(registeredInstallLocationOverride)
.EnvironmentVariable(Constants.TestOnlyEnvironmentVariables.DefaultInstallPath, defaultLocation)
.DotNetRoot(null)
.Execute()
.Should().Fail()
.And.HaveStdErrContaining("The following locations were searched:")
.And.HaveStdErrContaining(
$"""
Application directory:
{app.Location}
""")
.And.HaveStdErrContaining(
$"""
Environment variable:
DOTNET_ROOT_{TestContext.BuildArchitecture.ToUpper()} = <not set>
DOTNET_ROOT = <not set>
""")
.And.HaveStdErrMatching(
$"""
Registered location:
{System.Text.RegularExpressions.Regex.Escape(registeredLocationOverride)}.*{TestContext.BuildArchitecture}.* = <not set>
""")
.And.HaveStdErrContaining(
$"""
Default location:
{defaultLocation}
""");
}
}

[Theory]
[InlineData(SearchLocation.AppLocal)]
[InlineData(SearchLocation.AppRelative)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ hostfxr_resolver_t::hostfxr_resolver_t(const pal::string_t& app_root)
else if (!pal::is_path_fully_qualified(m_fxr_path))
{
// We should always be loading hostfxr from an absolute path
trace::error(_X("Path to %s must be fully qualified: [%s]"), LIBFXR_NAME, m_fxr_path.c_str());
m_status_code = StatusCode::CoreHostLibMissingFailure;
}
else if (pal::load_library(&m_fxr_path, &m_hostfxr_dll))
Expand Down
3 changes: 0 additions & 3 deletions src/native/corehost/corehost.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -212,10 +212,7 @@ int exe_start(const int argc, const pal::char_t* argv[])
// Obtain the entrypoints.
int rc = fxr.status_code();
if (rc != StatusCode::Success)
{
trace::error(_X("Failed to resolve %s [%s]. Error code: 0x%x"), LIBFXR_NAME, fxr.fxr_path().empty() ? _X("not found") : fxr.fxr_path().c_str(), rc);
return rc;
}

#if defined(FEATURE_APPHOST)
if (bundle_marker_t::is_bundle())
Expand Down
98 changes: 65 additions & 33 deletions src/native/corehost/fxr_resolver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,6 @@ bool fxr_resolver::try_get_path(
bool search_app_relative = (search & search_location_app_relative) != 0 && app_relative_dotnet_root != nullptr && !app_relative_dotnet_root->empty();
bool search_env = (search & search_location_environment_variable) != 0;
bool search_global = (search & search_location_global) != 0;
pal::string_t default_install_location;
pal::string_t dotnet_root_env_var_name;
if (search_app_relative && pal::fullpath(app_relative_dotnet_root))
{
Expand All @@ -111,10 +110,11 @@ bool fxr_resolver::try_get_path(
}
else if (search_global)
{
if (pal::get_dotnet_self_registered_dir(&default_install_location) || pal::get_default_installation_dir(&default_install_location))
pal::string_t global_install_location;
if (pal::get_dotnet_self_registered_dir(&global_install_location) || pal::get_default_installation_dir(&global_install_location))
{
trace::info(_X("Using global install location [%s] as runtime location."), default_install_location.c_str());
out_dotnet_root->assign(default_install_location);
trace::info(_X("Using global install location [%s] as runtime location."), global_install_location.c_str());
out_dotnet_root->assign(global_install_location);
}
else
{
Expand All @@ -130,35 +130,7 @@ bool fxr_resolver::try_get_path(
return get_latest_fxr(std::move(fxr_dir), out_fxr_path);

// Failed to find hostfxr
if (trace::is_enabled())
{
trace::verbose(_X("The required library %s could not be found. Search location options [0x%x]"), LIBFXR_NAME, search);
if (search_app_local)
trace::verbose(_X(" app-local: [%s]"), root_path.c_str());

if (search_app_relative)
trace::verbose(_X(" app-relative: [%s]"), app_relative_dotnet_root->c_str());

if (search_env)
trace::verbose(_X(" environment variable: [%s]"), dotnet_root_env_var_name.c_str());

if (search_global)
{
if (default_install_location.empty())
{
pal::get_dotnet_self_registered_dir(&default_install_location);
}
if (default_install_location.empty())
{
pal::get_default_installation_dir(&default_install_location);
}

pal::string_t self_registered_config_location = pal::get_dotnet_self_registered_config_location(get_current_arch());
trace::verbose(_X(" global install location [%s]\n self-registered config location [%s]"),
default_install_location.c_str(),
self_registered_config_location.c_str());
}
}
trace::verbose(_X("The required library %s could not be found. Search location options [0x%x]"), LIBFXR_NAME, search);

pal::string_t host_path;
pal::get_own_executable_path(&host_path);
Expand Down Expand Up @@ -187,6 +159,66 @@ bool fxr_resolver::try_get_path(
}
}

pal::string_t searched_locations = _X("The following locations were searched:");
if (search_app_local && !root_path.empty())
{
searched_locations.append(_X("\n Application directory:\n "));
searched_locations.append(root_path);
}

if (search_app_relative)
{
searched_locations.append(_X("\n App-relative location:\n "));
searched_locations.append(*app_relative_dotnet_root);
}

if (search_env)
{
searched_locations.append(_X("\n Environment variable:\n "));
if (dotnet_root_env_var_name.empty())
{
searched_locations.append(get_dotnet_root_env_var_for_arch(get_current_arch()));
searched_locations.append(_X(" = <not set>\n "));
searched_locations.append(DOTNET_ROOT_ENV_VAR _X(" = <not set>"));
}
else
{
searched_locations.append(dotnet_root_env_var_name);
searched_locations.append(_X(" = "));
searched_locations.append(*out_dotnet_root);
}
}

// Global locations are only searched if environment variables are not set
if (search_global && dotnet_root_env_var_name.empty())
{
searched_locations.append(_X("\n Registered location:\n "));
searched_locations.append(pal::get_dotnet_self_registered_config_location(get_current_arch()));

pal::string_t self_registered_dir;
if (pal::get_dotnet_self_registered_dir(&self_registered_dir) && !self_registered_dir.empty())
{
searched_locations.append(_X(" = "));
searched_locations.append(self_registered_dir);
}
else
{
searched_locations.append(_X(" = <not set>"));
}

// Default install location is only searched if self-registered location is not set
if (self_registered_dir.empty())
{
pal::string_t default_install_location;
pal::get_default_installation_dir(&default_install_location);
searched_locations.append(_X("\n Default location:\n "));
searched_locations.append(default_install_location);
}
}

location.append(_X("\n\n"));
location.append(searched_locations);

trace::error(
MISSING_RUNTIME_ERROR_FORMAT,
INSTALL_NET_ERROR_MESSAGE,
Expand Down
22 changes: 16 additions & 6 deletions src/native/corehost/hostmisc/utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -360,23 +360,33 @@ pal::string_t get_dotnet_root_env_var_for_arch(pal::architecture arch)

bool get_dotnet_root_from_env(pal::string_t* dotnet_root_env_var_name, pal::string_t* recv)
{
*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))
pal::string_t env_var_name = get_dotnet_root_env_var_for_arch(get_current_arch());
if (get_file_path_from_env(env_var_name.c_str(), recv))
{
*dotnet_root_env_var_name = env_var_name;
return true;
}

#if defined(WIN32)
if (pal::is_running_in_wow64())
{
*dotnet_root_env_var_name = _X("DOTNET_ROOT(x86)");
if (get_file_path_from_env(dotnet_root_env_var_name->c_str(), recv))
if (get_file_path_from_env(_X("DOTNET_ROOT(x86)"), recv))
{
*dotnet_root_env_var_name = _X("DOTNET_ROOT(x86)");
return true;
}
}
#endif

// If no architecture-specific environment variable was set
// fallback to the default 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);
if (get_file_path_from_env(DOTNET_ROOT_ENV_VAR, recv))
{
*dotnet_root_env_var_name = DOTNET_ROOT_ENV_VAR;
return true;
}

return false;
}

/**
Expand Down
Loading