Skip to content

Commit 3fb171e

Browse files
committed
Handle satellite assemblies in the Blazor build targets
* Pass the same closure of assemblies that is used by the SDK's linker to Blazor's linker and ResolveBlazorRuntimeDependencies task * Quote the mono linker path Fixes #17644 Fixes #17754
1 parent 2e2d062 commit 3fb171e

File tree

8 files changed

+191
-32
lines changed

8 files changed

+191
-32
lines changed

src/Components/Blazor/Build/src/Tasks/BlazorILLink.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,12 @@ private string DotNetPath
6666

6767
protected override string GenerateFullPathToTool() => DotNetPath;
6868

69-
protected override string GenerateCommandLineCommands() => ILLinkPath;
69+
protected override string GenerateCommandLineCommands()
70+
{
71+
var args = new StringBuilder();
72+
args.Append(Quote(ILLinkPath));
73+
return args.ToString();
74+
}
7075

7176
private static string Quote(string path)
7277
{

src/Components/Blazor/Build/src/Tasks/GenerateBlazorBootJson.cs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

4+
using System;
45
using System.IO;
56
using System.Linq;
67
using System.Reflection;
@@ -27,12 +28,23 @@ public class GenerateBlazorBootJson : Task
2728
public override bool Execute()
2829
{
2930
var entryAssemblyName = AssemblyName.GetAssemblyName(AssemblyPath).Name;
30-
var assemblies = References.Select(c => Path.GetFileName(c.ItemSpec)).ToArray();
31+
var assemblies = References.Select(GetUriPath).OrderBy(c => c, StringComparer.Ordinal).ToArray();
3132

3233
using var fileStream = File.Create(OutputPath);
3334
WriteBootJson(fileStream, entryAssemblyName, assemblies, LinkerEnabled);
3435

3536
return true;
37+
38+
static string GetUriPath(ITaskItem item)
39+
{
40+
var outputPath = item.GetMetadata("RelativeOutputPath");
41+
if (string.IsNullOrEmpty(outputPath))
42+
{
43+
outputPath = Path.GetFileName(item.ItemSpec);
44+
}
45+
46+
return outputPath.Replace('\\', '/');
47+
}
3648
}
3749

3850
internal static void WriteBootJson(Stream stream, string entryAssemblyName, string[] assemblies, bool linkerEnabled)

src/Components/Blazor/Build/src/targets/Blazor.MonoRuntime.targets

Lines changed: 52 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@
7474
</ItemGroup>
7575
</Target>
7676

77-
<Target Name="_ResolveBlazorInputs">
77+
<Target Name="_ResolveBlazorInputs" DependsOnTargets="ResolveReferences;ResolveRuntimePackAssets">
7878
<PropertyGroup>
7979
<!-- /obj/<<configuration>>/<<targetframework>>/blazor -->
8080
<BlazorIntermediateOutputPath>$(IntermediateOutputPath)blazor\</BlazorIntermediateOutputPath>
@@ -94,8 +94,6 @@
9494
</PropertyGroup>
9595

9696
<ItemGroup>
97-
<_BlazorDependencyInput Include="@(ReferenceCopyLocalPaths->WithMetadataValue('Extension','.dll')->'%(FullPath)')" />
98-
9997
<_WebAssemblyBCLFolder Include="
10098
$(DotNetWebAssemblyBCLPath);
10199
$(DotNetWebAssemblyBCLFacadesPath);
@@ -104,13 +102,50 @@
104102
<_WebAssemblyBCLAssembly Include="%(_WebAssemblyBCLFolder.Identity)*.dll" />
105103
</ItemGroup>
106104

105+
<!--
106+
Calculate the assemblies that act as inputs to calculate assembly closure. Based on _ComputeAssembliesToPostprocessOnPublish which is used as input to SDK's linker
107+
https://github.com/dotnet/sdk/blob/d597e7b09d7657ba4e326d6734e14fcbf8473564/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Publish.targets#L864-L873
108+
-->
109+
<ItemGroup>
110+
<!-- Assemblies from packages -->
111+
<_BlazorManagedRuntimeAssemby Include="@(RuntimeCopyLocalItems)" />
112+
113+
<!-- Assemblies from other references -->
114+
<_BlazorUserRuntimeAssembly Include="@(ReferencePath->WithMetadataValue('CopyLocal', 'true'))" />
115+
<_BlazorUserRuntimeAssembly Include="@(ReferenceDependencyPaths->WithMetadataValue('CopyLocal', 'true'))" />
116+
117+
<_BlazorManagedRuntimeAssemby Include="@(_BlazorUserRuntimeAssembly)" />
118+
<_BlazorManagedRuntimeAssemby Include="@(IntermediateAssembly)" />
119+
</ItemGroup>
120+
107121
<MakeDir Directories="$(BlazorIntermediateOutputPath)" />
108122
</Target>
109123

110124
<Target Name="_ResolveBlazorOutputs" DependsOnTargets="_ResolveBlazorOutputsWhenLinked;_ResolveBlazorOutputsWhenNotLinked">
111125
<Error
112126
Message="Unrecongnized value for BlazorLinkOnBuild: '$(BlazorLinkOnBuild)'. Valid values are 'true' or 'false'."
113127
Condition="'$(BlazorLinkOnBuild)' != 'true' AND '$(BlazorLinkOnBuild)' != 'false'" />
128+
129+
<ItemGroup>
130+
<!--
131+
ReferenceCopyLocalPaths includes all files that are part of the build out with CopyLocalLockFileAssemblies on.
132+
Remove assemblies that are inputs to calculating the assembly closure. Instead use the resolved outputs, since it is the minimal set.
133+
-->
134+
<_BlazorCopyLocalPaths Include="@(ReferenceCopyLocalPaths)" />
135+
<_BlazorCopyLocalPaths Remove="@(_BlazorManagedRuntimeAssemby)" />
136+
137+
<BlazorOutputWithTargetPath Include="@(_BlazorCopyLocalPaths)">
138+
<BlazorRuntimeFile>true</BlazorRuntimeFile>
139+
<TargetOutputPath>$(BlazorRuntimeBinOutputPath)%(_BlazorCopyLocalPaths.DestinationSubDirectory)%(FileName)%(Extension)</TargetOutputPath>
140+
<RelativeOutputPath>%(_BlazorCopyLocalPaths.DestinationSubDirectory)%(FileName)%(Extension)</RelativeOutputPath>
141+
</BlazorOutputWithTargetPath>
142+
143+
<BlazorOutputWithTargetPath Include="@(_BlazorResolvedAssembly)">
144+
<BlazorRuntimeFile>true</BlazorRuntimeFile>
145+
<TargetOutputPath>$(BlazorRuntimeBinOutputPath)%(FileName)%(Extension)</TargetOutputPath>
146+
<RelativeOutputPath>%(FileName)%(Extension)</RelativeOutputPath>
147+
</BlazorOutputWithTargetPath>
148+
</ItemGroup>
114149
</Target>
115150

116151
<!--
@@ -128,14 +163,8 @@
128163

129164
<!-- _BlazorLinkerOutputCache records files linked during the last incremental build of the target. Read the contents and assign linked files to be copied to the output. -->
130165
<ReadLinesFromFile File="$(_BlazorLinkerOutputCache)">
131-
<Output TaskParameter="Lines" ItemName="_BlazorLinkedFile"/>
166+
<Output TaskParameter="Lines" ItemName="_BlazorResolvedAssembly"/>
132167
</ReadLinesFromFile>
133-
134-
<ItemGroup>
135-
<BlazorOutputWithTargetPath Include="%(_BlazorLinkedFile.Identity)">
136-
<TargetOutputPath>$(BlazorRuntimeBinOutputPath)%(FileName)%(Extension)</TargetOutputPath>
137-
</BlazorOutputWithTargetPath>
138-
</ItemGroup>
139168
</Target>
140169

141170
<UsingTask TaskName="BlazorCreateRootDescriptorFile" AssemblyFile="$(BlazorTasksPath)" />
@@ -163,8 +192,7 @@
163192
<Target
164193
Name="_LinkBlazorApplication"
165194
Inputs="$(ProjectAssetsFile);
166-
@(IntermediateAssembly);
167-
@(_BlazorDependencyInput);
195+
@(_BlazorManagedRuntimeAssemby);
168196
@(BlazorLinkerDescriptor);
169197
$(MSBuildAllProjects)"
170198
Outputs="$(_BlazorLinkerOutputCache)">
@@ -174,12 +202,15 @@
174202
<_BlazorDependencyAssembly IsLinkable="true" Condition="$([System.String]::Copy('%(Filename)').StartsWith('System.'))" />
175203
<_BlazorDependencyAssembly IsLinkable="true" TypeGranularity="true" Condition="$([System.String]::Copy('%(Filename)').StartsWith('Microsoft.AspNetCore.'))" />
176204
<_BlazorDependencyAssembly IsLinkable="true" TypeGranularity="true" Condition="$([System.String]::Copy('%(Filename)').StartsWith('Microsoft.Extensions.'))" />
205+
<!-- Any assembly from a package reference that starts with System. file name is allowed to be linked -->
206+
<_BlazorRuntimeCopyLocalItems Include="@(RuntimeCopyLocalItems)" IsLinkable="$([System.String]::Copy('%(FileName)').StartsWith('System.'))" />
177207

178208
<_BlazorAssemblyToLink Include="@(_WebAssemblyBCLAssembly)" />
179-
<_BlazorAssemblyToLink Include="@(_BlazorDependencyAssembly)" Condition="'%(_BlazorDependencyAssembly.IsLinkable)' == 'true'" />
209+
<_BlazorAssemblyToLink Include="@(_BlazorRuntimeCopyLocalItems)" Condition="'%(_BlazorRuntimeCopyLocalItems.IsLinkable)' == 'true'" />
180210

181211
<_BlazorLinkerRoot Include="@(IntermediateAssembly)" />
182-
<_BlazorLinkerRoot Include="@(_BlazorDependencyAssembly)" Condition="'%(_BlazorDependencyAssembly.IsLinkable)' != 'true'" />
212+
<_BlazorLinkerRoot Include="@(_BlazorUserRuntimeAssembly)" />
213+
<_BlazorLinkerRoot Include="@(_BlazorRuntimeCopyLocalItems)" Condition="'%(_BlazorRuntimeCopyLocalItems.IsLinkable)' != 'true'" />
183214
</ItemGroup>
184215

185216
<PropertyGroup>
@@ -230,29 +261,22 @@
230261
<WriteLinesToFile File="$(_BlazorLinkerOutputCache)" Lines="@(_LinkerResult)" Overwrite="true" />
231262
</Target>
232263

233-
234264
<UsingTask TaskName="ResolveBlazorRuntimeDependencies" AssemblyFile="$(BlazorTasksPath)" />
235265
<Target
236266
Name="_ResolveBlazorOutputsWhenNotLinked"
237267
DependsOnTargets="_ResolveBlazorRuntimeDependencies"
238268
Condition="'$(BlazorLinkOnBuild)' != 'true'">
239269

240-
<ReadLinesFromFile File="$(_BlazorApplicationAssembliesCacheFile)" Condition="'@(_BlazorResolvedRuntimeDependencies->Count())' == '0'">
241-
<Output TaskParameter="Lines" ItemName="_BlazorResolvedRuntimeDependencies"/>
270+
<ReadLinesFromFile File="$(_BlazorApplicationAssembliesCacheFile)" Condition="'@(_BlazorResolvedAssembly->Count())' == '0'">
271+
<Output TaskParameter="Lines" ItemName="_BlazorResolvedAssembly"/>
242272
</ReadLinesFromFile>
243-
244-
<ItemGroup>
245-
<BlazorOutputWithTargetPath Include="@(_BlazorResolvedRuntimeDependencies)">
246-
<TargetOutputPath>$(BlazorRuntimeBinOutputPath)%(FileName)%(Extension)</TargetOutputPath>
247-
</BlazorOutputWithTargetPath>
248-
</ItemGroup>
249273
</Target>
250274

251275
<Target
252276
Name="_ResolveBlazorRuntimeDependencies"
253277
Inputs="$(ProjectAssetsFile);
254278
@(IntermediateAssembly);
255-
@(_BlazorDependencyInput)"
279+
@(_BlazorManagedRuntimeAssemby)"
256280
Outputs="$(_BlazorApplicationAssembliesCacheFile)">
257281

258282
<!--
@@ -262,10 +286,10 @@
262286
-->
263287
<ResolveBlazorRuntimeDependencies
264288
EntryPoint="@(IntermediateAssembly)"
265-
ApplicationDependencies="@(_BlazorDependencyInput)"
289+
ApplicationDependencies="@(_BlazorManagedRuntimeAssemby)"
266290
WebAssemblyBCLAssemblies="@(_WebAssemblyBCLAssembly)">
267291

268-
<Output TaskParameter="Dependencies" ItemName="_BlazorResolvedRuntimeDependencies" />
292+
<Output TaskParameter="Dependencies" ItemName="_BlazorResolvedAssembly" />
269293
</ResolveBlazorRuntimeDependencies>
270294

271295
<WriteLinesToFile File="$(_BlazorApplicationAssembliesCacheFile)" Lines="@(_BlazorResolvedRuntimeDependencies)" Overwrite="true" />
@@ -282,13 +306,12 @@
282306
Inputs="@(BlazorOutputWithTargetPath)"
283307
Outputs="$(BlazorBootJsonIntermediateOutputPath)">
284308
<ItemGroup>
285-
<_AppReferences Include="@(BlazorOutputWithTargetPath->WithMetadataValue('Extension','.dll'))" />
286-
<_AppReferences Include="@(BlazorOutputWithTargetPath->WithMetadataValue('Extension','.pdb'))" Condition="'$(BlazorEnableDebugging)' == 'true'" />
309+
<_BlazorRuntimeFile Include="@(BlazorOutputWithTargetPath->WithMetadataValue('BlazorRuntimeFile', 'true'))" />
287310
</ItemGroup>
288311

289312
<GenerateBlazorBootJson
290313
AssemblyPath="@(IntermediateAssembly)"
291-
References="@(_AppReferences)"
314+
References="@(_BlazorRuntimeFile)"
292315
LinkerEnabled="$(BlazorLinkOnBuild)"
293316
OutputPath="$(BlazorBootJsonIntermediateOutputPath)" />
294317

src/Components/Blazor/Build/test/BuildIntegrationTests/BuildIntegrationTest.cs

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,5 +70,66 @@ public async Task Build_WithLinkOnBuildDisabled_Works()
7070
Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "standalone.dll");
7171
Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "Microsoft.Extensions.Logging.Abstractions.dll"); // Verify dependencies are part of the output.
7272
}
73+
74+
[Fact]
75+
public async Task Build_SatelliteAssembliesAreCopiedToBuildOutput()
76+
{
77+
// Arrange
78+
using var project = ProjectDirectory.Create("standalone", additionalProjects: new[] { "razorclasslibrary", "classlibrarywithsatelliteassemblies" });
79+
project.AddProjectFileContent(
80+
@"
81+
<PropertyGroup>
82+
<DefineConstants>$(DefineConstants);REFERENCE_classlibrarywithsatelliteassemblies</DefineConstants>
83+
</PropertyGroup>
84+
<ItemGroup>
85+
<ProjectReference Include=""..\classlibrarywithsatelliteassemblies\classlibrarywithsatelliteassemblies.csproj"" />
86+
</ItemGroup>");
87+
88+
var result = await MSBuildProcessManager.DotnetMSBuild(project, args: "/restore");
89+
90+
Assert.BuildPassed(result);
91+
92+
var buildOutputDirectory = project.BuildOutputDirectory;
93+
94+
Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "standalone.dll");
95+
Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "classlibrarywithsatelliteassemblies.dll");
96+
Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "Microsoft.CodeAnalysis.CSharp.dll");
97+
Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "fr", "Microsoft.CodeAnalysis.CSharp.resources.dll"); // Verify satellite assemblies are present in the build output.
98+
99+
var bootJsonPath = Path.Combine(buildOutputDirectory, "dist", "_framework", "blazor.boot.json");
100+
Assert.FileContains(result, bootJsonPath, "\"Microsoft.CodeAnalysis.CSharp.dll\"");
101+
Assert.FileContains(result, bootJsonPath, "\"fr\\/Microsoft.CodeAnalysis.CSharp.resources.dll\"");
102+
}
103+
104+
[Fact]
105+
public async Task Build_WithBlazorLinkOnBuildFalse_SatelliteAssembliesAreCopiedToBuildOutput()
106+
{
107+
// Arrange
108+
using var project = ProjectDirectory.Create("standalone", additionalProjects: new[] { "razorclasslibrary", "classlibrarywithsatelliteassemblies" });
109+
project.AddProjectFileContent(
110+
@"
111+
<PropertyGroup>
112+
<BlazorLinkOnBuild>false</BlazorLinkOnBuild>
113+
<DefineConstants>$(DefineConstants);REFERENCE_classlibrarywithsatelliteassemblies</DefineConstants>
114+
</PropertyGroup>
115+
<ItemGroup>
116+
<ProjectReference Include=""..\classlibrarywithsatelliteassemblies\classlibrarywithsatelliteassemblies.csproj"" />
117+
</ItemGroup>");
118+
119+
var result = await MSBuildProcessManager.DotnetMSBuild(project, args: "/restore");
120+
121+
Assert.BuildPassed(result);
122+
123+
var buildOutputDirectory = project.BuildOutputDirectory;
124+
125+
Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "standalone.dll");
126+
Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "classlibrarywithsatelliteassemblies.dll");
127+
Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "Microsoft.CodeAnalysis.CSharp.dll");
128+
Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "fr", "Microsoft.CodeAnalysis.CSharp.resources.dll"); // Verify satellite assemblies are present in the build output.
129+
130+
var bootJsonPath = Path.Combine(buildOutputDirectory, "dist", "_framework", "blazor.boot.json");
131+
Assert.FileContains(result, bootJsonPath, "\"Microsoft.CodeAnalysis.CSharp.dll\"");
132+
Assert.FileContains(result, bootJsonPath, "\"fr\\/Microsoft.CodeAnalysis.CSharp.resources.dll\"");
133+
}
73134
}
74135
}

src/Components/Blazor/Build/test/BuildIntegrationTests/PublishIntegrationTest.cs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,35 @@ public async Task Publish_WithLinkOnBuildDisabled_Works()
112112
Assert.FileExists(result, publishDirectory, "web.config");
113113
}
114114

115+
[Fact]
116+
public async Task Publish_SatelliteAssemblies_AreCopiedToBuildOutput()
117+
{
118+
// Arrange
119+
using var project = ProjectDirectory.Create("standalone", additionalProjects: new[] { "razorclasslibrary", "classlibrarywithsatelliteassemblies" });
120+
project.AddProjectFileContent(
121+
@"
122+
<PropertyGroup>
123+
<DefineConstants>$(DefineConstants);REFERENCE_classlibrarywithsatelliteassemblies</DefineConstants>
124+
</PropertyGroup>
125+
<ItemGroup>
126+
<ProjectReference Include=""..\classlibrarywithsatelliteassemblies\classlibrarywithsatelliteassemblies.csproj"" />
127+
</ItemGroup>");
128+
129+
var result = await MSBuildProcessManager.DotnetMSBuild(project, "Publish", args: "/restore");
130+
131+
Assert.BuildPassed(result);
132+
133+
var publishDirectory = project.PublishOutputDirectory;
134+
var blazorPublishDirectory = Path.Combine(publishDirectory, Path.GetFileNameWithoutExtension(project.ProjectFilePath));
135+
136+
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "_bin", "Microsoft.CodeAnalysis.CSharp.dll");
137+
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "_bin", "fr", "Microsoft.CodeAnalysis.CSharp.resources.dll"); // Verify satellite assemblies are present in the build output.
138+
139+
var bootJsonPath = Path.Combine(blazorPublishDirectory, "dist", "_framework", "blazor.boot.json");
140+
Assert.FileContains(result, bootJsonPath, "\"Microsoft.CodeAnalysis.CSharp.dll\"");
141+
Assert.FileContains(result, bootJsonPath, "\"fr\\/Microsoft.CodeAnalysis.CSharp.resources.dll\"");
142+
}
143+
115144
[Fact]
116145
public async Task Publish_HostedApp_Works()
117146
{
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
using System;
2+
3+
namespace classlibrarywithsatelliteassemblies
4+
{
5+
public class Class1
6+
{
7+
public static void Test()
8+
{
9+
GC.KeepAlive(typeof(Microsoft.CodeAnalysis.CSharp.CSharpCompilation));
10+
}
11+
}
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<Project Sdk="Microsoft.NET.Sdk.Razor">
2+
3+
<PropertyGroup>
4+
<TargetFramework>netstandard2.1</TargetFramework>
5+
<RazorLangVersion>3.0</RazorLangVersion>
6+
</PropertyGroup>
7+
8+
<ItemGroup>
9+
<!-- The compiler package contains quite a few satellite assemblies -->
10+
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.4.0" />
11+
</ItemGroup>
12+
13+
</Project>
Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
1-

1+
using System;
2+
23
namespace standalone
34
{
45
public class Program
56
{
67
public static void Main(string[] args)
78
{
9+
#if REFERENCE_classlibrarywithsatelliteassemblies
10+
GC.KeepAlive(typeof(classlibrarywithsatelliteassemblies.Class1));
11+
#endif
812
}
913
}
1014
}

0 commit comments

Comments
 (0)