Use a task to launch the linker (#17313)
* Use a task to launch the linker Fixes https://github.com/aspnet/AspNetCore/issues/17264
This commit is contained in:
parent
e470aead3e
commit
e862ce7cee
|
|
@ -0,0 +1,56 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Xml;
|
||||
using System.Xml.Linq;
|
||||
using Microsoft.Build.Framework;
|
||||
using Microsoft.Build.Utilities;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Build
|
||||
{
|
||||
// Based on https://github.com/mono/linker/blob/3b329b9481e300bcf4fb88a2eebf8cb5ef8b323b/src/ILLink.Tasks/CreateRootDescriptorFile.cs
|
||||
public class BlazorCreateRootDescriptorFile : Task
|
||||
{
|
||||
[Required]
|
||||
public ITaskItem[] AssemblyNames { get; set; }
|
||||
|
||||
[Required]
|
||||
public ITaskItem RootDescriptorFilePath { get; set; }
|
||||
|
||||
public override bool Execute()
|
||||
{
|
||||
using var fileStream = File.Create(RootDescriptorFilePath.ItemSpec);
|
||||
var assemblyNames = AssemblyNames.Select(a => a.ItemSpec);
|
||||
|
||||
WriteRootDescriptor(fileStream, assemblyNames);
|
||||
return true;
|
||||
}
|
||||
|
||||
internal static void WriteRootDescriptor(Stream stream, IEnumerable<string> assemblyNames)
|
||||
{
|
||||
var roots = new XElement("linker");
|
||||
foreach (var assemblyName in assemblyNames)
|
||||
{
|
||||
roots.Add(new XElement("assembly",
|
||||
new XAttribute("fullname", assemblyName),
|
||||
new XElement("type",
|
||||
new XAttribute("fullname", "*"),
|
||||
new XAttribute("required", "true"))));
|
||||
}
|
||||
|
||||
var xmlWriterSettings = new XmlWriterSettings
|
||||
{
|
||||
Indent = true,
|
||||
OmitXmlDeclaration = true
|
||||
};
|
||||
|
||||
using var writer = XmlWriter.Create(stream, xmlWriterSettings);
|
||||
var xDocument = new XDocument(roots);
|
||||
|
||||
xDocument.Save(writer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,189 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using Microsoft.Build.Framework;
|
||||
using Microsoft.Build.Utilities;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Build.Tasks
|
||||
{
|
||||
// Based on https://github.com/mono/linker/blob/3b329b9481e300bcf4fb88a2eebf8cb5ef8b323b/src/ILLink.Tasks/LinkTask.cs
|
||||
public class BlazorILLink : ToolTask
|
||||
{
|
||||
private const string DotNetHostPathEnvironmentName = "DOTNET_HOST_PATH";
|
||||
|
||||
[Required]
|
||||
public string ILLinkPath { get; set; }
|
||||
|
||||
[Required]
|
||||
public ITaskItem[] AssemblyPaths { get; set; }
|
||||
|
||||
public ITaskItem[] ReferenceAssemblyPaths { get; set; }
|
||||
|
||||
[Required]
|
||||
public ITaskItem[] RootAssemblyNames { get; set; }
|
||||
|
||||
[Required]
|
||||
public ITaskItem OutputDirectory { get; set; }
|
||||
|
||||
public ITaskItem[] RootDescriptorFiles { get; set; }
|
||||
|
||||
public bool ClearInitLocals { get; set; }
|
||||
|
||||
public string ClearInitLocalsAssemblies { get; set; }
|
||||
|
||||
public string ExtraArgs { get; set; }
|
||||
|
||||
public bool DumpDependencies { get; set; }
|
||||
|
||||
private string _dotnetPath;
|
||||
|
||||
private string DotNetPath
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!string.IsNullOrEmpty(_dotnetPath))
|
||||
{
|
||||
return _dotnetPath;
|
||||
}
|
||||
|
||||
_dotnetPath = Environment.GetEnvironmentVariable(DotNetHostPathEnvironmentName);
|
||||
if (string.IsNullOrEmpty(_dotnetPath))
|
||||
{
|
||||
throw new InvalidOperationException($"{DotNetHostPathEnvironmentName} is not set");
|
||||
}
|
||||
|
||||
return _dotnetPath;
|
||||
}
|
||||
}
|
||||
|
||||
protected override MessageImportance StandardErrorLoggingImportance => MessageImportance.High;
|
||||
|
||||
protected override string ToolName => Path.GetFileName(DotNetPath);
|
||||
|
||||
protected override string GenerateFullPathToTool() => DotNetPath;
|
||||
|
||||
protected override string GenerateCommandLineCommands() => ILLinkPath;
|
||||
|
||||
private static string Quote(string path)
|
||||
{
|
||||
return $"\"{path.TrimEnd('\\')}\"";
|
||||
}
|
||||
|
||||
protected override string GenerateResponseFileCommands()
|
||||
{
|
||||
var args = new StringBuilder();
|
||||
|
||||
if (RootDescriptorFiles != null)
|
||||
{
|
||||
foreach (var rootFile in RootDescriptorFiles)
|
||||
{
|
||||
args.Append("-x ").AppendLine(Quote(rootFile.ItemSpec));
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var assemblyItem in RootAssemblyNames)
|
||||
{
|
||||
args.Append("-a ").AppendLine(Quote(assemblyItem.ItemSpec));
|
||||
}
|
||||
|
||||
var assemblyNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var assembly in AssemblyPaths)
|
||||
{
|
||||
var assemblyPath = assembly.ItemSpec;
|
||||
var assemblyName = Path.GetFileNameWithoutExtension(assemblyPath);
|
||||
|
||||
// If there are multiple paths with the same assembly name, only use the first one.
|
||||
if (!assemblyNames.Add(assemblyName))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
args.Append("-reference ")
|
||||
.AppendLine(Quote(assemblyPath));
|
||||
|
||||
var action = assembly.GetMetadata("action");
|
||||
if ((action != null) && (action.Length > 0))
|
||||
{
|
||||
args.Append("-p ");
|
||||
args.Append(action);
|
||||
args.Append(" ").AppendLine(Quote(assemblyName));
|
||||
}
|
||||
}
|
||||
|
||||
if (ReferenceAssemblyPaths != null)
|
||||
{
|
||||
foreach (var assembly in ReferenceAssemblyPaths)
|
||||
{
|
||||
var assemblyPath = assembly.ItemSpec;
|
||||
var assemblyName = Path.GetFileNameWithoutExtension(assemblyPath);
|
||||
|
||||
// Don't process references for which we already have
|
||||
// implementation assemblies.
|
||||
if (assemblyNames.Contains(assemblyName))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
args.Append("-reference ").AppendLine(Quote(assemblyPath));
|
||||
|
||||
// Treat reference assemblies as "skip". Ideally we
|
||||
// would not even look at the IL, but only use them to
|
||||
// resolve surface area.
|
||||
args.Append("-p skip ").AppendLine(Quote(assemblyName));
|
||||
}
|
||||
}
|
||||
|
||||
if (OutputDirectory != null)
|
||||
{
|
||||
args.Append("-out ").AppendLine(Quote(OutputDirectory.ItemSpec));
|
||||
}
|
||||
|
||||
if (ClearInitLocals)
|
||||
{
|
||||
args.AppendLine("--enable-opt clearinitlocals");
|
||||
if ((ClearInitLocalsAssemblies != null) && (ClearInitLocalsAssemblies.Length > 0))
|
||||
{
|
||||
args.Append("-m ClearInitLocalsAssemblies ");
|
||||
args.AppendLine(ClearInitLocalsAssemblies);
|
||||
}
|
||||
}
|
||||
|
||||
if (ExtraArgs != null)
|
||||
{
|
||||
args.AppendLine(ExtraArgs);
|
||||
}
|
||||
|
||||
if (DumpDependencies)
|
||||
{
|
||||
args.AppendLine("--dump-dependencies");
|
||||
}
|
||||
|
||||
return args.ToString();
|
||||
}
|
||||
|
||||
protected override bool HandleTaskExecutionErrors()
|
||||
{
|
||||
// Show a slightly better error than the standard ToolTask message that says "dotnet" failed.
|
||||
Log.LogError($"ILLink failed with exited code {ExitCode}.");
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override void LogEventsFromTextOutput(string singleLine, MessageImportance messageImportance)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(singleLine) && singleLine.StartsWith("Unhandled exception.", StringComparison.Ordinal))
|
||||
{
|
||||
// The Mono linker currently prints out an entire stack trace when the linker fails.
|
||||
// We want to show something actionable in the VS Error window.
|
||||
Log.LogError(singleLine);
|
||||
}
|
||||
else
|
||||
{
|
||||
base.LogEventsFromTextOutput(singleLine, messageImportance);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -97,6 +97,13 @@
|
|||
|
||||
<ItemGroup>
|
||||
<_BlazorDependencyInput Include="@(ReferenceCopyLocalPaths->WithMetadataValue('Extension','.dll')->'%(FullPath)')" />
|
||||
|
||||
<_WebAssemblyBCLFolder Include="
|
||||
$(DotNetWebAssemblyBCLPath);
|
||||
$(DotNetWebAssemblyBCLFacadesPath);
|
||||
$(DotNetWebAssemblyFrameworkPath)" />
|
||||
|
||||
<_WebAssemblyBCLAssembly Include="%(_WebAssemblyBCLFolder.Identity)*.dll" />
|
||||
</ItemGroup>
|
||||
|
||||
<MakeDir Directories="$(BlazorIntermediateOutputPath)" />
|
||||
|
|
@ -119,7 +126,7 @@
|
|||
<Target
|
||||
Name="_ResolveBlazorOutputsWhenLinked"
|
||||
Condition="'$(BlazorLinkOnBuild)' == 'true'"
|
||||
DependsOnTargets="_GenerateLinkerDescriptor;_LinkBlazorApplication">
|
||||
DependsOnTargets="_GenerateBlazorLinkerDescriptor;_LinkBlazorApplication">
|
||||
|
||||
<!-- _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. -->
|
||||
<ReadLinesFromFile File="$(_BlazorLinkerOutputCache)">
|
||||
|
|
@ -133,36 +140,27 @@
|
|||
</ItemGroup>
|
||||
</Target>
|
||||
|
||||
<Target Name="_GenerateLinkerDescriptor"
|
||||
<UsingTask TaskName="BlazorCreateRootDescriptorFile" AssemblyFile="$(BlazorTasksPath)" />
|
||||
<Target Name="_GenerateBlazorLinkerDescriptor"
|
||||
Inputs="@(IntermediateAssembly)"
|
||||
Outputs="$(GeneratedBlazorLinkerDescriptor)"
|
||||
Condition="'@(BlazorLinkerDescriptor)' == ''">
|
||||
|
||||
<!-- Generate linker descriptors if the project doesn't explicitly provide one. -->
|
||||
|
||||
<ItemGroup>
|
||||
<_PrepareLinkerDescriptorAssemblyLine Include="@(IntermediateAssembly->'%(FileName)')" />
|
||||
<_GeneratedLinkerDescriptorLine Include="<linker>" />
|
||||
<_GeneratedLinkerDescriptorLine Include="@(_PrepareLinkerDescriptorAssemblyLine->'<assembly fullname="%(Identity)" />')" />
|
||||
<_GeneratedLinkerDescriptorLine Include="</linker>" />
|
||||
</ItemGroup>
|
||||
|
||||
<WriteLinesToFile
|
||||
Lines="@(_GeneratedLinkerDescriptorLine)"
|
||||
File="$(GeneratedBlazorLinkerDescriptor)"
|
||||
Overwrite="true"
|
||||
WriteOnlyWhenDifferent="True" />
|
||||
<BlazorCreateRootDescriptorFile
|
||||
AssemblyNames="@(IntermediateAssembly->'%(Filename)')"
|
||||
RootDescriptorFilePath="$(GeneratedBlazorLinkerDescriptor)" />
|
||||
|
||||
<ItemGroup>
|
||||
<FileWrites Include="$(GeneratedBlazorLinkerDescriptor)" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<BlazorLinkerDescriptor Include="$(_BlazorBuiltInBclLinkerDescriptor)" />
|
||||
<BlazorLinkerDescriptor Include="$(GeneratedBlazorLinkerDescriptor)" />
|
||||
<BlazorLinkerDescriptor Include="$(_BlazorBuiltInBclLinkerDescriptor)" />
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
|
||||
<UsingTask TaskName="BlazorILLink" AssemblyFile="$(BlazorTasksPath)" />
|
||||
|
||||
<Target
|
||||
Name="_LinkBlazorApplication"
|
||||
Inputs="$(ProjectAssetsFile);
|
||||
|
|
@ -173,39 +171,44 @@
|
|||
Outputs="$(_BlazorLinkerOutputCache)">
|
||||
|
||||
<ItemGroup>
|
||||
<_BlazorDependencyAssembly Include="@(_BlazorDependencyInput)">
|
||||
<RelativeDirNoTrailingSlash>$([System.String]::Copy('%(RelativeDir)').TrimEnd('\').TrimEnd('/'))</RelativeDirNoTrailingSlash>
|
||||
<IsLinkable Condition="$([System.String]::Copy('%(FileName)').StartsWith('System.'))">true</IsLinkable>
|
||||
</_BlazorDependencyAssembly>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<_WebAssemblyBCLFolder Include="$(DotNetWebAssemblyBCLPath);$(DotNetWebAssemblyBCLFacadesPath);$(DotNetWebAssemblyFrameworkPath)" />
|
||||
<_BlazorAssembliesToCopy Include="@(IntermediateAssembly->'-a "%(FullPath)"')" />
|
||||
<_BlazorFolderLookupPaths Include="@(_WebAssemblyBCLFolder->'-d "%(Identity)"')" />
|
||||
<_BlazorDependencyAssembly Include="@(_BlazorDependencyInput)" IsLinkable="$([System.String]::Copy('%(FileName)').StartsWith('System.'))" />
|
||||
|
||||
<!-- For linkable assemblies, add their directories as lookup paths -->
|
||||
<_BlazorFolderLookupPaths Condition="'%(_BlazorDependencyAssembly.IsLinkable)' == 'true'" Include="@(_BlazorDependencyAssembly->'-d "%(RelativeDirNoTrailingSlash)"')" />
|
||||
<_BlazorAssemblyToLink Include="@(_WebAssemblyBCLAssembly)" />
|
||||
<_BlazorAssemblyToLink Include="@(_BlazorDependencyAssembly)" Condition="'%(_BlazorDependencyAssembly.IsLinkable)' == 'true'" />
|
||||
|
||||
<!-- For non-linkable assemblies, reference the .dll directly -->
|
||||
<_BlazorAssembliesToCopy Condition="'%(_BlazorDependencyAssembly.IsLinkable)' != 'true'" Include="@(_BlazorDependencyAssembly->'-a "%(FullPath)"')" />
|
||||
|
||||
<_BlazorAssemblyDescriptorFiles
|
||||
Include="@(BlazorLinkerDescriptor->'-x "%(FullPath)"')" Condition="'@(BlazorLinkerDescriptor)' != ''" />
|
||||
<_BlazorLinkerRoot Include="@(IntermediateAssembly)" />
|
||||
<_BlazorLinkerRoot Include="@(_BlazorDependencyAssembly)" Condition="'%(_BlazorDependencyAssembly.IsLinkable)' != 'true'" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<_BlazorLinkerAdditionalOptions>-l $(MonoLinkerI18NAssemblies) $(AdditionalMonoLinkerOptions)</_BlazorLinkerAdditionalOptions>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Clear the contents of /obj/<<configuration>>/<<targetframework>>/blazor/linker -->
|
||||
<ItemGroup>
|
||||
<_OldLinkedFile Include="$(BlazorIntermediateLinkerOutputPath)*.dll" />
|
||||
</ItemGroup>
|
||||
|
||||
<Delete Files="@(_OldLinkedFile)" />
|
||||
|
||||
<!-- Run the linker and put the results in /obj/<<configuration>>/<<targetframework>>/blazor/blazor/linker -->
|
||||
<Exec Command="dotnet "$(MonoLinkerPath)" $(_BlazorLinkerAdditionalOptions) @(_BlazorFolderLookupPaths, ' ') -o "$(BlazorIntermediateLinkerOutputPath)" @(_BlazorAssemblyDescriptorFiles, ' ') @(_BlazorAssembliesToCopy, ' ')" />
|
||||
<!--
|
||||
When running from Desktop MSBuild, DOTNET_HOST_PATH is not set.
|
||||
In this case, explicitly specify the path to the dotnet host.
|
||||
-->
|
||||
<PropertyGroup Condition=" '$(DOTNET_HOST_PATH)' == '' ">
|
||||
<_DotNetHostDirectory>$(NetCoreRoot)</_DotNetHostDirectory>
|
||||
<_DotNetHostFileName>dotnet</_DotNetHostFileName>
|
||||
<_DotNetHostFileName Condition=" '$(OS)' == 'Windows_NT' ">dotnet.exe</_DotNetHostFileName>
|
||||
</PropertyGroup>
|
||||
|
||||
<BlazorILLink
|
||||
ILLinkPath="$(MonoLinkerPath)"
|
||||
AssemblyPaths="@(_BlazorAssemblyToLink)"
|
||||
RootAssemblyNames="@(_BlazorLinkerRoot)"
|
||||
RootDescriptorFiles="@(BlazorLinkerDescriptor)"
|
||||
OutputDirectory="$(BlazorIntermediateLinkerOutputPath)"
|
||||
ExtraArgs="$(_BlazorLinkerAdditionalOptions)"
|
||||
ToolExe="$(_DotNetHostFileName)"
|
||||
ToolPath="$(_DotNetHostDirectory)" />
|
||||
|
||||
<ItemGroup>
|
||||
<_LinkerResult Include="$(BlazorIntermediateLinkerOutputPath)*.dll" />
|
||||
|
|
@ -214,6 +217,7 @@
|
|||
<WriteLinesToFile File="$(_BlazorLinkerOutputCache)" Lines="@(_LinkerResult)" Overwrite="true" />
|
||||
</Target>
|
||||
|
||||
|
||||
<UsingTask TaskName="ResolveBlazorRuntimeDependencies" AssemblyFile="$(BlazorTasksPath)" />
|
||||
<Target
|
||||
Name="_ResolveBlazorOutputsWhenNotLinked"
|
||||
|
|
@ -242,13 +246,7 @@
|
|||
At this point we have decided not to run the linker and instead to just copy the assemblies
|
||||
from the BCL referenced by the app the nuget package into the _framework/_bin folder.
|
||||
The only thing we need to do here is collect the list of items that will go into _framework/_bin.
|
||||
-->
|
||||
|
||||
<ItemGroup>
|
||||
<_WebAssemblyBCLFolder Include="$(DotNetWebAssemblyBCLPath);$(DotNetWebAssemblyBCLFacadesPath);$(DotNetWebAssemblyFrameworkPath)" />
|
||||
<_WebAssemblyBCLAssembly Include="%(_WebAssemblyBCLFolder.Identity)*.dll" />
|
||||
</ItemGroup>
|
||||
|
||||
-->
|
||||
<ResolveBlazorRuntimeDependencies
|
||||
EntryPoint="@(IntermediateAssembly)"
|
||||
ApplicationDependencies="@(_BlazorDependencyInput)"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,38 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.IO;
|
||||
using System.Xml.Linq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Build
|
||||
{
|
||||
public class BlazorCreateRootDescriptorFileTest
|
||||
{
|
||||
[Fact]
|
||||
public void ProducesRootDescriptor()
|
||||
{
|
||||
// Arrange/Act
|
||||
using var stream = new MemoryStream();
|
||||
|
||||
// Act
|
||||
BlazorCreateRootDescriptorFile.WriteRootDescriptor(
|
||||
stream,
|
||||
new[] { "MyApp.dll" });
|
||||
|
||||
// Assert
|
||||
stream.Position = 0;
|
||||
var document = XDocument.Load(stream);
|
||||
var rootElement = document.Root;
|
||||
|
||||
var assemblyElement = Assert.Single(rootElement.Elements());
|
||||
Assert.Equal("assembly", assemblyElement.Name.ToString());
|
||||
Assert.Equal("MyApp.dll", assemblyElement.Attribute("fullname").Value);
|
||||
|
||||
var typeElement = Assert.Single(assemblyElement.Elements());
|
||||
Assert.Equal("type", typeElement.Name.ToString());
|
||||
Assert.Equal("*", typeElement.Attribute("fullname").Value);
|
||||
Assert.Equal("true", typeElement.Attribute("required").Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue