diff --git a/src/Components/Blazor/Build/src/Tasks/BlazorILLink.cs b/src/Components/Blazor/Build/src/Tasks/BlazorILLink.cs index 5e0a86d384..d5dc22cde0 100644 --- a/src/Components/Blazor/Build/src/Tasks/BlazorILLink.cs +++ b/src/Components/Blazor/Build/src/Tasks/BlazorILLink.cs @@ -66,7 +66,12 @@ namespace Microsoft.AspNetCore.Blazor.Build.Tasks protected override string GenerateFullPathToTool() => DotNetPath; - protected override string GenerateCommandLineCommands() => ILLinkPath; + protected override string GenerateCommandLineCommands() + { + var args = new StringBuilder(); + args.Append(Quote(ILLinkPath)); + return args.ToString(); + } private static string Quote(string path) { diff --git a/src/Components/Blazor/Build/src/Tasks/GenerateBlazorBootJson.cs b/src/Components/Blazor/Build/src/Tasks/GenerateBlazorBootJson.cs index b383d183e4..1984de0a57 100644 --- a/src/Components/Blazor/Build/src/Tasks/GenerateBlazorBootJson.cs +++ b/src/Components/Blazor/Build/src/Tasks/GenerateBlazorBootJson.cs @@ -1,6 +1,7 @@ // 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.IO; using System.Linq; using System.Reflection; @@ -27,12 +28,23 @@ namespace Microsoft.AspNetCore.Blazor.Build public override bool Execute() { var entryAssemblyName = AssemblyName.GetAssemblyName(AssemblyPath).Name; - var assemblies = References.Select(c => Path.GetFileName(c.ItemSpec)).ToArray(); + var assemblies = References.Select(GetUriPath).OrderBy(c => c, StringComparer.Ordinal).ToArray(); using var fileStream = File.Create(OutputPath); WriteBootJson(fileStream, entryAssemblyName, assemblies, LinkerEnabled); return true; + + static string GetUriPath(ITaskItem item) + { + var outputPath = item.GetMetadata("RelativeOutputPath"); + if (string.IsNullOrEmpty(outputPath)) + { + outputPath = Path.GetFileName(item.ItemSpec); + } + + return outputPath.Replace('\\', '/'); + } } internal static void WriteBootJson(Stream stream, string entryAssemblyName, string[] assemblies, bool linkerEnabled) diff --git a/src/Components/Blazor/Build/src/targets/Blazor.MonoRuntime.targets b/src/Components/Blazor/Build/src/targets/Blazor.MonoRuntime.targets index cdbe7169a0..3c7d126561 100644 --- a/src/Components/Blazor/Build/src/targets/Blazor.MonoRuntime.targets +++ b/src/Components/Blazor/Build/src/targets/Blazor.MonoRuntime.targets @@ -74,7 +74,7 @@ - + $(IntermediateOutputPath)blazor\ @@ -82,6 +82,8 @@ $(BlazorIntermediateOutputPath)linker.descriptor.xml + <_TypeGranularityLinkerDescriptor>$(BlazorIntermediateOutputPath)linker.typegranularityconfig.xml + $(BlazorIntermediateOutputPath)linker/ @@ -94,8 +96,6 @@ - <_BlazorDependencyInput Include="@(ReferenceCopyLocalPaths->WithMetadataValue('Extension','.dll')->'%(FullPath)')" /> - <_WebAssemblyBCLFolder Include=" $(DotNetWebAssemblyBCLPath); $(DotNetWebAssemblyBCLFacadesPath); @@ -104,6 +104,22 @@ <_WebAssemblyBCLAssembly Include="%(_WebAssemblyBCLFolder.Identity)*.dll" /> + + + + <_BlazorManagedRuntimeAssemby Include="@(RuntimeCopyLocalItems)" /> + + + <_BlazorUserRuntimeAssembly Include="@(ReferencePath->WithMetadataValue('CopyLocal', 'true'))" /> + <_BlazorUserRuntimeAssembly Include="@(ReferenceDependencyPaths->WithMetadataValue('CopyLocal', 'true'))" /> + + <_BlazorManagedRuntimeAssemby Include="@(_BlazorUserRuntimeAssembly)" /> + <_BlazorManagedRuntimeAssemby Include="@(IntermediateAssembly)" /> + + @@ -111,6 +127,27 @@ + + + + <_BlazorCopyLocalPaths Include="@(ReferenceCopyLocalPaths)" /> + <_BlazorCopyLocalPaths Remove="@(_BlazorManagedRuntimeAssemby)" /> + + + true + $(BlazorRuntimeBinOutputPath)%(_BlazorCopyLocalPaths.DestinationSubDirectory)%(FileName)%(Extension) + %(_BlazorCopyLocalPaths.DestinationSubDirectory)%(FileName)%(Extension) + + + + true + $(BlazorRuntimeBinOutputPath)%(FileName)%(Extension) + %(FileName)%(Extension) + + - + + + - - $(BlazorRuntimeBinOutputPath)%(FileName)%(Extension) - + <_BlazorRuntimeCopyLocalItems Include="@(RuntimeCopyLocalItems)" /> + + + <_BlazorRuntimeCopyLocalItems IsLinkable="true" Condition="$([System.String]::Copy('%(Filename)').StartsWith('System.'))" /> + <_BlazorRuntimeCopyLocalItems IsLinkable="true" TypeGranularity="true" Condition="$([System.String]::Copy('%(Filename)').StartsWith('Microsoft.AspNetCore.'))" /> + <_BlazorRuntimeCopyLocalItems IsLinkable="true" TypeGranularity="true" Condition="$([System.String]::Copy('%(Filename)').StartsWith('Microsoft.Extensions.'))" /> + + <_BlazorAssemblyToLink Include="@(_WebAssemblyBCLAssembly)" /> + <_BlazorAssemblyToLink Include="@(_BlazorRuntimeCopyLocalItems)" Condition="'%(_BlazorRuntimeCopyLocalItems.IsLinkable)' == 'true'" /> + + <_BlazorLinkerRoot Include="@(IntermediateAssembly)" /> + <_BlazorLinkerRoot Include="@(_BlazorUserRuntimeAssembly)" /> + <_BlazorLinkerRoot Include="@(_BlazorRuntimeCopyLocalItems)" Condition="'%(_BlazorRuntimeCopyLocalItems.IsLinkable)' != 'true'" /> + @@ -157,31 +210,30 @@ - + + + + + + + + + + - - <_BlazorDependencyAssembly Include="@(_BlazorDependencyInput)" /> - <_BlazorDependencyAssembly IsLinkable="true" Condition="$([System.String]::Copy('%(Filename)').StartsWith('System.'))" /> - <_BlazorDependencyAssembly IsLinkable="true" TypeGranularity="true" Condition="$([System.String]::Copy('%(Filename)').StartsWith('Microsoft.AspNetCore.'))" /> - <_BlazorDependencyAssembly IsLinkable="true" TypeGranularity="true" Condition="$([System.String]::Copy('%(Filename)').StartsWith('Microsoft.Extensions.'))" /> - - <_BlazorAssemblyToLink Include="@(_WebAssemblyBCLAssembly)" /> - <_BlazorAssemblyToLink Include="@(_BlazorDependencyAssembly)" Condition="'%(_BlazorDependencyAssembly.IsLinkable)' == 'true'" /> - - <_BlazorLinkerRoot Include="@(IntermediateAssembly)" /> - <_BlazorLinkerRoot Include="@(_BlazorDependencyAssembly)" Condition="'%(_BlazorDependencyAssembly.IsLinkable)' != 'true'" /> - - <_BlazorLinkerAdditionalOptions>-l $(MonoLinkerI18NAssemblies) $(AdditionalMonoLinkerOptions) @@ -203,15 +255,6 @@ <_DotNetHostFileName Condition=" '$(OS)' == 'Windows_NT' ">dotnet.exe - - <_TypeGranularityLinkingConfig>$(BlazorIntermediateOutputPath)linker.typegranularityconfig.xml - - - - - - - - - - + + - - - - $(BlazorRuntimeBinOutputPath)%(FileName)%(Extension) - - - + @@ -282,13 +318,12 @@ Inputs="@(BlazorOutputWithTargetPath)" Outputs="$(BlazorBootJsonIntermediateOutputPath)"> - <_AppReferences Include="@(BlazorOutputWithTargetPath->WithMetadataValue('Extension','.dll'))" /> - <_AppReferences Include="@(BlazorOutputWithTargetPath->WithMetadataValue('Extension','.pdb'))" Condition="'$(BlazorEnableDebugging)' == 'true'" /> + <_BlazorRuntimeFile Include="@(BlazorOutputWithTargetPath->WithMetadataValue('BlazorRuntimeFile', 'true'))" /> diff --git a/src/Components/Blazor/Build/test/BuildIntegrationTests/BuildIntegrationTest.cs b/src/Components/Blazor/Build/test/BuildIntegrationTests/BuildIntegrationTest.cs index 1cc39f0d6e..027fe59990 100644 --- a/src/Components/Blazor/Build/test/BuildIntegrationTests/BuildIntegrationTest.cs +++ b/src/Components/Blazor/Build/test/BuildIntegrationTests/BuildIntegrationTest.cs @@ -70,5 +70,66 @@ namespace Microsoft.AspNetCore.Blazor.Build Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "standalone.dll"); Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "Microsoft.Extensions.Logging.Abstractions.dll"); // Verify dependencies are part of the output. } + + [Fact] + public async Task Build_SatelliteAssembliesAreCopiedToBuildOutput() + { + // Arrange + using var project = ProjectDirectory.Create("standalone", additionalProjects: new[] { "razorclasslibrary", "classlibrarywithsatelliteassemblies" }); + project.AddProjectFileContent( +@" + + $(DefineConstants);REFERENCE_classlibrarywithsatelliteassemblies + + + +"); + + var result = await MSBuildProcessManager.DotnetMSBuild(project, args: "/restore"); + + Assert.BuildPassed(result); + + var buildOutputDirectory = project.BuildOutputDirectory; + + Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "standalone.dll"); + Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "classlibrarywithsatelliteassemblies.dll"); + Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "Microsoft.CodeAnalysis.CSharp.dll"); + Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "fr", "Microsoft.CodeAnalysis.CSharp.resources.dll"); // Verify satellite assemblies are present in the build output. + + var bootJsonPath = Path.Combine(buildOutputDirectory, "dist", "_framework", "blazor.boot.json"); + Assert.FileContains(result, bootJsonPath, "\"Microsoft.CodeAnalysis.CSharp.dll\""); + Assert.FileContains(result, bootJsonPath, "\"fr\\/Microsoft.CodeAnalysis.CSharp.resources.dll\""); + } + + [Fact] + public async Task Build_WithBlazorLinkOnBuildFalse_SatelliteAssembliesAreCopiedToBuildOutput() + { + // Arrange + using var project = ProjectDirectory.Create("standalone", additionalProjects: new[] { "razorclasslibrary", "classlibrarywithsatelliteassemblies" }); + project.AddProjectFileContent( +@" + + false + $(DefineConstants);REFERENCE_classlibrarywithsatelliteassemblies + + + +"); + + var result = await MSBuildProcessManager.DotnetMSBuild(project, args: "/restore"); + + Assert.BuildPassed(result); + + var buildOutputDirectory = project.BuildOutputDirectory; + + Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "standalone.dll"); + Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "classlibrarywithsatelliteassemblies.dll"); + Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "Microsoft.CodeAnalysis.CSharp.dll"); + Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "fr", "Microsoft.CodeAnalysis.CSharp.resources.dll"); // Verify satellite assemblies are present in the build output. + + var bootJsonPath = Path.Combine(buildOutputDirectory, "dist", "_framework", "blazor.boot.json"); + Assert.FileContains(result, bootJsonPath, "\"Microsoft.CodeAnalysis.CSharp.dll\""); + Assert.FileContains(result, bootJsonPath, "\"fr\\/Microsoft.CodeAnalysis.CSharp.resources.dll\""); + } } } diff --git a/src/Components/Blazor/Build/test/BuildIntegrationTests/PublishIntegrationTest.cs b/src/Components/Blazor/Build/test/BuildIntegrationTests/PublishIntegrationTest.cs index 6995ffde4d..f1f554d3b9 100644 --- a/src/Components/Blazor/Build/test/BuildIntegrationTests/PublishIntegrationTest.cs +++ b/src/Components/Blazor/Build/test/BuildIntegrationTests/PublishIntegrationTest.cs @@ -112,6 +112,35 @@ namespace Microsoft.AspNetCore.Blazor.Build Assert.FileExists(result, publishDirectory, "web.config"); } + [Fact] + public async Task Publish_SatelliteAssemblies_AreCopiedToBuildOutput() + { + // Arrange + using var project = ProjectDirectory.Create("standalone", additionalProjects: new[] { "razorclasslibrary", "classlibrarywithsatelliteassemblies" }); + project.AddProjectFileContent( +@" + + $(DefineConstants);REFERENCE_classlibrarywithsatelliteassemblies + + + +"); + + var result = await MSBuildProcessManager.DotnetMSBuild(project, "Publish", args: "/restore"); + + Assert.BuildPassed(result); + + var publishDirectory = project.PublishOutputDirectory; + var blazorPublishDirectory = Path.Combine(publishDirectory, Path.GetFileNameWithoutExtension(project.ProjectFilePath)); + + Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "_bin", "Microsoft.CodeAnalysis.CSharp.dll"); + Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "_bin", "fr", "Microsoft.CodeAnalysis.CSharp.resources.dll"); // Verify satellite assemblies are present in the build output. + + var bootJsonPath = Path.Combine(blazorPublishDirectory, "dist", "_framework", "blazor.boot.json"); + Assert.FileContains(result, bootJsonPath, "\"Microsoft.CodeAnalysis.CSharp.dll\""); + Assert.FileContains(result, bootJsonPath, "\"fr\\/Microsoft.CodeAnalysis.CSharp.resources.dll\""); + } + [Fact] public async Task Publish_HostedApp_Works() { diff --git a/src/Components/Blazor/Build/testassets/classlibrarywithsatelliteassemblies/Class1.cs b/src/Components/Blazor/Build/testassets/classlibrarywithsatelliteassemblies/Class1.cs new file mode 100644 index 0000000000..944699cdb3 --- /dev/null +++ b/src/Components/Blazor/Build/testassets/classlibrarywithsatelliteassemblies/Class1.cs @@ -0,0 +1,12 @@ +using System; + +namespace classlibrarywithsatelliteassemblies +{ + public class Class1 + { + public static void Test() + { + GC.KeepAlive(typeof(Microsoft.CodeAnalysis.CSharp.CSharpCompilation)); + } + } +} \ No newline at end of file diff --git a/src/Components/Blazor/Build/testassets/classlibrarywithsatelliteassemblies/classlibrarywithsatelliteassemblies.csproj b/src/Components/Blazor/Build/testassets/classlibrarywithsatelliteassemblies/classlibrarywithsatelliteassemblies.csproj new file mode 100644 index 0000000000..7081842748 --- /dev/null +++ b/src/Components/Blazor/Build/testassets/classlibrarywithsatelliteassemblies/classlibrarywithsatelliteassemblies.csproj @@ -0,0 +1,13 @@ + + + + netstandard2.1 + 3.0 + + + + + + + + diff --git a/src/Components/Blazor/Build/testassets/standalone/Program.cs b/src/Components/Blazor/Build/testassets/standalone/Program.cs index 16bfae7e43..3e46e63316 100644 --- a/src/Components/Blazor/Build/testassets/standalone/Program.cs +++ b/src/Components/Blazor/Build/testassets/standalone/Program.cs @@ -1,10 +1,14 @@ - +using System; + namespace standalone { public class Program { public static void Main(string[] args) { +#if REFERENCE_classlibrarywithsatelliteassemblies + GC.KeepAlive(typeof(classlibrarywithsatelliteassemblies.Class1)); +#endif } } }