Skip to content

Commit bb71812

Browse files
authored
Update host to use the optional localPath property when resolving assets (#118297)
Currently, when resolving RID-agnostic assets from an application itself (that is, those in the app's .deps.json), any subdirectory specified in the file path is ignored. We just take the file name and expect it to be next to the .deps.json. #117682 added an optional `localPath` property for assets. If set, this represents the path on disk relative to the .deps.json. This change updates the host to consume that property. When resolving assets from an application itself: 1. `localPath` property is set: use `<deps_dir>/<localPath>` 2. asset is from a runtimepack: use `<deps_dir>/<file_path>` - runtimepack assets are always generated with the actual destination path in the file path 3. otherwise (this is the existing behaviour): - RID-specific assets: use `<deps_dir>/<file_path>` - RID-agnostic assets: - runtime: take only the file name, use `<deps_dir>/<file_name>` - resources: take parent directory name of the file path, use `<deps_dir>/<dir_name>/<file_name>` With this change, usage of app-relative .NET search, and some msbuild hackery, it is possible to publish a self-contained application and stick the runtimepack in a subdirectory: ```xml <PropertyGroup> <AppHostRelativeDotNet>runtimepack</AppHostRelativeDotNet> </PropertyGroup> <Target Name="UpdateRuntimePackAssets" AfterTargets="ResolveRuntimePackAssets"> <ItemGroup> <RuntimePackAsset Update="@(RuntimePackAsset)->WithMetadataValue('CopyLocal', 'true')" DestinationSubPath="$(AppHostRelativeDotNet)\%(FileName)%(Extension)" DestinationSubDirectory="$(AppHostRelativeDotNet)\" /> <ReferenceCopyLocalPaths Remove="@(RuntimePackAsset)" /> <ReferenceCopyLocalPaths Include="@(RuntimePackAsset)" /> </ItemGroup> </Target> ``` Once we have the corresponding change on the SDK side to include the localPath property, references can be put in a subdirectory as well (also with some msbuild hackery)
1 parent a693af3 commit bb71812

File tree

11 files changed

+505
-97
lines changed

11 files changed

+505
-97
lines changed

src/installer/tests/HostActivation.Tests/DependencyResolution/DependencyResolutionCommandResultExtensions.cs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation.DependencyResolution
1212
public static class DependencyResolutionCommandResultExtensions
1313
{
1414
// App asset resolution extensions
15-
public const string TRUSTED_PLATFORM_ASSEMBLIES = "TRUSTED_PLATFORM_ASSEMBLIES";
16-
public const string NATIVE_DLL_SEARCH_DIRECTORIES = "NATIVE_DLL_SEARCH_DIRECTORIES";
15+
private const string TRUSTED_PLATFORM_ASSEMBLIES = nameof(TRUSTED_PLATFORM_ASSEMBLIES);
16+
private const string NATIVE_DLL_SEARCH_DIRECTORIES = nameof(NATIVE_DLL_SEARCH_DIRECTORIES);
17+
private const string PLATFORM_RESOURCE_ROOTS = nameof(PLATFORM_RESOURCE_ROOTS);
1718

1819
public static AndConstraint<CommandResultAssertions> HaveRuntimePropertyContaining(this CommandResultAssertions assertion, string propertyName, params string[] values)
1920
{
@@ -67,6 +68,16 @@ public static AndConstraint<CommandResultAssertions> NotHaveResolvedNativeLibrar
6768
return assertion.NotHaveRuntimePropertyContaining(NATIVE_DLL_SEARCH_DIRECTORIES, RelativePathsToAbsoluteAppPaths(path, app));
6869
}
6970

71+
public static AndConstraint<CommandResultAssertions> HaveResolvedResourceRootPath(this CommandResultAssertions assertion, string path, TestApp app = null)
72+
{
73+
return assertion.HaveRuntimePropertyContaining(PLATFORM_RESOURCE_ROOTS, RelativePathsToAbsoluteAppPaths(path, app));
74+
}
75+
76+
public static AndConstraint<CommandResultAssertions> NotHaveResolvedResourceRootPath(this CommandResultAssertions assertion, string path, TestApp app = null)
77+
{
78+
return assertion.NotHaveRuntimePropertyContaining(PLATFORM_RESOURCE_ROOTS, RelativePathsToAbsoluteAppPaths(path, app));
79+
}
80+
7081
// Component asset resolution extensions
7182
private const string assemblies = "assemblies";
7283
private const string native_search_paths = "native_search_paths";
Lines changed: 265 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,265 @@
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 Microsoft.DotNet.Cli.Build;
7+
using Xunit;
8+
using static Microsoft.DotNet.CoreSetup.Test.NetCoreAppBuilder;
9+
10+
namespace Microsoft.DotNet.CoreSetup.Test.HostActivation.DependencyResolution
11+
{
12+
public class LocalPath : IClassFixture<LocalPath.SharedTestState>
13+
{
14+
private readonly SharedTestState sharedState;
15+
16+
public LocalPath(SharedTestState sharedState)
17+
{
18+
this.sharedState = sharedState;
19+
}
20+
21+
[Theory]
22+
[InlineData(true)]
23+
[InlineData(false)]
24+
public void RuntimeAssemblies_FrameworkDependent(bool useLocalPath) => RuntimeAssemblies(isSelfContained: false, useLocalPath);
25+
26+
[Theory]
27+
[InlineData(true)]
28+
[InlineData(false)]
29+
public void RuntimeAssemblies_SelfContained(bool useLocalPath) => RuntimeAssemblies(isSelfContained: true, useLocalPath);
30+
31+
[Theory]
32+
[InlineData(true)]
33+
[InlineData(false)]
34+
public void NativeLibraries_FrameworkDependent(bool useLocalPath) => NativeLibraries(isSelfContained: false, useLocalPath);
35+
36+
[Theory]
37+
[InlineData(true)]
38+
[InlineData(false)]
39+
public void NativeLibraries_SelfContained(bool useLocalPath) => NativeLibraries(isSelfContained: true, useLocalPath);
40+
41+
[Theory]
42+
[InlineData(true)]
43+
[InlineData(false)]
44+
public void ResourceAssemblies_FrameworkDependent(bool useLocalPath) => ResourceAssemblies(isSelfContained: false, useLocalPath);
45+
46+
[Theory]
47+
[InlineData(true)]
48+
[InlineData(false)]
49+
public void ResourceAssemblies_SelfContained(bool useLocalPath) => ResourceAssemblies(isSelfContained: true, useLocalPath);
50+
51+
private void RuntimeAssemblies(bool isSelfContained, bool useLocalPath)
52+
{
53+
RuntimeLibraryType[] libraryTypes = [ RuntimeLibraryType.project, RuntimeLibraryType.package, RuntimeLibraryType.runtimepack ];
54+
55+
Action<NetCoreAppBuilder> customizer = b =>
56+
{
57+
foreach (var libraryType in libraryTypes)
58+
{
59+
string library = $"Test{libraryType}";
60+
(string path, string localPath) = GetPaths(libraryType, false);
61+
b.WithRuntimeLibrary(libraryType, library, "1.0.0", p => p
62+
.WithAssemblyGroup(null, g => g
63+
.WithAsset(path, useLocalPath ? f => f.WithLocalPath(localPath) : null)));
64+
65+
if (!isSelfContained)
66+
{
67+
// Add RID-specific assembly
68+
(string ridPath, string localRidPath) = GetPaths(libraryType, true);
69+
b.WithRuntimeLibrary(libraryType, $"{library}-{TestContext.BuildRID}", "1.0.0", p => p
70+
.WithAssemblyGroup(TestContext.BuildRID, g => g
71+
.WithAsset(ridPath, useLocalPath ? f => f.WithLocalPath(localRidPath) : null)));
72+
}
73+
}
74+
75+
b.WithLocalPathsInDepsJson(useLocalPath);
76+
};
77+
78+
using TestApp app = CreateApp(isSelfContained, customizer);
79+
var result = sharedState.DotNetWithNetCoreApp.Exec(app.AppDll)
80+
.EnableTracingAndCaptureOutputs()
81+
.Execute();
82+
result.Should().Pass();
83+
84+
// Check all library types
85+
foreach (var libraryType in libraryTypes)
86+
{
87+
// Check RID-agnostic assembly
88+
(string path, string localPath) = GetPaths(libraryType, false);
89+
90+
// Without localPath, RID-agnostic non-runtimepack runtime assemblies are assumed to be in <app_directory>
91+
string relativePath = useLocalPath
92+
? localPath
93+
: libraryType == RuntimeLibraryType.runtimepack ? path : Path.GetFileName(path);
94+
string expectedPath = Path.Join(app.Location, relativePath);
95+
result.Should().HaveResolvedAssembly(expectedPath);
96+
if (useLocalPath)
97+
{
98+
result.Should().NotHaveResolvedAssembly(Path.Join(app.Location, path));
99+
}
100+
101+
// Check RID-specific assembly
102+
if (!isSelfContained)
103+
{
104+
(string ridPath, string localRidPath) = GetPaths(libraryType, true);
105+
string expectedRidPath = Path.Join(app.Location, useLocalPath ? localRidPath : ridPath);
106+
result.Should().HaveResolvedAssembly(expectedRidPath);
107+
if (useLocalPath)
108+
{
109+
result.Should().NotHaveResolvedAssembly(Path.Join(app.Location, ridPath));
110+
}
111+
}
112+
}
113+
114+
static (string Path, string LocalPath) GetPaths(RuntimeLibraryType libraryType, bool useRid)
115+
{
116+
string library = $"Test{libraryType}";
117+
string path = useRid ? $"lib/{TestContext.BuildRID}/{library}-{TestContext.BuildRID}.dll" : $"lib/{library}.dll";
118+
return (path, $"{libraryType}/{path}");
119+
}
120+
}
121+
122+
private void NativeLibraries(bool isSelfContained, bool useLocalPath)
123+
{
124+
NetCoreAppBuilder.RuntimeLibraryType[] libraryTypes = [NetCoreAppBuilder.RuntimeLibraryType.project, NetCoreAppBuilder.RuntimeLibraryType.package, NetCoreAppBuilder.RuntimeLibraryType.runtimepack];
125+
126+
Action<NetCoreAppBuilder> customizer = b =>
127+
{
128+
foreach (var libraryType in libraryTypes)
129+
{
130+
string library = $"Test{libraryType}";
131+
(string path, string localPath) = GetPaths(libraryType, false);
132+
b.WithRuntimeLibrary(libraryType, library, "1.0.0", p => p
133+
.WithNativeLibraryGroup(null, g => g
134+
.WithAsset($"{path}/{library}.native", useLocalPath ? f => f.WithLocalPath($"{localPath}/{library}.native") : null)));
135+
136+
if (!isSelfContained)
137+
{
138+
// Add RID-specific native library
139+
(string ridPath, string localRidPath) = GetPaths(libraryType, true);
140+
b.WithRuntimeLibrary(libraryType, $"{library}-{TestContext.BuildRID}", "1.0.0", p => p
141+
.WithNativeLibraryGroup(TestContext.BuildRID, g => g
142+
.WithAsset($"{ridPath}/{library}-{TestContext.BuildRID}.native", useLocalPath ? f => f.WithLocalPath($"{localRidPath}/{library}-{TestContext.BuildRID}.native") : null)));
143+
}
144+
}
145+
146+
b.WithLocalPathsInDepsJson(useLocalPath);
147+
};
148+
149+
using TestApp app = CreateApp(isSelfContained, customizer);
150+
var result = sharedState.DotNetWithNetCoreApp.Exec(app.AppDll)
151+
.EnableTracingAndCaptureOutputs()
152+
.Execute();
153+
result.Should().Pass();
154+
155+
// Check all library types
156+
foreach (NetCoreAppBuilder.RuntimeLibraryType libraryType in libraryTypes)
157+
{
158+
// Check RID-agnostic native library path
159+
(string path, string localPath) = GetPaths(libraryType, false);
160+
161+
// Without localPath, RID-agnostic non-runtimepack native libraries are assumed to be in <app_directory>
162+
string relativePath = useLocalPath
163+
? localPath
164+
: libraryType == RuntimeLibraryType.runtimepack ? path : string.Empty;
165+
string expectedPath = Path.Join(app.Location, relativePath);
166+
result.Should().HaveResolvedNativeLibraryPath(expectedPath);
167+
if (useLocalPath)
168+
{
169+
result.Should().NotHaveResolvedNativeLibraryPath(Path.Join(app.Location, path));
170+
}
171+
172+
// Check RID-specific native library path
173+
if (!isSelfContained)
174+
{
175+
(string ridPath, string localRidPath) = GetPaths(libraryType, true);
176+
string expectedRidPath = Path.Join(app.Location, useLocalPath ? localRidPath : ridPath);
177+
result.Should().HaveResolvedNativeLibraryPath(expectedRidPath);
178+
if (useLocalPath)
179+
{
180+
result.Should().NotHaveResolvedNativeLibraryPath(Path.Join(app.Location, ridPath));
181+
}
182+
}
183+
}
184+
185+
static (string Path, string LocalPath) GetPaths(NetCoreAppBuilder.RuntimeLibraryType libraryType, bool useRid)
186+
{
187+
string path = useRid ? $"native/{TestContext.BuildRID}" : "native";
188+
return (path, $"{libraryType}/{path}");
189+
}
190+
}
191+
192+
private void ResourceAssemblies(bool isSelfContained, bool useLocalPath)
193+
{
194+
NetCoreAppBuilder.RuntimeLibraryType[] libraryTypes = [NetCoreAppBuilder.RuntimeLibraryType.project, NetCoreAppBuilder.RuntimeLibraryType.package, NetCoreAppBuilder.RuntimeLibraryType.runtimepack];
195+
196+
Action<NetCoreAppBuilder> customizer = b =>
197+
{
198+
foreach (var libraryType in libraryTypes)
199+
{
200+
string library = $"Test{libraryType}";
201+
(string path, string localPath) = GetPaths(libraryType);
202+
b.WithRuntimeLibrary(libraryType, library, "1.0.0", p => p
203+
.WithResourceAssembly($"{path}/fr/{library}.resources.dll", useLocalPath ? f => f.WithLocalPath($"{localPath}/fr/{library}.resources.dll") : null));
204+
}
205+
206+
b.WithLocalPathsInDepsJson(useLocalPath);
207+
};
208+
209+
using TestApp app = CreateApp(isSelfContained, customizer);
210+
var result = sharedState.DotNetWithNetCoreApp.Exec(app.AppDll)
211+
.EnableTracingAndCaptureOutputs()
212+
.Execute();
213+
result.Should().Pass();
214+
215+
// Check all library types
216+
foreach (var libraryType in libraryTypes)
217+
{
218+
(string path, string localPath) = GetPaths(libraryType);
219+
220+
// Without localPath, non-runtimepack resource assemblies are assumed to be in <app_directory>/<locale>/
221+
string relativePath = useLocalPath
222+
? localPath
223+
: libraryType == RuntimeLibraryType.runtimepack ? path : string.Empty;
224+
string expectedPath = Path.Join(app.Location, relativePath);
225+
result.Should().HaveResolvedResourceRootPath(expectedPath);
226+
if (useLocalPath)
227+
{
228+
result.Should().NotHaveResolvedResourceRootPath(Path.Join(app.Location, path));
229+
}
230+
}
231+
232+
static (string Path, string LocalPath) GetPaths(NetCoreAppBuilder.RuntimeLibraryType libraryType)
233+
{
234+
string path = $"resources";
235+
return (path, $"{libraryType}/{path}");
236+
}
237+
}
238+
239+
private static TestApp CreateApp(bool isSelfContained, Action<NetCoreAppBuilder> customizer)
240+
{
241+
TestApp app = TestApp.CreateEmpty("App");
242+
if (isSelfContained)
243+
{
244+
app.PopulateSelfContained(TestApp.MockedComponent.CoreClr, customizer);
245+
}
246+
else
247+
{
248+
app.PopulateFrameworkDependent(Constants.MicrosoftNETCoreApp, TestContext.MicrosoftNETCoreAppVersion, customizer);
249+
}
250+
return app;
251+
}
252+
253+
public class SharedTestState : SharedTestStateBase
254+
{
255+
public DotNetCli DotNetWithNetCoreApp { get; }
256+
257+
public SharedTestState()
258+
{
259+
DotNetWithNetCoreApp = DotNet("WithNetCoreApp")
260+
.AddMicrosoftNETCoreAppFrameworkMockCoreClr(TestContext.MicrosoftNETCoreAppVersion)
261+
.Build();
262+
}
263+
}
264+
}
265+
}

src/installer/tests/HostActivation.Tests/DependencyResolution/ResolveComponentDependencies.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -458,7 +458,7 @@ public TestApp CreateComponentWithDependencies(Action<NetCoreAppBuilder> customi
458458
.WithPackage(AdditionalDependencyName, "2.0.1", p => p.WithAssemblyGroup(null, g => g
459459
.WithAsset($"lib/netstandard1.0/{AdditionalDependencyName}.dll", f => f
460460
.WithVersion("2.0.0.0", "2.0.1.23344")
461-
.WithFileOnDiskPath($"{AdditionalDependencyName}.dll"))))
461+
.WithLocalPath($"{AdditionalDependencyName}.dll"))))
462462
.WithPackage("Libuv", "1.9.1", p => p
463463
.WithNativeLibraryGroup("debian-x64", g => g.WithAsset("runtimes/debian-x64/native/libuv.so"))
464464
.WithNativeLibraryGroup("fedora-x64", g => g.WithAsset("runtimes/fedora-x64/native/libuv.so"))

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

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,15 @@
33

44
using System;
55
using System.IO;
6+
using System.Linq;
67
using System.Runtime.InteropServices;
78
using FluentAssertions;
89
using Microsoft.DotNet.Cli.Build.Framework;
910
using Microsoft.DotNet.CoreSetup.Test;
1011
using Microsoft.NET.HostModel.AppHost;
1112
using Xunit;
13+
using static Microsoft.DotNet.CoreSetup.Test.NetCoreAppBuilder;
14+
using static Microsoft.NET.HostModel.AppHost.HostWriter;
1215

1316
namespace HostActivation.Tests
1417
{
@@ -180,6 +183,51 @@ public void DevicePath()
180183
.And.HaveStdOutContaining(TestContext.MicrosoftNETCoreAppVersion);
181184
}
182185

186+
[Fact]
187+
public void CustomRuntimeLocation()
188+
{
189+
string subdirectory = "runtime-directory";
190+
Action<NetCoreAppBuilder> customizer = b =>
191+
{
192+
// Find the NETCoreApp runtime pack
193+
RuntimeLibraryBuilder netCoreApp = b.RuntimeLibraries.First(
194+
r => r.Type == RuntimeLibraryType.runtimepack.ToString() && r.Name == $"runtimepack.{Constants.MicrosoftNETCoreApp}.Runtime.{TestContext.BuildRID}");
195+
196+
// Update all NETCoreApp asset paths to point to the subdirectory
197+
RuntimeAssetGroupBuilder[] groups = [.. netCoreApp.AssemblyGroups, .. netCoreApp.NativeLibraryGroups];
198+
foreach (RuntimeAssetGroupBuilder group in groups)
199+
{
200+
foreach (RuntimeFileBuilder asset in group.Assets)
201+
{
202+
asset.Path = Path.Join(subdirectory, asset.Path);
203+
}
204+
}
205+
206+
foreach (ResourceAssemblyBuilder resource in netCoreApp.ResourceAssemblies)
207+
{
208+
resource.Path = Path.Join(subdirectory, resource.Path);
209+
}
210+
};
211+
212+
using TestApp app = TestApp.CreateFromBuiltAssets("HelloWorld");
213+
app.PopulateSelfContained(TestApp.MockedComponent.None, customizer);
214+
app.CreateAppHost(dotNetRootOptions: new()
215+
{
216+
Location = DotNetSearchOptions.SearchLocation.AppRelative,
217+
AppRelativeDotNet = subdirectory
218+
});
219+
220+
// Apphost should be able to find hostfxr based on the .NET search options
221+
// Runtime files should be resolved based on relative path in .deps.json
222+
Command.Create(app.AppExe)
223+
.EnableTracingAndCaptureOutputs()
224+
.Execute()
225+
.Should().Pass()
226+
.And.HaveStdOutContaining("Hello World")
227+
.And.HaveStdOutContaining(TestContext.MicrosoftNETCoreAppVersion)
228+
.And.HaveStdErrContaining($"CoreCLR path = '{Path.Join(app.Location, subdirectory, Binaries.CoreClr.FileName)}'");
229+
}
230+
183231
public class SharedTestState : IDisposable
184232
{
185233
public TestApp App { get; }

src/installer/tests/TestUtils/DotNetBuilder.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,13 +140,13 @@ public DotNetBuilder AddMicrosoftNETCoreAppFrameworkMockCoreClr(string version,
140140
// ./shared/Microsoft.NETCore.App/<version>/coreclr.dll - this is a mock, will not actually run CoreClr
141141
.WithAsset((new NetCoreAppBuilder.RuntimeFileBuilder($"runtimes/{currentRid}/native/{Binaries.CoreClr.FileName}"))
142142
.CopyFromFile(Binaries.CoreClr.MockPath)
143-
.WithFileOnDiskPath(Binaries.CoreClr.FileName))))
143+
.WithLocalPath(Binaries.CoreClr.FileName))))
144144
.WithPackage($"runtime.{currentRid}.Microsoft.NETCore.DotNetHostPolicy", version, p => p
145145
.WithNativeLibraryGroup(null, g => g
146146
// ./shared/Microsoft.NETCore.App/<version>/hostpolicy.dll - this is the real component and will load CoreClr library
147147
.WithAsset((new NetCoreAppBuilder.RuntimeFileBuilder($"runtimes/{currentRid}/native/{Binaries.HostPolicy.FileName}"))
148148
.CopyFromFile(Binaries.HostPolicy.FilePath)
149-
.WithFileOnDiskPath(Binaries.HostPolicy.FileName))))
149+
.WithLocalPath(Binaries.HostPolicy.FileName))))
150150
.WithCustomizer(customizer)
151151
.Build(new TestApp(netCoreAppPath, "Microsoft.NETCore.App"));
152152

0 commit comments

Comments
 (0)