From e862ce7cee740dc4c1163b9629cfdffbabc34bcc Mon Sep 17 00:00:00 2001 From: Pranav K Date: Mon, 25 Nov 2019 10:09:06 -0800 Subject: [PATCH] Use a task to launch the linker (#17313) * Use a task to launch the linker Fixes https://github.com/aspnet/AspNetCore/issues/17264 --- .../Tasks/BlazorCreateRootDescriptorFile.cs | 56 ++++++ .../Blazor/Build/src/Tasks/BlazorILLink.cs | 189 ++++++++++++++++++ .../src/targets/Blazor.MonoRuntime.targets | 86 ++++---- .../BlazorCreateRootDescriptorFileTest.cs | 38 ++++ 4 files changed, 325 insertions(+), 44 deletions(-) create mode 100644 src/Components/Blazor/Build/src/Tasks/BlazorCreateRootDescriptorFile.cs create mode 100644 src/Components/Blazor/Build/src/Tasks/BlazorILLink.cs create mode 100644 src/Components/Blazor/Build/test/BlazorCreateRootDescriptorFileTest.cs diff --git a/src/Components/Blazor/Build/src/Tasks/BlazorCreateRootDescriptorFile.cs b/src/Components/Blazor/Build/src/Tasks/BlazorCreateRootDescriptorFile.cs new file mode 100644 index 0000000000..1aa8536856 --- /dev/null +++ b/src/Components/Blazor/Build/src/Tasks/BlazorCreateRootDescriptorFile.cs @@ -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 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); + } + } +} diff --git a/src/Components/Blazor/Build/src/Tasks/BlazorILLink.cs b/src/Components/Blazor/Build/src/Tasks/BlazorILLink.cs new file mode 100644 index 0000000000..527f302a46 --- /dev/null +++ b/src/Components/Blazor/Build/src/Tasks/BlazorILLink.cs @@ -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(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); + } + } + } +} diff --git a/src/Components/Blazor/Build/src/targets/Blazor.MonoRuntime.targets b/src/Components/Blazor/Build/src/targets/Blazor.MonoRuntime.targets index 26cd58d0c1..beeb6cfccc 100644 --- a/src/Components/Blazor/Build/src/targets/Blazor.MonoRuntime.targets +++ b/src/Components/Blazor/Build/src/targets/Blazor.MonoRuntime.targets @@ -97,6 +97,13 @@ <_BlazorDependencyInput Include="@(ReferenceCopyLocalPaths->WithMetadataValue('Extension','.dll')->'%(FullPath)')" /> + + <_WebAssemblyBCLFolder Include=" + $(DotNetWebAssemblyBCLPath); + $(DotNetWebAssemblyBCLFacadesPath); + $(DotNetWebAssemblyFrameworkPath)" /> + + <_WebAssemblyBCLAssembly Include="%(_WebAssemblyBCLFolder.Identity)*.dll" /> @@ -119,7 +126,7 @@ + DependsOnTargets="_GenerateBlazorLinkerDescriptor;_LinkBlazorApplication"> @@ -133,36 +140,27 @@ - + - - <_PrepareLinkerDescriptorAssemblyLine Include="@(IntermediateAssembly->'%(FileName)')" /> - <_GeneratedLinkerDescriptorLine Include="<linker>" /> - <_GeneratedLinkerDescriptorLine Include="@(_PrepareLinkerDescriptorAssemblyLine->'<assembly fullname="%(Identity)" />')" /> - <_GeneratedLinkerDescriptorLine Include="</linker>" /> - - - + - - - - + + + - <_BlazorDependencyAssembly Include="@(_BlazorDependencyInput)"> - $([System.String]::Copy('%(RelativeDir)').TrimEnd('\').TrimEnd('/')) - true - - - - <_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.'))" /> - - <_BlazorFolderLookupPaths Condition="'%(_BlazorDependencyAssembly.IsLinkable)' == 'true'" Include="@(_BlazorDependencyAssembly->'-d "%(RelativeDirNoTrailingSlash)"')" /> + <_BlazorAssemblyToLink Include="@(_WebAssemblyBCLAssembly)" /> + <_BlazorAssemblyToLink Include="@(_BlazorDependencyAssembly)" Condition="'%(_BlazorDependencyAssembly.IsLinkable)' == 'true'" /> - - <_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'" /> <_BlazorLinkerAdditionalOptions>-l $(MonoLinkerI18NAssemblies) $(AdditionalMonoLinkerOptions) - <_OldLinkedFile Include="$(BlazorIntermediateLinkerOutputPath)*.dll" /> - - + + + <_DotNetHostDirectory>$(NetCoreRoot) + <_DotNetHostFileName>dotnet + <_DotNetHostFileName Condition=" '$(OS)' == 'Windows_NT' ">dotnet.exe + + + <_LinkerResult Include="$(BlazorIntermediateLinkerOutputPath)*.dll" /> @@ -214,6 +217,7 @@ + - - - <_WebAssemblyBCLFolder Include="$(DotNetWebAssemblyBCLPath);$(DotNetWebAssemblyBCLFacadesPath);$(DotNetWebAssemblyFrameworkPath)" /> - <_WebAssemblyBCLAssembly Include="%(_WebAssemblyBCLFolder.Identity)*.dll" /> - - + -->