Skip to content

Commit e862ce7

Browse files
authored
Use a task to launch the linker (#17313)
* Use a task to launch the linker Fixes #17264
1 parent e470aea commit e862ce7

File tree

4 files changed

+325
-44
lines changed

4 files changed

+325
-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: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
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+
protected override bool HandleTaskExecutionErrors()
169+
{
170+
// Show a slightly better error than the standard ToolTask message that says "dotnet" failed.
171+
Log.LogError($"ILLink failed with exited code {ExitCode}.");
172+
return false;
173+
}
174+
175+
protected override void LogEventsFromTextOutput(string singleLine, MessageImportance messageImportance)
176+
{
177+
if (!string.IsNullOrEmpty(singleLine) && singleLine.StartsWith("Unhandled exception.", StringComparison.Ordinal))
178+
{
179+
// The Mono linker currently prints out an entire stack trace when the linker fails.
180+
// We want to show something actionable in the VS Error window.
181+
Log.LogError(singleLine);
182+
}
183+
else
184+
{
185+
base.LogEventsFromTextOutput(singleLine, messageImportance);
186+
}
187+
}
188+
}
189+
}

0 commit comments

Comments
 (0)