Skip to content

Commit 9643b72

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 d4ada34 commit 9643b72

File tree

8 files changed

+191
-33
lines changed

8 files changed

+191
-33
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 & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@
7676
</ItemGroup>
7777
</Target>
7878

79-
<Target Name="_ResolveBlazorInputs">
79+
<Target Name="_ResolveBlazorInputs" DependsOnTargets="ResolveReferences;ResolveRuntimePackAssets;ResolveLockFileCopyLocalFiles">
8080
<PropertyGroup>
8181
<!-- /obj/<<configuration>>/<<targetframework>>/blazor -->
8282
<BlazorIntermediateOutputPath>$(IntermediateOutputPath)blazor\</BlazorIntermediateOutputPath>
@@ -96,8 +96,6 @@
9696
</PropertyGroup>
9797

9898
<ItemGroup>
99-
<_BlazorDependencyInput Include="@(ReferenceCopyLocalPaths->WithMetadataValue('Extension','.dll')->'%(FullPath)')" />
100-
10199
<_WebAssemblyBCLFolder Include="
102100
$(DotNetWebAssemblyBCLPath);
103101
$(DotNetWebAssemblyBCLFacadesPath);
@@ -106,13 +104,50 @@
106104
<_WebAssemblyBCLAssembly Include="%(_WebAssemblyBCLFolder.Identity)*.dll" />
107105
</ItemGroup>
108106

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

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

118153
<!--
@@ -130,14 +165,8 @@
130165

131166
<!-- _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. -->
132167
<ReadLinesFromFile File="$(_BlazorLinkerOutputCache)">
133-
<Output TaskParameter="Lines" ItemName="_BlazorLinkedFile"/>
168+
<Output TaskParameter="Lines" ItemName="_BlazorResolvedAssembly"/>
134169
</ReadLinesFromFile>
135-
136-
<ItemGroup>
137-
<BlazorOutputWithTargetPath Include="%(_BlazorLinkedFile.Identity)">
138-
<TargetOutputPath>$(BlazorRuntimeBinOutputPath)%(FileName)%(Extension)</TargetOutputPath>
139-
</BlazorOutputWithTargetPath>
140-
</ItemGroup>
141170
</Target>
142171

143172
<UsingTask TaskName="BlazorCreateRootDescriptorFile" AssemblyFile="$(BlazorTasksPath)" />
@@ -164,20 +193,21 @@
164193
<Target
165194
Name="_LinkBlazorApplication"
166195
Inputs="$(ProjectAssetsFile);
167-
@(IntermediateAssembly);
168-
@(_BlazorDependencyInput);
196+
@(_BlazorManagedRuntimeAssemby);
169197
@(BlazorLinkerDescriptor);
170198
$(MSBuildAllProjects)"
171199
Outputs="$(_BlazorLinkerOutputCache)">
172200

173201
<ItemGroup>
174-
<_BlazorDependencyAssembly Include="@(_BlazorDependencyInput)" IsLinkable="$([System.String]::Copy('%(FileName)').StartsWith('System.'))" />
202+
<!-- Any assembly from a package reference that starts with System. file name is allowed to be linked -->
203+
<_BlazorRuntimeCopyLocalItems Include="@(RuntimeCopyLocalItems)" IsLinkable="$([System.String]::Copy('%(FileName)').StartsWith('System.'))" />
175204

176205
<_BlazorAssemblyToLink Include="@(_WebAssemblyBCLAssembly)" />
177-
<_BlazorAssemblyToLink Include="@(_BlazorDependencyAssembly)" Condition="'%(_BlazorDependencyAssembly.IsLinkable)' == 'true'" />
206+
<_BlazorAssemblyToLink Include="@(_BlazorRuntimeCopyLocalItems)" Condition="'%(_BlazorRuntimeCopyLocalItems.IsLinkable)' == 'true'" />
178207

179208
<_BlazorLinkerRoot Include="@(IntermediateAssembly)" />
180-
<_BlazorLinkerRoot Include="@(_BlazorDependencyAssembly)" Condition="'%(_BlazorDependencyAssembly.IsLinkable)' != 'true'" />
209+
<_BlazorLinkerRoot Include="@(_BlazorUserRuntimeAssembly)" />
210+
<_BlazorLinkerRoot Include="@(_BlazorRuntimeCopyLocalItems)" Condition="'%(_BlazorRuntimeCopyLocalItems.IsLinkable)' != 'true'" />
181211
</ItemGroup>
182212

183213
<PropertyGroup>
@@ -219,29 +249,22 @@
219249
<WriteLinesToFile File="$(_BlazorLinkerOutputCache)" Lines="@(_LinkerResult)" Overwrite="true" />
220250
</Target>
221251

222-
223252
<UsingTask TaskName="ResolveBlazorRuntimeDependencies" AssemblyFile="$(BlazorTasksPath)" />
224253
<Target
225254
Name="_ResolveBlazorOutputsWhenNotLinked"
226255
DependsOnTargets="_ResolveBlazorRuntimeDependencies"
227256
Condition="'$(BlazorLinkOnBuild)' != 'true'">
228257

229-
<ReadLinesFromFile File="$(_BlazorApplicationAssembliesCacheFile)" Condition="'@(_BlazorResolvedRuntimeDependencies->Count())' == '0'">
230-
<Output TaskParameter="Lines" ItemName="_BlazorResolvedRuntimeDependencies"/>
258+
<ReadLinesFromFile File="$(_BlazorApplicationAssembliesCacheFile)" Condition="'@(_BlazorResolvedAssembly->Count())' == '0'">
259+
<Output TaskParameter="Lines" ItemName="_BlazorResolvedAssembly"/>
231260
</ReadLinesFromFile>
232-
233-
<ItemGroup>
234-
<BlazorOutputWithTargetPath Include="@(_BlazorResolvedRuntimeDependencies)">
235-
<TargetOutputPath>$(BlazorRuntimeBinOutputPath)%(FileName)%(Extension)</TargetOutputPath>
236-
</BlazorOutputWithTargetPath>
237-
</ItemGroup>
238261
</Target>
239262

240263
<Target
241264
Name="_ResolveBlazorRuntimeDependencies"
242265
Inputs="$(ProjectAssetsFile);
243266
@(IntermediateAssembly);
244-
@(_BlazorDependencyInput)"
267+
@(_BlazorManagedRuntimeAssemby)"
245268
Outputs="$(_BlazorApplicationAssembliesCacheFile)">
246269

247270
<!--
@@ -251,10 +274,10 @@
251274
-->
252275
<ResolveBlazorRuntimeDependencies
253276
EntryPoint="@(IntermediateAssembly)"
254-
ApplicationDependencies="@(_BlazorDependencyInput)"
277+
ApplicationDependencies="@(_BlazorManagedRuntimeAssemby)"
255278
WebAssemblyBCLAssemblies="@(_WebAssemblyBCLAssembly)">
256279

257-
<Output TaskParameter="Dependencies" ItemName="_BlazorResolvedRuntimeDependencies" />
280+
<Output TaskParameter="Dependencies" ItemName="_BlazorResolvedAssembly" />
258281
</ResolveBlazorRuntimeDependencies>
259282

260283
<ItemGroup>
@@ -269,13 +292,12 @@
269292
Inputs="@(BlazorOutputWithTargetPath)"
270293
Outputs="$(BlazorBootJsonIntermediateOutputPath)">
271294
<ItemGroup>
272-
<_AppReferences Include="@(BlazorOutputWithTargetPath->WithMetadataValue('Extension','.dll'))" />
273-
<_AppReferences Include="@(BlazorOutputWithTargetPath->WithMetadataValue('Extension','.pdb'))" Condition="'$(BlazorEnableDebugging)' == 'true'" />
295+
<_BlazorRuntimeFile Include="@(BlazorOutputWithTargetPath->WithMetadataValue('BlazorRuntimeFile', 'true'))" />
274296
</ItemGroup>
275297

276298
<GenerateBlazorBootJson
277299
AssemblyPath="@(IntermediateAssembly)"
278-
References="@(_AppReferences)"
300+
References="@(_BlazorRuntimeFile)"
279301
LinkerEnabled="$(BlazorLinkOnBuild)"
280302
OutputPath="$(BlazorBootJsonIntermediateOutputPath)" />
281303

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)