Skip to content

Commit b0f76ec

Browse files
elinor-fungCopilotvitek-karas
authored
Add SYSTEM_CORELIB_DIRECTORY host property for explicit CoreLib location (#128734)
Add a new well-known host runtime property, `SYSTEM_CORELIB_DIRECTORY`, that lets a host explicitly specify the directory containing `System.Private.CoreLib.dll`. When set, the runtime loads CoreLib from that directory instead of the default lookup beside `coreclr`, and the single-file bundle-extraction fallback is also skipped. #128278 removed the TPA-scanning fallback in `BindToSystem`, which broke scenarios (notably iOS) where `coreclr` is not next to `System.Private.CoreLib.dll` is not located beside it. Rather than restoring implicit scanning, this PR provides an explicit, opt-in contract for hosts to point the runtime at CoreLib. - Property is treated as an authoritative override. When set, no fallback search is performed - if the file cannot be loaded from the specified directory, startup fails. An empty value is treated as not set. - Host-provided assembly probe extensions (bundle probe, external assembly probe) still run first. It is the on-disk lookup and the bundle-extraction fallback that are replaced by the property. --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Vitek Karas <10670590+vitek-karas@users.noreply.github.com>
1 parent f5f1079 commit b0f76ec

6 files changed

Lines changed: 110 additions & 1 deletion

File tree

docs/design/features/host-runtime-information.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,10 @@ List of directory paths to search for managed assemblies. Paths are delimited by
7878

7979
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).
8080

81+
`SYSTEM_CORELIB_DIRECTORY`
82+
83+
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`.
84+
8185
### Single-file
8286

8387
`BUNDLE_EXTRACTION_PATH`

src/coreclr/binder/assemblybindercommon.cpp

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include "failurecache.hpp"
2323
#include "utils.hpp"
2424
#include "stringarraylist.h"
25+
#include "hostinformation.h"
2526

2627
#if !defined(DACCESS_COMPILE)
2728
#include "defaultassemblybinder.h"
@@ -271,6 +272,9 @@ namespace BINDER_SPACE
271272
// * Non-single-file app: In systemDirectory, beside coreclr.dll
272273
// * Framework-dependent single-file app: In systemDirectory, beside coreclr.dll
273274
// * Self-contained single-file app: Within the single-file bundle.
275+
// * Host explicitly provided directory: In the directory set via the
276+
// SYSTEM_CORELIB_DIRECTORY runtime property. Used by hosts where SPCL is not located
277+
// in the same directory as coreclr.
274278
//
275279
// CoreLib path (sCoreLib):
276280
// * Absolute path when looking for a file on disk
@@ -284,7 +288,16 @@ namespace BINDER_SPACE
284288
{
285289
pathSource = BinderTracing::PathSource::ApplicationAssemblies;
286290
}
287-
sCoreLib.Set(systemDirectory);
291+
292+
// Check for a host-provided explicit directory for CoreLib. When set, this replaces
293+
// the default lookup beside coreclr and the bundle extraction path fallback.
294+
bool hasHostProvidedDirectory = HostInformation::GetProperty(HOST_PROPERTY_SYSTEM_CORELIB_DIRECTORY, sCoreLib)
295+
&& !sCoreLib.IsEmpty();
296+
if (!hasHostProvidedDirectory)
297+
{
298+
sCoreLib.Set(systemDirectory);
299+
}
300+
288301
CombinePath(sCoreLib, sCoreLibName, sCoreLib);
289302

290303
hr = AssemblyBinderCommon::GetAssembly(sCoreLib,
@@ -295,6 +308,7 @@ namespace BINDER_SPACE
295308
BinderTracing::PathProbed(sCoreLib, pathSource, hr);
296309

297310
if (hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)
311+
&& !hasHostProvidedDirectory
298312
&& Bundle::AppIsBundle()
299313
&& Bundle::AppBundle->HasExtractedFiles())
300314
{

src/coreclr/hosts/corerun/corerun.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,24 @@ size_t HOST_CONTRACT_CALLTYPE get_runtime_property(
279279
return len;
280280
}
281281

282+
// Look up user-defined properties passed to corerun via -p/--property.
283+
assert(config->user_defined_keys.size() == config->user_defined_values.size());
284+
pal::string_t key_native = pal::convert_from_utf8(key);
285+
for (size_t i = 0; i < config->user_defined_keys.size(); ++i)
286+
{
287+
if (config->user_defined_keys[i] == key_native)
288+
{
289+
pal::string_utf8_t value_utf8 = pal::convert_to_utf8(config->user_defined_values[i].c_str());
290+
size_t len = value_utf8.size() + 1;
291+
if (value_buffer_size < len)
292+
return len;
293+
294+
::strncpy(value_buffer, value_utf8.c_str(), len - 1);
295+
value_buffer[len - 1] = '\0';
296+
return len;
297+
}
298+
}
299+
282300
return -1;
283301
}
284302

src/native/corehost/host_runtime_contract.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#define HOST_PROPERTY_NATIVE_DLL_SEARCH_DIRECTORIES "NATIVE_DLL_SEARCH_DIRECTORIES"
2323
#define HOST_PROPERTY_PINVOKE_OVERRIDE "PINVOKE_OVERRIDE"
2424
#define HOST_PROPERTY_PLATFORM_RESOURCE_ROOTS "PLATFORM_RESOURCE_ROOTS"
25+
#define HOST_PROPERTY_SYSTEM_CORELIB_DIRECTORY "SYSTEM_CORELIB_DIRECTORY"
2526
#define HOST_PROPERTY_TRUSTED_PLATFORM_ASSEMBLIES "TRUSTED_PLATFORM_ASSEMBLIES"
2627

2728
// Context passed to get_native_code_data callback
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.IO;
6+
using Xunit;
7+
8+
public class SystemCoreLibDirectory
9+
{
10+
[ConditionalFact(typeof(TestLibrary.Utilities), nameof(TestLibrary.Utilities.HasAssemblyFiles))]
11+
public static void HostProvidedPath()
12+
{
13+
string configuredDirectory = AppContext.GetData("SYSTEM_CORELIB_DIRECTORY") as string;
14+
Assert.False(string.IsNullOrEmpty(configuredDirectory), "SYSTEM_CORELIB_DIRECTORY should be set by the test harness");
15+
Assert.True(Directory.Exists(configuredDirectory), $"SYSTEM_CORELIB_DIRECTORY should reference an existing directory: '{configuredDirectory}'");
16+
17+
string spclLocation = Path.GetFullPath(typeof(object).Assembly.Location);
18+
string expectedLocation = Path.GetFullPath(Path.Combine(configuredDirectory, "System.Private.CoreLib.dll"));
19+
20+
StringComparison comparison = OperatingSystem.IsWindows()
21+
? StringComparison.OrdinalIgnoreCase
22+
: StringComparison.Ordinal;
23+
24+
Assert.True(
25+
string.Equals(spclLocation, expectedLocation, comparison),
26+
$"SPCL should be loaded from '{expectedLocation}', but was loaded from '{spclLocation}'");
27+
28+
string coreRoot = Environment.GetEnvironmentVariable("CORE_ROOT");
29+
if (!string.IsNullOrEmpty(coreRoot))
30+
{
31+
string defaultPath = Path.Combine(coreRoot, "System.Private.CoreLib.dll");
32+
Assert.False(
33+
string.Equals(spclLocation, Path.GetFullPath(defaultPath), comparison),
34+
$"SPCL should not be loaded from the default location beside coreclr: '{defaultPath}'");
35+
}
36+
}
37+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<PropertyGroup>
3+
<RequiresProcessIsolation>true</RequiresProcessIsolation>
4+
<NativeAotIncompatible>true</NativeAotIncompatible>
5+
<!-- This test exercises the CoreCLR system assembly binder -->
6+
<CLRTestTargetUnsupported Condition="'$(RuntimeFlavor)' != 'coreclr'">true</CLRTestTargetUnsupported>
7+
</PropertyGroup>
8+
<ItemGroup>
9+
<Compile Include="SystemCoreLibDirectory.cs" />
10+
11+
<ProjectReference Include="$(TestLibraryProjectPath)" />
12+
13+
<!-- Point the runtime at the Subdirectory created by the pre-commands below to look for
14+
System.Private.CoreLib.dll. The %25cd%25 / $%28pwd) tokens expand at script execution
15+
time to the test working directory. -->
16+
<RuntimeHostConfigurationOption Include="SYSTEM_CORELIB_DIRECTORY"
17+
Value="%25cd%25\Subdirectory"
18+
Condition="'$(TestWrapperTargetsWindows)' == 'true'" />
19+
<RuntimeHostConfigurationOption Include="SYSTEM_CORELIB_DIRECTORY"
20+
Value="$%28pwd)/Subdirectory"
21+
Condition="'$(TestWrapperTargetsWindows)' != 'true'" />
22+
</ItemGroup>
23+
<PropertyGroup>
24+
<CoreLibSetupBatch><![CDATA[
25+
mkdir Subdirectory
26+
copy /Y "%CORE_ROOT%\System.Private.CoreLib.dll" "Subdirectory\"
27+
]]></CoreLibSetupBatch>
28+
<CoreLibSetupBash><![CDATA[
29+
mkdir -p Subdirectory
30+
cp "$CORE_ROOT/System.Private.CoreLib.dll" "Subdirectory/"
31+
]]></CoreLibSetupBash>
32+
<CLRTestBatchPreCommands>$(CLRTestBatchPreCommands);$(CoreLibSetupBatch)</CLRTestBatchPreCommands>
33+
<CLRTestBashPreCommands>$(CLRTestBashPreCommands);$(CoreLibSetupBash)</CLRTestBashPreCommands>
34+
</PropertyGroup>
35+
</Project>

0 commit comments

Comments
 (0)