Skip to content

Commit c358107

Browse files
committed
Use a task to launch the linker
1 parent 755ebac commit c358107

File tree

3 files changed

+266
-42
lines changed

3 files changed

+266
-42
lines changed
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System.Collections.Generic;
5+
using System.IO;
6+
using System.Linq;
7+
using System.Xml;
8+
using System.Xml.Linq;
9+
using Microsoft.Build.Framework;
10+
using Microsoft.Build.Utilities;
11+
12+
namespace Microsoft.AspNetCore.Blazor.Build
13+
{
14+
// Based on https://github.com/mono/linker/blob/3b329b9481e300bcf4fb88a2eebf8cb5ef8b323b/src/ILLink.Tasks/CreateRootDescriptorFile.cs
15+
public class BlazorCreateRootDescriptorFile : Task
16+
{
17+
[Required]
18+
public ITaskItem[] AssemblyNames { get; set; }
19+
20+
[Required]
21+
public ITaskItem RootDescriptorFilePath { get; set; }
22+
23+
public override bool Execute()
24+
{
25+
using var fileStream = File.Create(RootDescriptorFilePath.ItemSpec);
26+
var assemblyNames = AssemblyNames.Select(a => a.ItemSpec);
27+
28+
WriteRootDescriptor(fileStream, assemblyNames);
29+
return true;
30+
}
31+
32+
internal static void WriteRootDescriptor(FileStream stream, IEnumerable<string> assemblyNames)
33+
{
34+
var roots = new XElement("linker");
35+
foreach (var assemblyName in assemblyNames)
36+
{
37+
roots.Add(new XElement("assembly",
38+
new XAttribute("fullname", assemblyName),
39+
new XElement("type",
40+
new XAttribute("fullname", "*"),
41+
new XAttribute("required", "true"))));
42+
}
43+
44+
var xmlWriterSettings = new XmlWriterSettings
45+
{
46+
Indent = true,
47+
OmitXmlDeclaration = true
48+
};
49+
50+
var writer = XmlWriter.Create(stream, xmlWriterSettings);
51+
var xDocument = new XDocument(roots);
52+
53+
xDocument.Save(writer);
54+
}
55+
}
56+
}
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.IO;
7+
using System.Text;
8+
using Microsoft.Build.Framework;
9+
using Microsoft.Build.Utilities;
10+
11+
namespace Microsoft.AspNetCore.Blazor.Build.Tasks
12+
{
13+
// Based on https://github.com/mono/linker/blob/3b329b9481e300bcf4fb88a2eebf8cb5ef8b323b/src/ILLink.Tasks/LinkTask.cs
14+
public class BlazorILLink : ToolTask
15+
{
16+
private const string DotNetHostPathEnvironmentName = "DOTNET_HOST_PATH";
17+
18+
[Required]
19+
public string ILLinkPath { get; set; }
20+
21+
[Required]
22+
public ITaskItem[] AssemblyPaths { get; set; }
23+
24+
public ITaskItem[] ReferenceAssemblyPaths { get; set; }
25+
26+
[Required]
27+
public ITaskItem[] RootAssemblyNames { get; set; }
28+
29+
[Required]
30+
public ITaskItem OutputDirectory { get; set; }
31+
32+
public ITaskItem[] RootDescriptorFiles { get; set; }
33+
34+
public bool ClearInitLocals { get; set; }
35+
36+
public string ClearInitLocalsAssemblies { get; set; }
37+
38+
public string ExtraArgs { get; set; }
39+
40+
public bool DumpDependencies { get; set; }
41+
42+
private string _dotnetPath;
43+
44+
private string DotNetPath
45+
{
46+
get
47+
{
48+
if (!string.IsNullOrEmpty(_dotnetPath))
49+
{
50+
return _dotnetPath;
51+
}
52+
53+
_dotnetPath = Environment.GetEnvironmentVariable(DotNetHostPathEnvironmentName);
54+
if (string.IsNullOrEmpty(_dotnetPath))
55+
{
56+
throw new InvalidOperationException($"{DotNetHostPathEnvironmentName} is not set");
57+
}
58+
59+
return _dotnetPath;
60+
}
61+
}
62+
63+
protected override MessageImportance StandardErrorLoggingImportance => MessageImportance.High;
64+
65+
protected override string ToolName => Path.GetFileName(DotNetPath);
66+
67+
protected override string GenerateFullPathToTool() => DotNetPath;
68+
69+
protected override string GenerateCommandLineCommands() => ILLinkPath;
70+
71+
private static string Quote(string path)
72+
{
73+
return $"\"{path.TrimEnd('\\')}\"";
74+
}
75+
76+
protected override string GenerateResponseFileCommands()
77+
{
78+
var args = new StringBuilder();
79+
80+
if (RootDescriptorFiles != null)
81+
{
82+
foreach (var rootFile in RootDescriptorFiles)
83+
{
84+
args.Append("-x ").AppendLine(Quote(rootFile.ItemSpec));
85+
}
86+
}
87+
88+
foreach (var assemblyItem in RootAssemblyNames)
89+
{
90+
args.Append("-a ").AppendLine(Quote(assemblyItem.ItemSpec));
91+
}
92+
93+
var assemblyNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
94+
foreach (var assembly in AssemblyPaths)
95+
{
96+
var assemblyPath = assembly.ItemSpec;
97+
var assemblyName = Path.GetFileNameWithoutExtension(assemblyPath);
98+
99+
// If there are multiple paths with the same assembly name, only use the first one.
100+
if (!assemblyNames.Add(assemblyName))
101+
{
102+
continue;
103+
}
104+
105+
args.Append("-reference ")
106+
.AppendLine(Quote(assemblyPath));
107+
108+
var action = assembly.GetMetadata("action");
109+
if ((action != null) && (action.Length > 0))
110+
{
111+
args.Append("-p ");
112+
args.Append(action);
113+
args.Append(" ").AppendLine(Quote(assemblyName));
114+
}
115+
}
116+
117+
if (ReferenceAssemblyPaths != null)
118+
{
119+
foreach (var assembly in ReferenceAssemblyPaths)
120+
{
121+
var assemblyPath = assembly.ItemSpec;
122+
var assemblyName = Path.GetFileNameWithoutExtension(assemblyPath);
123+
124+
// Don't process references for which we already have
125+
// implementation assemblies.
126+
if (assemblyNames.Contains(assemblyName))
127+
{
128+
continue;
129+
}
130+
131+
args.Append("-reference ").AppendLine(Quote(assemblyPath));
132+
133+
// Treat reference assemblies as "skip". Ideally we
134+
// would not even look at the IL, but only use them to
135+
// resolve surface area.
136+
args.Append("-p skip ").AppendLine(Quote(assemblyName));
137+
}
138+
}
139+
140+
if (OutputDirectory != null)
141+
{
142+
args.Append("-out ").AppendLine(Quote(OutputDirectory.ItemSpec));
143+
}
144+
145+
if (ClearInitLocals)
146+
{
147+
args.AppendLine("--enable-opt clearinitlocals");
148+
if ((ClearInitLocalsAssemblies != null) && (ClearInitLocalsAssemblies.Length > 0))
149+
{
150+
args.Append("-m ClearInitLocalsAssemblies ");
151+
args.AppendLine(ClearInitLocalsAssemblies);
152+
}
153+
}
154+
155+
if (ExtraArgs != null)
156+
{
157+
args.AppendLine(ExtraArgs);
158+
}
159+
160+
if (DumpDependencies)
161+
{
162+
args.AppendLine("--dump-dependencies");
163+
}
164+
165+
return args.ToString();
166+
}
167+
}
168+
}

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

Lines changed: 42 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,17 @@
9797

9898
<ItemGroup>
9999
<_BlazorDependencyInput Include="@(ReferenceCopyLocalPaths->WithMetadataValue('Extension','.dll')->'%(FullPath)')" />
100+
101+
<_WebAssemblyBCLFolder Include="
102+
$(DotNetWebAssemblyBCLPath);
103+
$(DotNetWebAssemblyBCLFacadesPath);
104+
$(DotNetWebAssemblyFrameworkPath)" />
105+
106+
<_WebAssemblyBCLAssembly Include="%(_WebAssemblyBCLFolder.Identity)*.dll" />
107+
</ItemGroup>
108+
109+
<ItemGroup>
110+
<BlazorLinkerDescriptor Include="$(_BlazorBuiltInBclLinkerDescriptor)" />
100111
</ItemGroup>
101112

102113
<MakeDir Directories="$(BlazorIntermediateOutputPath)" />
@@ -133,36 +144,25 @@
133144
</ItemGroup>
134145
</Target>
135146

147+
<UsingTask TaskName="BlazorCreateRootDescriptorFile" AssemblyFile="$(BlazorTasksPath)" />
136148
<Target Name="_GenerateLinkerDescriptor"
137149
Inputs="@(IntermediateAssembly)"
138150
Outputs="$(GeneratedBlazorLinkerDescriptor)"
139151
Condition="'@(BlazorLinkerDescriptor)' == ''">
140152

141153
<!-- Generate linker descriptors if the project doesn't explicitly provide one. -->
142154

143-
<ItemGroup>
144-
<_PrepareLinkerDescriptorAssemblyLine Include="@(IntermediateAssembly->'%(FileName)')" />
145-
<_GeneratedLinkerDescriptorLine Include="&lt;linker&gt;" />
146-
<_GeneratedLinkerDescriptorLine Include="@(_PrepareLinkerDescriptorAssemblyLine->'&lt;assembly fullname=&quot;%(Identity)&quot; /&gt;')" />
147-
<_GeneratedLinkerDescriptorLine Include="&lt;/linker&gt;" />
148-
</ItemGroup>
149-
150-
<WriteLinesToFile
151-
Lines="@(_GeneratedLinkerDescriptorLine)"
152-
File="$(GeneratedBlazorLinkerDescriptor)"
153-
Overwrite="true"
154-
WriteOnlyWhenDifferent="True" />
155+
<BlazorCreateRootDescriptorFile
156+
AssemblyNames="@(IntermediateAssembly->'%(Filename)')"
157+
RootDescriptorFilePath="$(GeneratedBlazorLinkerDescriptor)" />
155158

156159
<ItemGroup>
157160
<FileWrites Include="$(GeneratedBlazorLinkerDescriptor)" />
158-
</ItemGroup>
159-
160-
<ItemGroup>
161-
<BlazorLinkerDescriptor Include="$(_BlazorBuiltInBclLinkerDescriptor)" />
162161
<BlazorLinkerDescriptor Include="$(GeneratedBlazorLinkerDescriptor)" />
163162
</ItemGroup>
164163
</Target>
165164

165+
<UsingTask TaskName="BlazorILLink" AssemblyFile="$(BlazorTasksPath)" />
166166
<Target
167167
Name="_LinkBlazorApplication"
168168
Inputs="$(ProjectAssetsFile);
@@ -173,24 +173,15 @@
173173
Outputs="$(_BlazorLinkerOutputCache)">
174174

175175
<ItemGroup>
176-
<_BlazorDependencyAssembly Include="@(_BlazorDependencyInput)">
177-
<RelativeDirNoTrailingSlash>$([System.String]::Copy('%(RelativeDir)').TrimEnd('\').TrimEnd('/'))</RelativeDirNoTrailingSlash>
178-
<IsLinkable Condition="$([System.String]::Copy('%(FileName)').StartsWith('System.'))">true</IsLinkable>
179-
</_BlazorDependencyAssembly>
180-
</ItemGroup>
181-
<ItemGroup>
182-
<_WebAssemblyBCLFolder Include="$(DotNetWebAssemblyBCLPath);$(DotNetWebAssemblyBCLFacadesPath);$(DotNetWebAssemblyFrameworkPath)" />
183-
<_BlazorAssembliesToCopy Include="@(IntermediateAssembly->'-a &quot;%(FullPath)&quot;')" />
184-
<_BlazorFolderLookupPaths Include="@(_WebAssemblyBCLFolder->'-d &quot;%(Identity)&quot;')" />
185-
186-
<!-- For linkable assemblies, add their directories as lookup paths -->
187-
<_BlazorFolderLookupPaths Condition="'%(_BlazorDependencyAssembly.IsLinkable)' == 'true'" Include="@(_BlazorDependencyAssembly->'-d &quot;%(RelativeDirNoTrailingSlash)&quot;')" />
188-
189-
<!-- For non-linkable assemblies, reference the .dll directly -->
190-
<_BlazorAssembliesToCopy Condition="'%(_BlazorDependencyAssembly.IsLinkable)' != 'true'" Include="@(_BlazorDependencyAssembly->'-a &quot;%(FullPath)&quot;')" />
191-
192-
<_BlazorAssemblyDescriptorFiles
193-
Include="@(BlazorLinkerDescriptor->'-x &quot;%(FullPath)&quot;')" Condition="'@(BlazorLinkerDescriptor)' != ''" />
176+
<!--
177+
Previously we limited the linker to run exclusively on assemblies found in the WASM BCL. However, this misses
178+
out on some assemblies that aren't part of the the BCL but the linker has innate understanding of, and can operate on.
179+
Consequently we tell the linker to consider any assembly that starts with System. for linking.
180+
-->
181+
<_BlazorAssemblyToLink Include="@(_BlazorDependencyInput)" Condition="$([System.String]::Copy('%(_BlazorDependencyInput.FileName)').StartsWith('System.'))" />
182+
<_BlazorAssemblyToLink Include="@(_WebAssemblyBCLAssembly)" />
183+
<_BlazorLinkerRoot Include="@(_BlazorDependencyInput)" Exclude="@(_BlazorAssemblyToLink)" />
184+
<_BlazorLinkerRoot Include="@(IntermediateAssembly)" />
194185
</ItemGroup>
195186

196187
<PropertyGroup>
@@ -202,10 +193,25 @@
202193
<_OldLinkedFile Include="$(BlazorIntermediateLinkerOutputPath)*.dll" />
203194
</ItemGroup>
204195

196+
<!-- When running from Desktop MSBuild, DOTNET_HOST_PATH is not set.
197+
In this case, explicitly specify the path to the dotnet host. -->
198+
<PropertyGroup Condition=" '$(DOTNET_HOST_PATH)' == '' ">
199+
<_DotNetHostDirectory>$(NetCoreRoot)</_DotNetHostDirectory>
200+
<_DotNetHostFileName>dotnet</_DotNetHostFileName>
201+
<_DotNetHostFileName Condition=" '$(OS)' == 'Windows_NT' ">dotnet.exe</_DotNetHostFileName>
202+
</PropertyGroup>
203+
205204
<Delete Files="@(_OldLinkedFile)" />
206205

207-
<!-- Run the linker and put the results in /obj/<<configuration>>/<<targetframework>>/blazor/blazor/linker -->
208-
<Exec Command="dotnet &quot;$(MonoLinkerPath)&quot; $(_BlazorLinkerAdditionalOptions) @(_BlazorFolderLookupPaths, ' ') -o &quot;$(BlazorIntermediateLinkerOutputPath)&quot; @(_BlazorAssemblyDescriptorFiles, ' ') @(_BlazorAssembliesToCopy, ' ')" />
206+
<BlazorILLink
207+
ILLinkPath="$(MonoLinkerPath)"
208+
AssemblyPaths="@(_BlazorAssemblyToLink)"
209+
RootAssemblyNames="@(_BlazorLinkerRoot)"
210+
RootDescriptorFiles="@(BlazorLinkerDescriptor)"
211+
OutputDirectory="$(BlazorIntermediateLinkerOutputPath)"
212+
ExtraArgs="$(_BlazorLinkerAdditionalOptions)"
213+
ToolExe="$(_DotNetHostFileName)"
214+
ToolPath="$(_DotNetHostDirectory)" />
209215

210216
<ItemGroup>
211217
<_LinkerResult Include="$(BlazorIntermediateLinkerOutputPath)*.dll" />
@@ -243,12 +249,6 @@
243249
from the BCL referenced by the app the nuget package into the _framework/_bin folder.
244250
The only thing we need to do here is collect the list of items that will go into _framework/_bin.
245251
-->
246-
247-
<ItemGroup>
248-
<_WebAssemblyBCLFolder Include="$(DotNetWebAssemblyBCLPath);$(DotNetWebAssemblyBCLFacadesPath);$(DotNetWebAssemblyFrameworkPath)" />
249-
<_WebAssemblyBCLAssembly Include="%(_WebAssemblyBCLFolder.Identity)*.dll" />
250-
</ItemGroup>
251-
252252
<ResolveBlazorRuntimeDependencies
253253
EntryPoint="@(IntermediateAssembly)"
254254
ApplicationDependencies="@(_BlazorDependencyInput)"

0 commit comments

Comments
 (0)