Skip to content

Commit 1ecb165

Browse files
committed
Use a task to launch the linker
1 parent d6c88a3 commit 1ecb165

File tree

4 files changed

+305
-44
lines changed

4 files changed

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

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

Lines changed: 42 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,13 @@
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" />
100107
</ItemGroup>
101108

102109
<MakeDir Directories="$(BlazorIntermediateOutputPath)" />
@@ -119,7 +126,7 @@
119126
<Target
120127
Name="_ResolveBlazorOutputsWhenLinked"
121128
Condition="'$(BlazorLinkOnBuild)' == 'true'"
122-
DependsOnTargets="_GenerateLinkerDescriptor;_LinkBlazorApplication">
129+
DependsOnTargets="_GenerateBlazorLinkerDescriptor;_LinkBlazorApplication">
123130

124131
<!-- _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. -->
125132
<ReadLinesFromFile File="$(_BlazorLinkerOutputCache)">
@@ -133,36 +140,27 @@
133140
</ItemGroup>
134141
</Target>
135142

136-
<Target Name="_GenerateLinkerDescriptor"
143+
<UsingTask TaskName="BlazorCreateRootDescriptorFile" AssemblyFile="$(BlazorTasksPath)" />
144+
<Target Name="_GenerateBlazorLinkerDescriptor"
137145
Inputs="@(IntermediateAssembly)"
138146
Outputs="$(GeneratedBlazorLinkerDescriptor)"
139147
Condition="'@(BlazorLinkerDescriptor)' == ''">
140148

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

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" />
151+
<BlazorCreateRootDescriptorFile
152+
AssemblyNames="@(IntermediateAssembly->'%(Filename)')"
153+
RootDescriptorFilePath="$(GeneratedBlazorLinkerDescriptor)" />
155154

156155
<ItemGroup>
157156
<FileWrites Include="$(GeneratedBlazorLinkerDescriptor)" />
158-
</ItemGroup>
159-
160-
<ItemGroup>
161-
<BlazorLinkerDescriptor Include="$(_BlazorBuiltInBclLinkerDescriptor)" />
162157
<BlazorLinkerDescriptor Include="$(GeneratedBlazorLinkerDescriptor)" />
158+
<BlazorLinkerDescriptor Include="$(_BlazorBuiltInBclLinkerDescriptor)" />
163159
</ItemGroup>
164160
</Target>
165161

162+
<UsingTask TaskName="BlazorILLink" AssemblyFile="$(BlazorTasksPath)" />
163+
166164
<Target
167165
Name="_LinkBlazorApplication"
168166
Inputs="$(ProjectAssetsFile);
@@ -173,39 +171,44 @@
173171
Outputs="$(_BlazorLinkerOutputCache)">
174172

175173
<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;')" />
174+
<_BlazorDependencyAssembly Include="@(_BlazorDependencyInput)" IsLinkable="$([System.String]::Copy('%(FileName)').StartsWith('System.'))" />
188175

189-
<!-- For non-linkable assemblies, reference the .dll directly -->
190-
<_BlazorAssembliesToCopy Condition="'%(_BlazorDependencyAssembly.IsLinkable)' != 'true'" Include="@(_BlazorDependencyAssembly->'-a &quot;%(FullPath)&quot;')" />
176+
<_BlazorAssemblyToLink Include="@(_WebAssemblyBCLAssembly)" />
177+
<_BlazorAssemblyToLink Include="@(_BlazorDependencyAssembly)" Condition="'%(_BlazorDependencyAssembly.IsLinkable)' == 'true'" />
191178

192-
<_BlazorAssemblyDescriptorFiles
193-
Include="@(BlazorLinkerDescriptor->'-x &quot;%(FullPath)&quot;')" Condition="'@(BlazorLinkerDescriptor)' != ''" />
179+
<_BlazorLinkerRoot Include="@(IntermediateAssembly)" />
180+
<_BlazorLinkerRoot Include="@(_BlazorDependencyAssembly)" Condition="'%(_BlazorDependencyAssembly.IsLinkable)' != 'true'" />
194181
</ItemGroup>
195182

196183
<PropertyGroup>
197184
<_BlazorLinkerAdditionalOptions>-l $(MonoLinkerI18NAssemblies) $(AdditionalMonoLinkerOptions)</_BlazorLinkerAdditionalOptions>
198185
</PropertyGroup>
199186

200-
<!-- Clear the contents of /obj/<<configuration>>/<<targetframework>>/blazor/linker -->
201187
<ItemGroup>
202188
<_OldLinkedFile Include="$(BlazorIntermediateLinkerOutputPath)*.dll" />
203189
</ItemGroup>
204190

205191
<Delete Files="@(_OldLinkedFile)" />
206192

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, ' ')" />
193+
<!--
194+
When running from Desktop MSBuild, DOTNET_HOST_PATH is not set.
195+
In this case, explicitly specify the path to the dotnet host.
196+
-->
197+
<PropertyGroup Condition=" '$(DOTNET_HOST_PATH)' == '' ">
198+
<_DotNetHostDirectory>$(NetCoreRoot)</_DotNetHostDirectory>
199+
<_DotNetHostFileName>dotnet</_DotNetHostFileName>
200+
<_DotNetHostFileName Condition=" '$(OS)' == 'Windows_NT' ">dotnet.exe</_DotNetHostFileName>
201+
</PropertyGroup>
202+
203+
<BlazorILLink
204+
ILLinkPath="$(MonoLinkerPath)"
205+
AssemblyPaths="@(_BlazorAssemblyToLink)"
206+
RootAssemblyNames="@(_BlazorLinkerRoot)"
207+
RootDescriptorFiles="@(BlazorLinkerDescriptor)"
208+
OutputDirectory="$(BlazorIntermediateLinkerOutputPath)"
209+
ExtraArgs="$(_BlazorLinkerAdditionalOptions)"
210+
ToolExe="$(_DotNetHostFileName)"
211+
ToolPath="$(_DotNetHostDirectory)" />
209212

210213
<ItemGroup>
211214
<_LinkerResult Include="$(BlazorIntermediateLinkerOutputPath)*.dll" />
@@ -214,6 +217,7 @@
214217
<WriteLinesToFile File="$(_BlazorLinkerOutputCache)" Lines="@(_LinkerResult)" Overwrite="true" />
215218
</Target>
216219

220+
217221
<UsingTask TaskName="ResolveBlazorRuntimeDependencies" AssemblyFile="$(BlazorTasksPath)" />
218222
<Target
219223
Name="_ResolveBlazorOutputsWhenNotLinked"
@@ -242,13 +246,7 @@
242246
At this point we have decided not to run the linker and instead to just copy the assemblies
243247
from the BCL referenced by the app the nuget package into the _framework/_bin folder.
244248
The only thing we need to do here is collect the list of items that will go into _framework/_bin.
245-
-->
246-
247-
<ItemGroup>
248-
<_WebAssemblyBCLFolder Include="$(DotNetWebAssemblyBCLPath);$(DotNetWebAssemblyBCLFacadesPath);$(DotNetWebAssemblyFrameworkPath)" />
249-
<_WebAssemblyBCLAssembly Include="%(_WebAssemblyBCLFolder.Identity)*.dll" />
250-
</ItemGroup>
251-
249+
-->
252250
<ResolveBlazorRuntimeDependencies
253251
EntryPoint="@(IntermediateAssembly)"
254252
ApplicationDependencies="@(_BlazorDependencyInput)"

0 commit comments

Comments
 (0)