diff --git a/docs/design/features/host-runtime-information.md b/docs/design/features/host-runtime-information.md index b4210293081cb2..7140bb0f658e39 100644 --- a/docs/design/features/host-runtime-information.md +++ b/docs/design/features/host-runtime-information.md @@ -78,6 +78,10 @@ List of directory paths to search for managed assemblies. Paths are delimited by List of directory paths corresponding to shared store paths and additional probing paths used by the host for [probing paths](./host-probing.md#probing-paths). Paths are delimited by a [platform-specific path separator](#path-separator). +`SYSTEM_CORELIB_DIRECTORY` + +Absolute path to the directory containing `System.Private.CoreLib.dll`. When set, the runtime loads `System.Private.CoreLib` from this directory instead of looking for it beside `coreclr`. This is intended for hosts where `System.Private.CoreLib.dll` is not placed alongside `coreclr`. For hosts providing an assembly probe extension, a valid probe result takes precedence over this property. Otherwise, when this property is set, no fallback search is performed - if the file cannot be loaded from the specified directory, startup fails. Host is only allowed to specify directory since the runtime expects corelib to always be called `System.Private.CoreLib.dll`. + ### Single-file `BUNDLE_EXTRACTION_PATH` diff --git a/src/coreclr/binder/assemblybindercommon.cpp b/src/coreclr/binder/assemblybindercommon.cpp index a77c53658a6a4e..7316f981d67d6a 100644 --- a/src/coreclr/binder/assemblybindercommon.cpp +++ b/src/coreclr/binder/assemblybindercommon.cpp @@ -22,6 +22,7 @@ #include "failurecache.hpp" #include "utils.hpp" #include "stringarraylist.h" +#include "hostinformation.h" #if !defined(DACCESS_COMPILE) #include "defaultassemblybinder.h" @@ -271,6 +272,9 @@ namespace BINDER_SPACE // * Non-single-file app: In systemDirectory, beside coreclr.dll // * Framework-dependent single-file app: In systemDirectory, beside coreclr.dll // * Self-contained single-file app: Within the single-file bundle. + // * Host explicitly provided directory: In the directory set via the + // SYSTEM_CORELIB_DIRECTORY runtime property. Used by hosts where SPCL is not located + // in the same directory as coreclr. // // CoreLib path (sCoreLib): // * Absolute path when looking for a file on disk @@ -284,7 +288,16 @@ namespace BINDER_SPACE { pathSource = BinderTracing::PathSource::ApplicationAssemblies; } - sCoreLib.Set(systemDirectory); + + // Check for a host-provided explicit directory for CoreLib. When set, this replaces + // the default lookup beside coreclr and the bundle extraction path fallback. + bool hasHostProvidedDirectory = HostInformation::GetProperty(HOST_PROPERTY_SYSTEM_CORELIB_DIRECTORY, sCoreLib) + && !sCoreLib.IsEmpty(); + if (!hasHostProvidedDirectory) + { + sCoreLib.Set(systemDirectory); + } + CombinePath(sCoreLib, sCoreLibName, sCoreLib); hr = AssemblyBinderCommon::GetAssembly(sCoreLib, @@ -295,6 +308,7 @@ namespace BINDER_SPACE BinderTracing::PathProbed(sCoreLib, pathSource, hr); if (hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) + && !hasHostProvidedDirectory && Bundle::AppIsBundle() && Bundle::AppBundle->HasExtractedFiles()) { diff --git a/src/coreclr/hosts/corerun/corerun.cpp b/src/coreclr/hosts/corerun/corerun.cpp index e5b7d2224cfa49..a47c1508f2a010 100644 --- a/src/coreclr/hosts/corerun/corerun.cpp +++ b/src/coreclr/hosts/corerun/corerun.cpp @@ -279,6 +279,24 @@ size_t HOST_CONTRACT_CALLTYPE get_runtime_property( return len; } + // Look up user-defined properties passed to corerun via -p/--property. + assert(config->user_defined_keys.size() == config->user_defined_values.size()); + pal::string_t key_native = pal::convert_from_utf8(key); + for (size_t i = 0; i < config->user_defined_keys.size(); ++i) + { + if (config->user_defined_keys[i] == key_native) + { + pal::string_utf8_t value_utf8 = pal::convert_to_utf8(config->user_defined_values[i].c_str()); + size_t len = value_utf8.size() + 1; + if (value_buffer_size < len) + return len; + + ::strncpy(value_buffer, value_utf8.c_str(), len - 1); + value_buffer[len - 1] = '\0'; + return len; + } + } + return -1; } diff --git a/src/native/corehost/host_runtime_contract.h b/src/native/corehost/host_runtime_contract.h index 558a7093379efa..9bfea43f168c44 100644 --- a/src/native/corehost/host_runtime_contract.h +++ b/src/native/corehost/host_runtime_contract.h @@ -22,6 +22,7 @@ #define HOST_PROPERTY_NATIVE_DLL_SEARCH_DIRECTORIES "NATIVE_DLL_SEARCH_DIRECTORIES" #define HOST_PROPERTY_PINVOKE_OVERRIDE "PINVOKE_OVERRIDE" #define HOST_PROPERTY_PLATFORM_RESOURCE_ROOTS "PLATFORM_RESOURCE_ROOTS" +#define HOST_PROPERTY_SYSTEM_CORELIB_DIRECTORY "SYSTEM_CORELIB_DIRECTORY" #define HOST_PROPERTY_TRUSTED_PLATFORM_ASSEMBLIES "TRUSTED_PLATFORM_ASSEMBLIES" // Context passed to get_native_code_data callback diff --git a/src/tests/Loader/SystemCoreLibDirectory/SystemCoreLibDirectory.cs b/src/tests/Loader/SystemCoreLibDirectory/SystemCoreLibDirectory.cs new file mode 100644 index 00000000000000..3d1e895b174c63 --- /dev/null +++ b/src/tests/Loader/SystemCoreLibDirectory/SystemCoreLibDirectory.cs @@ -0,0 +1,37 @@ +// 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.IO; +using Xunit; + +public class SystemCoreLibDirectory +{ + [ConditionalFact(typeof(TestLibrary.Utilities), nameof(TestLibrary.Utilities.HasAssemblyFiles))] + public static void HostProvidedPath() + { + string configuredDirectory = AppContext.GetData("SYSTEM_CORELIB_DIRECTORY") as string; + Assert.False(string.IsNullOrEmpty(configuredDirectory), "SYSTEM_CORELIB_DIRECTORY should be set by the test harness"); + Assert.True(Directory.Exists(configuredDirectory), $"SYSTEM_CORELIB_DIRECTORY should reference an existing directory: '{configuredDirectory}'"); + + string spclLocation = Path.GetFullPath(typeof(object).Assembly.Location); + string expectedLocation = Path.GetFullPath(Path.Combine(configuredDirectory, "System.Private.CoreLib.dll")); + + StringComparison comparison = OperatingSystem.IsWindows() + ? StringComparison.OrdinalIgnoreCase + : StringComparison.Ordinal; + + Assert.True( + string.Equals(spclLocation, expectedLocation, comparison), + $"SPCL should be loaded from '{expectedLocation}', but was loaded from '{spclLocation}'"); + + string coreRoot = Environment.GetEnvironmentVariable("CORE_ROOT"); + if (!string.IsNullOrEmpty(coreRoot)) + { + string defaultPath = Path.Combine(coreRoot, "System.Private.CoreLib.dll"); + Assert.False( + string.Equals(spclLocation, Path.GetFullPath(defaultPath), comparison), + $"SPCL should not be loaded from the default location beside coreclr: '{defaultPath}'"); + } + } +} diff --git a/src/tests/Loader/SystemCoreLibDirectory/SystemCoreLibDirectory.csproj b/src/tests/Loader/SystemCoreLibDirectory/SystemCoreLibDirectory.csproj new file mode 100644 index 00000000000000..e97d42bfb0fff5 --- /dev/null +++ b/src/tests/Loader/SystemCoreLibDirectory/SystemCoreLibDirectory.csproj @@ -0,0 +1,35 @@ + + + true + true + + true + + + + + + + + + + + + + + $(CLRTestBatchPreCommands);$(CoreLibSetupBatch) + $(CLRTestBashPreCommands);$(CoreLibSetupBash) + +