Skip to content

Commit e768358

Browse files
authored
Add searched .NET install locations to error message (#117796)
Include searched locations in error message when host cannot find a .NET install (`hostfxr`) Example - the 'following locations were searched' section is new: ``` You must install .NET to run this application. App: /home/repos/helloworld/bin/Debug/net10.0/helloworld Architecture: x64 App host version: 10.0.0-dev .NET location: Not found The following locations were searched: Application directory: /home/repos/helloworld/bin/Debug/net10.0/ Environment variable: DOTNET_ROOT_X64 = <not set> DOTNET_ROOT = <not set> Registered location: /etc/dotnet/install_location_x64 = <not set> Default location: /usr/share/dotnet Learn more: https://aka.ms/dotnet/app-launch-failed Download the .NET runtime: https://aka.ms/dotnet-core-applaunch?missing_runtime=true&arch=x64&rid=linux-x64&os=ubuntu.22.04&apphost_version=10.0.0-dev ```
1 parent 68cb956 commit e768358

File tree

5 files changed

+127
-42
lines changed

5 files changed

+127
-42
lines changed

src/installer/tests/HostActivation.Tests/InstallLocation.cs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,51 @@ public void RegisteredInstallLocation_DotNetInfo_ListOtherArchitectures()
322322
}
323323
}
324324

325+
[Fact]
326+
public void NotFound()
327+
{
328+
TestApp app = sharedTestState.TestBehaviourEnabledApp;
329+
330+
// Ensure no install locations are registered
331+
using (var registeredInstallLocationOverride = new RegisteredInstallLocationOverride(app.AppExe))
332+
{
333+
string defaultLocation = Path.GetTempPath();
334+
string registeredLocationOverride = OperatingSystem.IsWindows() // Host uses short form of base key for Windows
335+
? registeredInstallLocationOverride.PathValueOverride.Replace(Microsoft.Win32.Registry.CurrentUser.Name, "HKCU")
336+
: registeredInstallLocationOverride.PathValueOverride;
337+
Command.Create(app.AppExe)
338+
.CaptureStdOut()
339+
.CaptureStdErr()
340+
.ApplyRegisteredInstallLocationOverride(registeredInstallLocationOverride)
341+
.EnvironmentVariable(Constants.TestOnlyEnvironmentVariables.DefaultInstallPath, defaultLocation)
342+
.DotNetRoot(null)
343+
.Execute()
344+
.Should().Fail()
345+
.And.HaveStdErrContaining("The following locations were searched:")
346+
.And.HaveStdErrContaining(
347+
$"""
348+
Application directory:
349+
{app.Location}
350+
""")
351+
.And.HaveStdErrContaining(
352+
$"""
353+
Environment variable:
354+
DOTNET_ROOT_{TestContext.BuildArchitecture.ToUpper()} = <not set>
355+
DOTNET_ROOT = <not set>
356+
""")
357+
.And.HaveStdErrMatching(
358+
$"""
359+
Registered location:
360+
{System.Text.RegularExpressions.Regex.Escape(registeredLocationOverride)}.*{TestContext.BuildArchitecture}.* = <not set>
361+
""")
362+
.And.HaveStdErrContaining(
363+
$"""
364+
Default location:
365+
{defaultLocation}
366+
""");
367+
}
368+
}
369+
325370
[Theory]
326371
[InlineData(SearchLocation.AppLocal)]
327372
[InlineData(SearchLocation.AppRelative)]

src/native/corehost/apphost/standalone/hostfxr_resolver.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ hostfxr_resolver_t::hostfxr_resolver_t(const pal::string_t& app_root)
121121
else if (!pal::is_path_fully_qualified(m_fxr_path))
122122
{
123123
// We should always be loading hostfxr from an absolute path
124+
trace::error(_X("Path to %s must be fully qualified: [%s]"), LIBFXR_NAME, m_fxr_path.c_str());
124125
m_status_code = StatusCode::CoreHostLibMissingFailure;
125126
}
126127
else if (pal::load_library(&m_fxr_path, &m_hostfxr_dll))

src/native/corehost/corehost.cpp

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -212,10 +212,7 @@ int exe_start(const int argc, const pal::char_t* argv[])
212212
// Obtain the entrypoints.
213213
int rc = fxr.status_code();
214214
if (rc != StatusCode::Success)
215-
{
216-
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);
217215
return rc;
218-
}
219216

220217
#if defined(FEATURE_APPHOST)
221218
if (bundle_marker_t::is_bundle())

src/native/corehost/fxr_resolver.cpp

Lines changed: 65 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,6 @@ bool fxr_resolver::try_get_path(
9393
bool search_app_relative = (search & search_location_app_relative) != 0 && app_relative_dotnet_root != nullptr && !app_relative_dotnet_root->empty();
9494
bool search_env = (search & search_location_environment_variable) != 0;
9595
bool search_global = (search & search_location_global) != 0;
96-
pal::string_t default_install_location;
9796
pal::string_t dotnet_root_env_var_name;
9897
if (search_app_relative && pal::fullpath(app_relative_dotnet_root))
9998
{
@@ -111,10 +110,11 @@ bool fxr_resolver::try_get_path(
111110
}
112111
else if (search_global)
113112
{
114-
if (pal::get_dotnet_self_registered_dir(&default_install_location) || pal::get_default_installation_dir(&default_install_location))
113+
pal::string_t global_install_location;
114+
if (pal::get_dotnet_self_registered_dir(&global_install_location) || pal::get_default_installation_dir(&global_install_location))
115115
{
116-
trace::info(_X("Using global install location [%s] as runtime location."), default_install_location.c_str());
117-
out_dotnet_root->assign(default_install_location);
116+
trace::info(_X("Using global install location [%s] as runtime location."), global_install_location.c_str());
117+
out_dotnet_root->assign(global_install_location);
118118
}
119119
else
120120
{
@@ -130,35 +130,7 @@ bool fxr_resolver::try_get_path(
130130
return get_latest_fxr(std::move(fxr_dir), out_fxr_path);
131131

132132
// Failed to find hostfxr
133-
if (trace::is_enabled())
134-
{
135-
trace::verbose(_X("The required library %s could not be found. Search location options [0x%x]"), LIBFXR_NAME, search);
136-
if (search_app_local)
137-
trace::verbose(_X(" app-local: [%s]"), root_path.c_str());
138-
139-
if (search_app_relative)
140-
trace::verbose(_X(" app-relative: [%s]"), app_relative_dotnet_root->c_str());
141-
142-
if (search_env)
143-
trace::verbose(_X(" environment variable: [%s]"), dotnet_root_env_var_name.c_str());
144-
145-
if (search_global)
146-
{
147-
if (default_install_location.empty())
148-
{
149-
pal::get_dotnet_self_registered_dir(&default_install_location);
150-
}
151-
if (default_install_location.empty())
152-
{
153-
pal::get_default_installation_dir(&default_install_location);
154-
}
155-
156-
pal::string_t self_registered_config_location = pal::get_dotnet_self_registered_config_location(get_current_arch());
157-
trace::verbose(_X(" global install location [%s]\n self-registered config location [%s]"),
158-
default_install_location.c_str(),
159-
self_registered_config_location.c_str());
160-
}
161-
}
133+
trace::verbose(_X("The required library %s could not be found. Search location options [0x%x]"), LIBFXR_NAME, search);
162134

163135
pal::string_t host_path;
164136
pal::get_own_executable_path(&host_path);
@@ -187,6 +159,66 @@ bool fxr_resolver::try_get_path(
187159
}
188160
}
189161

162+
pal::string_t searched_locations = _X("The following locations were searched:");
163+
if (search_app_local && !root_path.empty())
164+
{
165+
searched_locations.append(_X("\n Application directory:\n "));
166+
searched_locations.append(root_path);
167+
}
168+
169+
if (search_app_relative)
170+
{
171+
searched_locations.append(_X("\n App-relative location:\n "));
172+
searched_locations.append(*app_relative_dotnet_root);
173+
}
174+
175+
if (search_env)
176+
{
177+
searched_locations.append(_X("\n Environment variable:\n "));
178+
if (dotnet_root_env_var_name.empty())
179+
{
180+
searched_locations.append(get_dotnet_root_env_var_for_arch(get_current_arch()));
181+
searched_locations.append(_X(" = <not set>\n "));
182+
searched_locations.append(DOTNET_ROOT_ENV_VAR _X(" = <not set>"));
183+
}
184+
else
185+
{
186+
searched_locations.append(dotnet_root_env_var_name);
187+
searched_locations.append(_X(" = "));
188+
searched_locations.append(*out_dotnet_root);
189+
}
190+
}
191+
192+
// Global locations are only searched if environment variables are not set
193+
if (search_global && dotnet_root_env_var_name.empty())
194+
{
195+
searched_locations.append(_X("\n Registered location:\n "));
196+
searched_locations.append(pal::get_dotnet_self_registered_config_location(get_current_arch()));
197+
198+
pal::string_t self_registered_dir;
199+
if (pal::get_dotnet_self_registered_dir(&self_registered_dir) && !self_registered_dir.empty())
200+
{
201+
searched_locations.append(_X(" = "));
202+
searched_locations.append(self_registered_dir);
203+
}
204+
else
205+
{
206+
searched_locations.append(_X(" = <not set>"));
207+
}
208+
209+
// Default install location is only searched if self-registered location is not set
210+
if (self_registered_dir.empty())
211+
{
212+
pal::string_t default_install_location;
213+
pal::get_default_installation_dir(&default_install_location);
214+
searched_locations.append(_X("\n Default location:\n "));
215+
searched_locations.append(default_install_location);
216+
}
217+
}
218+
219+
location.append(_X("\n\n"));
220+
location.append(searched_locations);
221+
190222
trace::error(
191223
MISSING_RUNTIME_ERROR_FORMAT,
192224
INSTALL_NET_ERROR_MESSAGE,

src/native/corehost/hostmisc/utils.cpp

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -360,23 +360,33 @@ pal::string_t get_dotnet_root_env_var_for_arch(pal::architecture arch)
360360

361361
bool get_dotnet_root_from_env(pal::string_t* dotnet_root_env_var_name, pal::string_t* recv)
362362
{
363-
*dotnet_root_env_var_name = get_dotnet_root_env_var_for_arch(get_current_arch());
364-
if (get_file_path_from_env(dotnet_root_env_var_name->c_str(), recv))
363+
pal::string_t env_var_name = get_dotnet_root_env_var_for_arch(get_current_arch());
364+
if (get_file_path_from_env(env_var_name.c_str(), recv))
365+
{
366+
*dotnet_root_env_var_name = env_var_name;
365367
return true;
368+
}
366369

367370
#if defined(WIN32)
368371
if (pal::is_running_in_wow64())
369372
{
370-
*dotnet_root_env_var_name = _X("DOTNET_ROOT(x86)");
371-
if (get_file_path_from_env(dotnet_root_env_var_name->c_str(), recv))
373+
if (get_file_path_from_env(_X("DOTNET_ROOT(x86)"), recv))
374+
{
375+
*dotnet_root_env_var_name = _X("DOTNET_ROOT(x86)");
372376
return true;
377+
}
373378
}
374379
#endif
375380

376381
// If no architecture-specific environment variable was set
377382
// fallback to the default DOTNET_ROOT.
378-
*dotnet_root_env_var_name = DOTNET_ROOT_ENV_VAR;
379-
return get_file_path_from_env(dotnet_root_env_var_name->c_str(), recv);
383+
if (get_file_path_from_env(DOTNET_ROOT_ENV_VAR, recv))
384+
{
385+
*dotnet_root_env_var_name = DOTNET_ROOT_ENV_VAR;
386+
return true;
387+
}
388+
389+
return false;
380390
}
381391

382392
/**

0 commit comments

Comments
 (0)