More cleaning up Blazor.Build package (#17157)

* More cleaning up Blazor.Build package

* Clean up MonoRuntime targets
* Convert executables in to tasks
* Add tests
This commit is contained in:
Pranav K 2019-11-22 09:54:20 -08:00 committed by GitHub
parent a42a40d487
commit d6c88a36ff
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 2213 additions and 799 deletions

View File

@ -34,6 +34,7 @@
$(RepoRoot)src\Installers\**\*.*proj;
$(RepoRoot)src\SignalR\clients\ts\**\node_modules\**\*.*proj;
$(RepoRoot)src\Components\Web.JS\node_modules\**\*.*proj;
$(RepoRoot)src\Components\Blazor\Build\testassets\**\*.*proj;
$(RepoRoot)src\Components\Blazor\Templates\src\content\**\*.*proj;
$(RepoRoot)src\ProjectTemplates\Web.ProjectTemplates\content\**\*.csproj;
$(RepoRoot)src\ProjectTemplates\Web.ProjectTemplates\content\**\*.fsproj;

View File

@ -1,61 +0,0 @@
// 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 Microsoft.Extensions.CommandLineUtils;
namespace Microsoft.AspNetCore.Blazor.Build.DevServer.Commands
{
class ResolveRuntimeDependenciesCommand
{
public static void Command(CommandLineApplication command)
{
var referencesFile = command.Option("--references",
"The path to a file that lists the paths to given referenced dll files",
CommandOptionType.SingleValue);
var baseClassLibrary = command.Option("--base-class-library",
"Full path to a directory in which BCL assemblies can be found",
CommandOptionType.MultipleValue);
var outputPath = command.Option("--output",
"Path to the output file that will contain the list with the full paths of the resolved assemblies",
CommandOptionType.SingleValue);
var mainAssemblyPath = command.Argument("assembly",
"Path to the assembly containing the entry point of the application.");
command.OnExecute(() =>
{
if (string.IsNullOrEmpty(mainAssemblyPath.Value) ||
!baseClassLibrary.HasValue() || !outputPath.HasValue())
{
command.ShowHelp(command.Name);
return 1;
}
try
{
var referencesSources = referencesFile.HasValue()
? File.ReadAllLines(referencesFile.Value())
: Array.Empty<string>();
RuntimeDependenciesResolver.ResolveRuntimeDependencies(
mainAssemblyPath.Value,
referencesSources,
baseClassLibrary.Values.ToArray(),
outputPath.Value());
return 0;
}
catch (Exception ex)
{
Console.WriteLine($"ERROR: {ex.Message}");
Console.WriteLine(ex.StackTrace);
return 1;
}
});
}
}
}

View File

@ -1,59 +0,0 @@
// 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 Microsoft.Extensions.CommandLineUtils;
using System;
using System.IO;
namespace Microsoft.AspNetCore.Blazor.Build.DevServer.Commands
{
internal class WriteBootJsonCommand
{
public static void Command(CommandLineApplication command)
{
var referencesFile = command.Option("--references",
"The path to a file that lists the paths to given referenced dll files",
CommandOptionType.SingleValue);
var outputPath = command.Option("--output",
"Path to the output file",
CommandOptionType.SingleValue);
var mainAssemblyPath = command.Argument("assembly",
"Path to the assembly containing the entry point of the application.");
var linkerEnabledFlag = command.Option("--linker-enabled",
"If set, specifies that the application is being built with linking enabled.",
CommandOptionType.NoValue);
command.OnExecute(() =>
{
if (string.IsNullOrEmpty(mainAssemblyPath.Value) || !outputPath.HasValue())
{
command.ShowHelp(command.Name);
return 1;
}
try
{
var referencesSources = referencesFile.HasValue()
? File.ReadAllLines(referencesFile.Value())
: Array.Empty<string>();
BootJsonWriter.WriteFile(
mainAssemblyPath.Value,
referencesSources,
linkerEnabledFlag.HasValue(),
outputPath.Value());
return 0;
}
catch (Exception ex)
{
Console.WriteLine($"ERROR: {ex.Message}");
Console.WriteLine(ex.StackTrace);
return 1;
}
});
}
}
}

View File

@ -1,33 +0,0 @@
// 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 Microsoft.AspNetCore.Blazor.Build.DevServer.Commands;
using Microsoft.Extensions.CommandLineUtils;
namespace Microsoft.AspNetCore.Blazor.Build
{
static class Program
{
static int Main(string[] args)
{
var app = new CommandLineApplication
{
Name = "Microsoft.AspNetCore.Blazor.Build"
};
app.HelpOption("-?|-h|--help");
app.Command("resolve-dependencies", ResolveRuntimeDependenciesCommand.Command);
app.Command("write-boot-json", WriteBootJsonCommand.Command);
if (args.Length > 0)
{
return app.Execute(args);
}
else
{
app.ShowHelp();
return 0;
}
}
}
}

View File

@ -1,70 +0,0 @@
// 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.Reflection;
using System.Text.Json;
using Microsoft.AspNetCore.Components;
namespace Microsoft.AspNetCore.Blazor.Build
{
internal class BootJsonWriter
{
public static void WriteFile(
string assemblyPath,
string[] assemblyReferences,
bool linkerEnabled,
string outputPath)
{
var bootJsonText = GetBootJsonContent(
AssemblyName.GetAssemblyName(assemblyPath).Name,
assemblyReferences,
linkerEnabled);
var normalizedOutputPath = Path.GetFullPath(outputPath);
Console.WriteLine("Writing boot data to: " + normalizedOutputPath);
File.WriteAllText(normalizedOutputPath, bootJsonText);
}
public static string GetBootJsonContent(string entryAssembly, string[] assemblyReferences, bool linkerEnabled)
{
var data = new BootJsonData(
entryAssembly,
assemblyReferences,
linkerEnabled);
return JsonSerializer.Serialize(data, JsonSerializerOptionsProvider.Options);
}
/// <summary>
/// Defines the structure of a Blazor boot JSON file
/// </summary>
readonly struct BootJsonData
{
/// <summary>
/// Gets the name of the assembly with the application entry point
/// </summary>
public string EntryAssembly { get; }
/// <summary>
/// Gets the closure of assemblies to be loaded by Blazor WASM. This includes the application entry assembly.
/// </summary>
public IEnumerable<string> Assemblies { get; }
/// <summary>
/// Gets a value that determines if the linker is enabled.
/// </summary>
public bool LinkerEnabled { get; }
public BootJsonData(
string entryAssembly,
IEnumerable<string> assemblies,
bool linkerEnabled)
{
EntryAssembly = entryAssembly;
Assemblies = assemblies;
LinkerEnabled = linkerEnabled;
}
}
}
}

View File

@ -1,37 +1,71 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
<TargetFrameworks>$(DefaultNetCoreTargetFramework);net46</TargetFrameworks>
<TargetName>Microsoft.AspNetCore.Blazor.Build.Tasks</TargetName>
<AssemblyName>Microsoft.AspNetCore.Blazor.Build</AssemblyName>
<Description>Build mechanism for ASP.NET Core Blazor applications.</Description>
<OutputType>Exe</OutputType>
<IsShippingPackage>true</IsShippingPackage>
<HasReferenceAssembly>false</HasReferenceAssembly>
<GenerateDependencyFile>false</GenerateDependencyFile>
</PropertyGroup>
<!-- Pack settings -->
<PropertyGroup>
<!-- Producing this package requires building with NodeJS enabled. -->
<IsPackable Condition="'$(BuildNodeJS)' == 'false'">false</IsPackable>
<GenerateNuspecDependsOn>$(GenerateNuspecDependsOn);Publish</GenerateNuspecDependsOn>
<NoPackageAnalysis>true</NoPackageAnalysis>
<NuspecFile>Microsoft.AspNetCore.Blazor.Build.nuspec</NuspecFile>
</PropertyGroup>
<ItemGroup>
<NuspecProperty Include="configuration=$(Configuration)" />
<NuspecProperty Include="publishDir=$(PublishDir)" />
<NuspecProperty Include="taskskDir=$(OutputPath)tools" />
<NuspecProperty Include="componentsversion=$(ComponentsPackageVersion)" />
<NuspecProperty Include="razorversion=$(MicrosoftAspNetCoreRazorDesignPackageVersion)" />
<NuspecProperty Include="blazormonoversion=$(MicrosoftAspNetCoreBlazorMonoPackageVersion)" />
</ItemGroup>
<ItemGroup>
<ProjectReference Condition="'$(BuildNodeJS)' != 'false' and '$(BuildingInsideVisualStudio)' != 'true'" Include="$(RepoRoot)src\Components\Web.JS\Microsoft.AspNetCore.Components.Web.JS.npmproj" ReferenceOutputAssembly="false" />
<Reference Include="Microsoft.Extensions.CommandLineUtils.Sources" />
<Reference Include="System.Reflection.Metadata" />
<!-- Add a project dependency without reference output assemblies to enforce build order -->
<!-- Applying workaround for https://github.com/microsoft/msbuild/issues/2661 and https://github.com/dotnet/sdk/issues/952 -->
<ProjectReference
Include="$(RepoRoot)src\Components\Web.JS\Microsoft.AspNetCore.Components.Web.JS.npmproj"
ReferenceOutputAssemblies="false"
SkipGetTargetFrameworkProperties="true"
UndefineProperties="TargetFramework"
Private="false"
Condition="'$(BuildNodeJS)' != 'false' and '$(BuildingInsideVisualStudio)' != 'true'" />
<Reference Include="Microsoft.Build.Framework" ExcludeAssets="Runtime" />
<Reference Include="Microsoft.Build.Utilities.Core" ExcludeAssets="Runtime" />
<Reference Include="System.Reflection.Metadata" Condition="'$(TargetFramework)' == 'net46'" />
</ItemGroup>
<Target Name="CopyBuildTask" BeforeTargets="Build" Condition="'$(DotNetBuildFromSource)' != 'true' AND '$(IsInnerBuild)' != 'true'">
<!--
The task produced by this project is referenced within this solution. When building, Visual Studio will lock up the assembly.
Any attempts to overwrite the binary with a newer version will fail. This is particularly grating if a developer "Rebuilds" the project
after an initial build since that would always attempt to overwrite the tasks dll
This target attempts to make this solution more usable at the cost of a more onerous inner-loop build of the Blazor.Build tasks.
We'll copy the tasks to a location other that than the build output and use that in the Blazor.Build.targets. In the most common
case where these tasks aren't being worked on, everything should work great. However, if you're attemping to modify these tasks,
you will need to manaully stop MSBuild.exe processes
-->
<ItemGroup>
<Compile Include="..\..\..\Shared\src\JsonSerializerOptionsProvider.cs" />
<_NetCoreFilesToCopy Include="$(OutputPath)$(DefaultNetCoreTargetFramework)\*" TargetPath="netcoreapp\" />
<_DesktopFilesToCopy Include="$(OutputPath)net46\*" TargetPath="netfx\" />
<_AllFilesToCopy Include="@(_NetCoreFilesToCopy);@(_DesktopFilesToCopy)" />
</ItemGroup>
<Error Text="No files found in $(OutputPath)$(DefaultNetCoreTargetFramework)" Condition="@(_NetCoreFilesToCopy->Count()) == 0" />
<Error Text="No files found in $(OutputPath)net46" Condition="@(_DesktopFilesToCopy->Count()) == 0" />
<Copy SourceFiles="@(_AllFilesToCopy)" DestinationFiles="@(_AllFilesToCopy->'$(OutputPath)tools\%(TargetPath)%(FileName)%(Extension)')" SkipUnchangedFiles="true" Retries="1" ContinueOnError="true">
<Output TaskParameter="CopiedFiles" ItemName="FileWrites" />
</Copy>
</Target>
</Project>

View File

@ -11,7 +11,7 @@
<file src="..\..\..\THIRD-PARTY-NOTICES.txt" />
<file src="build\**" target="build" />
<file src="targets\**" target="targets" />
<file src="$publishdir$**\*" target="tools/" />
<file src="..\..\..\Web.JS\dist\$configuration$\blazor.*.js" target="tools/blazor" />
<file src="$taskskDir$\*" target="tools/" />
<file src="..\..\..\Web.JS\dist\$configuration$\blazor.webassembly.js" target="tools/blazor" />
</files>
</package>

View File

@ -0,0 +1,25 @@
<Project>
<!--
Importing this file is equivalent to having:
<PackageDependency Include="Microsoft.AspNetCore.Blazor.Build" />
... except it's much more convenient when working in this repo, because it consumes the
Blazor.Build targets/exe directly without needing this project to be packed into a .nupkg.
This is only intended for use by other projects in this repo.
-->
<PropertyGroup>
<ComponentsRoot Condition="'$(ComponentsRoot)'==''">$(MSBuildThisFileDirectory)..\..\..\</ComponentsRoot>
<BlazorJsPath>$(ComponentsRoot)Web.JS\dist\$(Configuration)\blazor.webassembly.js</BlazorJsPath>
<BlazorJsMapPath>$(ComponentsRoot)Web.JS\dist\$(Configuration)\blazor.webassembly.js.map</BlazorJsMapPath>
<BlazorToolsDir>$(MSBuildThisFileDirectory)bin\$(Configuration)\tools\</BlazorToolsDir>
</PropertyGroup>
<Target Name="CheckBlazorJSFiles" BeforeTargets="Build">
<Error Text="blazor.webassembly.js file could not be found at $(BlazorJsPath)" Condition="!Exists($(BlazorJsPath))" />
</Target>
<Import Project="$(MSBuildThisFileDirectory)targets/All.props" />
<Import Project="$(MSBuildThisFileDirectory)targets/All.targets" />
</Project>

View File

@ -1,21 +1,6 @@
<Project>
<!--
Importing this file is equivalent to having:
<PackageDependency Include="Microsoft.AspNetCore.Blazor.Build" />
... except it's much more convenient when working in this repo, because it consumes the
Blazor.Build targets/exe directly without needing this project to be packed into a .nupkg.
This is only intended for use by other projects in this repo.
-->
<PropertyGroup>
<BlazorBuildReferenceFromSource>true</BlazorBuildReferenceFromSource>
<BlazorJsPath>$(RepoRoot)src\Components\Web.JS\dist\$(Configuration)\blazor.*.js.*</BlazorJsPath>
</PropertyGroup>
<Import Project="$(MSBuildThisFileDirectory)targets/All.props" />
<Import Project="$(MSBuildThisFileDirectory)targets/All.targets" />
<Import Project="ReferenceBlazorBuildFromSource.props" />
<!--
Debugging support using blazor-devserver serve.

View File

@ -0,0 +1,74 @@
// 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.Linq;
using System.Reflection;
using System.Runtime.Serialization.Json;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
namespace Microsoft.AspNetCore.Blazor.Build
{
public class GenerateBlazorBootJson : Task
{
[Required]
public string AssemblyPath { get; set; }
[Required]
public ITaskItem[] References { get; set; }
[Required]
public bool LinkerEnabled { get; set; }
[Required]
public string OutputPath { get; set; }
public override bool Execute()
{
var entryAssemblyName = AssemblyName.GetAssemblyName(AssemblyPath).Name;
var assemblies = References.Select(c => Path.GetFileName(c.ItemSpec)).ToArray();
using var fileStream = File.Create(OutputPath);
WriteBootJson(fileStream, entryAssemblyName, assemblies, LinkerEnabled);
return true;
}
internal static void WriteBootJson(Stream stream, string entryAssemblyName, string[] assemblies, bool linkerEnabled)
{
var data = new BootJsonData
{
entryAssembly = entryAssemblyName,
assemblies = assemblies,
linkerEnabled = linkerEnabled,
};
var serializer = new DataContractJsonSerializer(typeof(BootJsonData));
serializer.WriteObject(stream, data);
}
/// <summary>
/// Defines the structure of a Blazor boot JSON file
/// </summary>
#pragma warning disable IDE1006 // Naming Styles
public class BootJsonData
{
/// <summary>
/// Gets the name of the assembly with the application entry point
/// </summary>
public string entryAssembly { get; set; }
/// <summary>
/// Gets the closure of assemblies to be loaded by Blazor WASM. This includes the application entry assembly.
/// </summary>
public string[] assemblies { get; set; }
/// <summary>
/// Gets a value that determines if the linker is enabled.
/// </summary>
public bool linkerEnabled { get; set; }
}
#pragma warning restore IDE1006 // Naming Styles
}
}

View File

@ -9,32 +9,43 @@ using System.Linq;
using System.Reflection;
using System.Reflection.Metadata;
using System.Reflection.PortableExecutable;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
namespace Microsoft.AspNetCore.Blazor.Build
{
internal class RuntimeDependenciesResolver
public class ResolveBlazorRuntimeDependencies : Task
{
public static void ResolveRuntimeDependencies(
string entryPoint,
string[] applicationDependencies,
string[] monoBclDirectories,
string outputFile)
[Required]
public string EntryPoint { get; set; }
[Required]
public ITaskItem[] ApplicationDependencies { get; set; }
[Required]
public ITaskItem[] WebAssemblyBCLAssemblies { get; set; }
[Output]
public ITaskItem[] Dependencies { get; set; }
public override bool Execute()
{
var paths = ResolveRuntimeDependenciesCore(entryPoint, applicationDependencies, monoBclDirectories);
File.WriteAllLines(outputFile, paths);
var paths = ResolveRuntimeDependenciesCore(EntryPoint, ApplicationDependencies.Select(c => c.ItemSpec), WebAssemblyBCLAssemblies.Select(c => c.ItemSpec));
Dependencies = paths.Select(p => new TaskItem(p)).ToArray();
return true;
}
public static IEnumerable<string> ResolveRuntimeDependenciesCore(
string entryPoint,
string[] applicationDependencies,
string[] monoBclDirectories)
IEnumerable<string> applicationDependencies,
IEnumerable<string> monoBclAssemblies)
{
var entryAssembly = new AssemblyEntry(entryPoint, GetAssemblyName(entryPoint));
var dependencies = CreateAssemblyLookup(applicationDependencies);
var bcl = CreateAssemblyLookup(monoBclDirectories
.SelectMany(d => Directory.EnumerateFiles(d, "*.dll").Select(f => Path.Combine(d, f))));
var bcl = CreateAssemblyLookup(monoBclAssemblies);
var assemblyResolutionContext = new AssemblyResolutionContext(
entryAssembly,
@ -103,8 +114,9 @@ namespace Microsoft.AspNetCore.Blazor.Build
void ResolveAssembliesCore()
{
while (pendingAssemblies.TryPop(out var current))
while (pendingAssemblies.Count > 0)
{
var current = pendingAssemblies.Pop();
if (visitedAssemblies.Add(current))
{
// Not all references will be resolvable within the Mono BCL.

View File

@ -6,8 +6,10 @@
</PropertyGroup>
<PropertyGroup>
<BlazorToolsDir Condition="'$(BlazorToolsDir)' == ''">$(MSBuildThisFileDirectory)../tools/</BlazorToolsDir>
<BlazorBuildExe>dotnet &quot;$(BlazorToolsDir)Microsoft.AspNetCore.Blazor.Build.dll&quot;</BlazorBuildExe>
<BlazorToolsDir Condition="'$(BlazorToolsDir)' == ''">$(MSBuildThisFileDirectory)..\tools\</BlazorToolsDir>
<_BlazorTasksTFM Condition=" '$(MSBuildRuntimeType)' == 'Core'">netcoreapp</_BlazorTasksTFM>
<_BlazorTasksTFM Condition=" '$(_BlazorTasksTFM)' == ''">netfx</_BlazorTasksTFM>
<BlazorTasksPath>$(BlazorToolsDir)$(_BlazorTasksTFM)\Microsoft.AspNetCore.Blazor.Build.Tasks.dll</BlazorTasksPath>
<!-- The Blazor build code can only find your referenced assemblies if they are in the output directory -->
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
@ -16,15 +18,25 @@
<Import Project="Blazor.MonoRuntime.targets" />
<Import Project="Publish.targets" />
<Target Name="GenerateBlazorMetadataFile" BeforeTargets="GetCopyToOutputDirectoryItems">
<Target Name="GenerateBlazorMetadataFile"
BeforeTargets="GetCopyToOutputDirectoryItems">
<PropertyGroup>
<BlazorMetadataFileName>$(AssemblyName).blazor.config</BlazorMetadataFileName>
<BlazorMetadataFilePath>$(TargetDir)$(BlazorMetadataFileName)</BlazorMetadataFilePath>
</PropertyGroup>
<WriteLinesToFile File="$(BlazorMetadataFilePath)" Lines="$(MSBuildProjectFullPath)" Overwrite="true" Encoding="Unicode"/>
<WriteLinesToFile File="$(BlazorMetadataFilePath)" Lines="$(OutDir)$(AssemblyName).dll" Overwrite="false" Encoding="Unicode"/>
<WriteLinesToFile File="$(BlazorMetadataFilePath)" Condition="'$(BlazorRebuildOnFileChange)'=='true'" Lines="autorebuild:true" Overwrite="false" Encoding="Unicode"/>
<WriteLinesToFile File="$(BlazorMetadataFilePath)" Condition="'$(BlazorEnableDebugging)'=='true'" Lines="debug:true" Overwrite="false" Encoding="Unicode"/>
<ItemGroup>
<_BlazorConfigContent Include="$(MSBuildProjectFullPath)" />
<_BlazorConfigContent Include="$(TargetPath)" />
<_BlazorConfigContent Include="debug:true" Condition="'$(BlazorEnableDebugging)'=='true'" />
</ItemGroup>
<WriteLinesToFile
File="$(BlazorMetadataFilePath)"
Lines="@(_BlazorConfigContent)"
Overwrite="true"
WriteOnlyWhenDifferent="True" />
<ItemGroup>
<ContentWithTargetPath Include="$(BlazorMetadataFilePath)" TargetPath="$(BlazorMetadataFileName)" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>

View File

@ -1,22 +1,20 @@
<Project>
<PropertyGroup Condition="'$(BlazorBuildReferenceFromSource)'==''">
<BlazorJsPath>$(MSBuildThisFileDirectory)../tools/blazor/blazor.*.js</BlazorJsPath>
<PropertyGroup>
<BlazorJsPath Condition="'$(BlazorJsPath)' == ''">$(MSBuildThisFileDirectory)..\tools\blazor\blazor.webassembly.js</BlazorJsPath>
</PropertyGroup>
<PropertyGroup Label="Blazor build outputs">
<MonoLinkerI18NAssemblies>none</MonoLinkerI18NAssemblies> <!-- See Mono linker docs - allows comma-separated values from: none,all,cjk,mideast,other,rare,west -->
<AdditionalMonoLinkerOptions>--disable-opt unreachablebodies --verbose --strip-security true --exclude-feature com --exclude-feature sre -v false -c link -u link -b true</AdditionalMonoLinkerOptions>
<BaseBlazorDistPath>dist/</BaseBlazorDistPath>
<BaseBlazorPackageContentOutputPath>$(BaseBlazorDistPath)_content/</BaseBlazorPackageContentOutputPath>
<BaseBlazorRuntimeOutputPath>$(BaseBlazorDistPath)_framework/</BaseBlazorRuntimeOutputPath>
<BaseBlazorRuntimeBinOutputPath>$(BaseBlazorRuntimeOutputPath)_bin/</BaseBlazorRuntimeBinOutputPath>
<BaseBlazorRuntimeWasmOutputPath>$(BaseBlazorRuntimeOutputPath)wasm/</BaseBlazorRuntimeWasmOutputPath>
<BaseBlazorJsOutputPath>$(BaseBlazorRuntimeOutputPath)</BaseBlazorJsOutputPath>
<BaseBlazorIntermediateOutputPath>blazor/</BaseBlazorIntermediateOutputPath>
<BlazorWebRootName>wwwroot/</BlazorWebRootName>
<BaseBlazorDistPath>dist\</BaseBlazorDistPath>
<BaseBlazorPackageContentOutputPath>$(BaseBlazorDistPath)_content\</BaseBlazorPackageContentOutputPath>
<BaseBlazorRuntimeOutputPath>$(BaseBlazorDistPath)_framework\</BaseBlazorRuntimeOutputPath>
<BlazorRuntimeBinOutputPath>$(BaseBlazorRuntimeOutputPath)_bin\</BlazorRuntimeBinOutputPath>
<BlazorRuntimeWasmOutputPath>$(BaseBlazorRuntimeOutputPath)wasm\</BlazorRuntimeWasmOutputPath>
<BlazorWebRootName>wwwroot\</BlazorWebRootName>
<BlazorBootJsonName>blazor.boot.json</BlazorBootJsonName>
<BlazorBootJsonOutputPath>$(BaseBlazorRuntimeOutputPath)$(BlazorBootJsonName)</BlazorBootJsonOutputPath>
<_BlazorBuiltInBclLinkerDescriptor>$(MSBuildThisFileDirectory)BuiltInBclLinkerDescriptor.xml</_BlazorBuiltInBclLinkerDescriptor>
</PropertyGroup>
</Project>

View File

@ -1,206 +1,85 @@
<Project>
<PropertyGroup>
<BlazorLinkOnBuild Condition="$(BlazorLinkOnBuild) == ''">true</BlazorLinkOnBuild>
</PropertyGroup>
<PropertyGroup>
<!-- Stop-gap until we can migrate Blazor.Mono package to use better naming convention -->
<DotNetWebAssemblyBCLPath Condition="'$(DotNetWebAssemblyBCLPath)' == '' AND '$(MonoBaseClassLibraryPath)' != ''">$(MonoBaseClassLibraryPath)</DotNetWebAssemblyBCLPath>
<DotNetWebAssemblyBCLFacadesPath Condition="'$(DotNetWebAssemblyBCLFacadesPath)' == '' AND '$(MonoBaseClassLibraryFacadesPath)' != ''">$(MonoBaseClassLibraryFacadesPath)</DotNetWebAssemblyBCLFacadesPath>
<DotNetWebAssemblyRuntimePath Condition="'$(DotNetWebAssemblyRuntimePath)' == '' AND '$(MonoWasmRuntimePath)' != ''">$(MonoWasmRuntimePath)</DotNetWebAssemblyRuntimePath>
<DotNetWebAssemblyFrameworkPath Condition="'$(DotNetWebAssemblyFrameworkPath)' == '' AND '$(MonoWasmFrameworkPath)' != ''">$(MonoWasmFrameworkPath)</DotNetWebAssemblyFrameworkPath>
</PropertyGroup>
<PropertyGroup Condition="'$(DotNetWebAssemblyArtifactsRoot)' != ''">
<!-- Compute paths given a path to DotNet WASM artifacts. This is meant to make it easy to test WASM builds -->
<DotNetWebAssemblyBCLPath>$(DotNetWebAssemblyArtifactsRoot)\wasm-bcl\wasm\</DotNetWebAssemblyBCLPath>
<DotNetWebAssemblyBCLFacadesPath>$(DotNetWebAssemblyBCLPath)\Facades\</DotNetWebAssemblyBCLFacadesPath>
<DotNetWebAssemblyRuntimePath>$(DotNetWebAssemblyArtifactsRoot)\builds\debug\</DotNetWebAssemblyRuntimePath>
<DotNetWebAssemblyFrameworkPath>$(DotNetWebAssemblyArtifactsRoot)\framework\</DotNetWebAssemblyFrameworkPath>
</PropertyGroup>
<Target
Name="_BlazorCopyFilesToOutputDirectory"
DependsOnTargets="PrepareBlazorOutputs"
Inputs="@(BlazorItemOutput)"
Outputs="@(BlazorItemOutput->'%(TargetOutputPath)')"
Inputs="@(BlazorOutputWithTargetPath)"
Outputs="@(BlazorOutputWithTargetPath->'$(TargetDir)%(TargetOutputPath)')"
AfterTargets="CopyFilesToOutputDirectory"
Condition="'$(OutputType.ToLowerInvariant())'=='exe'">
<!-- Copy the blazor output files -->
<Copy
SourceFiles="@(BlazorItemOutput)"
DestinationFiles="@(BlazorItemOutput->'%(TargetOutputPath)')"
SourceFiles="@(BlazorOutputWithTargetPath)"
DestinationFiles="@(BlazorOutputWithTargetPath->'$(TargetDir)%(TargetOutputPath)')"
SkipUnchangedFiles="$(SkipCopyUnchangedFiles)"
OverwriteReadOnlyFiles="$(OverwriteReadOnlyFiles)"
Retries="$(CopyRetryCount)"
RetryDelayMilliseconds="$(CopyRetryDelayMilliseconds)"
UseHardlinksIfPossible="$(CreateHardLinksForCopyFilesToOutputDirectoryIfPossible)"
UseSymboliclinksIfPossible="$(CreateSymbolicLinksForCopyFilesToOutputDirectoryIfPossible)"
Condition="'@(BlazorItemOutput)' != '' and '$(CopyBuildOutputToOutputDirectory)' == 'true' and '$(SkipCopyBuildProduct)' != 'true'">
Condition="'@(BlazorOutputWithTargetPath)' != '' and '$(CopyBuildOutputToOutputDirectory)' == 'true' and '$(SkipCopyBuildProduct)' != 'true'">
</Copy>
<ItemGroup>
<FileWrites Include="@(BlazorItemOutput->'%(TargetOutputPath)')" />
<FileWrites Include="@(BlazorOutputWithTargetPath->'$(TargetDir)%(TargetOutputPath)')" />
</ItemGroup>
</Target>
<Target Name="_BlazorTrackResolveReferencesDidRun" AfterTargets="ResolveReferences">
<PropertyGroup>
<!-- So we know we can trust @(ReferenceCopyLocalPaths) later -->
<_BlazorResolveReferencesDidRun>true</_BlazorResolveReferencesDidRun>
</PropertyGroup>
</Target>
<Target Name="_BlazorBuildReport"
AfterTargets="_BlazorCopyFilesToOutputDirectory">
<ItemGroup>
<_BlazorStatisticsOutput Include="@(BlazorItemOutput->'%(TargetOutputPath)')" />
<_BlazorStatisticsOutput Include="@(BlazorOutputWithTargetPath->'%(TargetOutputPath)')" />
</ItemGroup>
<PropertyGroup>
<_BlazorStatisticsReportImportance Condition="'$(BlazorOutputStatistics)' == ''">normal</_BlazorStatisticsReportImportance>
<_BlazorStatisticsReportImportance Condition="'$(BlazorOutputStatistics)' != ''">high</_BlazorStatisticsReportImportance>
</PropertyGroup>
<Message Importance="high" Text="Blazor Build result -> @(_BlazorStatisticsOutput->Distinct()->Count()) files in $(TargetDir)dist" />
<Message Importance="$(_BlazorStatisticsReportImportance)" Text="%(_BlazorStatisticsOutput.Identity)" />
<Message Importance="high" Text="$(TargetName) (Blazor output) -> $(TargetDir)dist" />
</Target>
<!-- Preparing blazor files for output:
PrepareBlazorOutputs
_PrepareBlazorOutputConfiguration
_DefineBlazorCommonInputs
_BlazorResolveOutputBinaries
When link on build:
_GenerateLinkerDescriptor
_CollectBlazorLinkerDescriptors
_LinkBlazorApplication
_CollectLinkerOutputs
When don't link on build:
_CollectResolvedAssemblies
_ResolveBlazorApplicationAssemblies
_ReadResolvedBlazorApplicationAssemblies
_IntermediateCopyBlazorApplicationAssemblies
_TouchBlazorApplicationAssemblies
_GenerateBlazorBootJson
_BlazorCopyFilesToOutputDirectory
<Target
Name="PrepareBlazorOutputs"
DependsOnTargets="_ResolveBlazorInputs;_ResolveBlazorOutputs;_GenerateBlazorBootJson">
The process for doing builds goes as follows:
Produce a hash file with the Hash SDK task and write that hash to a marker file.
Produce a marker file that saves whether we are linking or not in this build so that we can take that as
input in future builds and do the correct thing for incremental builds.
We only produce marker files when the input changes, if the input doesn't change the marker stays the
same.
<ItemGroup>
<MonoWasmFile Include="$(DotNetWebAssemblyRuntimePath)*" />
<BlazorJSFile Include="$(BlazorJSPath)" />
<BlazorJSFile Include="$(BlazorJSMapPath)" Condition="Exists('$(BlazorJSMapPath)')" />
If we are linking on this build the process is as follows:
1) We determine if there are linker descriptors available, if not generate one.
2) Collect the list of linker descriptors and create a marker for the linker if it doesn't exist or changed
from a previous build.
3) Run the linker in case the linker inputs marker is newer than the linker result file.
4) Collect the outputs from the linker.
If we are not linking in this build the process is as follows:
1) Resolve the assemblies for the application only if the inputs marker is newer than the resolved assemblies
result file.
2) Read the result file with the resolved assemblies.
3) Copy the resolved assemblies to an intermediate folder.
4) In case we are switching from linking to not linking, touch the files in the intermediate folder to ensure
that updated versions of the files get copied to the output folder.
Once the binary outputs are resolved:
1) Create a marker file with the resolved assemblies and the boot json data as inputs.
2) If the marker file is newer than the boot json in the output folder, regenerate the
boot json
Once all the outputs are resolved (static content + binary outputs + boot json)
Copy all the files to the output folder.
-->
<PropertyGroup>
<PrepareBlazorOutputs>
_PrepareBlazorOutputConfiguration;
_DefineBlazorCommonInputs;
_BlazorResolveOutputBinaries;
_GenerateBlazorBootJson;
</PrepareBlazorOutputs>
</PropertyGroup>
<Target Name="PrepareBlazorOutputs" DependsOnTargets="$(PrepareBlazorOutputs)" />
<!--
Prepare blazor outputs preamble:
* Creates updated marker files (if necessary) for incremental builds.
* Computes intermediate and final output paths.
* Computes the list of static items to copy to the output folder.
-->
<Target Name="_PrepareBlazorOutputConfiguration">
<!--
This task produces all the "final" paths for all the files we need to produce the final output.
The final folder is something like bin/<<Configuration>>/<<TargetFramework>>/dist
/_framework/_bin <- This will contain either the BCL + app assemblies or the result of linking the app.
/_framework/wasm <- This will contain the wsm runtime copied from the nuget package.
/_framework/blazor.js <- This is the blazor.js file copied from the nuget package.
/_framework/blazor.boot.json <- This is the boot json file
This task also defines some intermediate paths that we will use:
/obj/<<configuration>>/<<targetframework>>/blazor/blazor/linker <- This will be used to create the output from the linker.
/obj/<<configuration>>/<<targetframework>>/blazor/blazor/linked.assemblies.txt <- This will be used to save the output files from
the linker and use that as marker to identify whether or not we need to run the linker.
/obj/<<configuration>>/<<targetframework>>/blazor/blazor/linker.descriptor.xml <- This will be used to generate an XML descriptor
for the mono linker.
/obj/<<configuration>>/<<targetframework>>/blazor/inputs.basic.cache <- This is the marker file to track the inputs common
inputs to the output generation process.
/obj/<<configuration>>/<<targetframework>>/blazor/inputs.copylocal.txt <- Paths to all the copy-local referenced assemblies found
during the build process (i.e., the @(ReferenceCopyLocalPaths) values). We need this because when publishing, the build doesn't
necessarily also run so this is the only way we know which assemblies to include in linking/resolveassemblies.
/obj/<<configuration>>/<<targetframework>>/blazor/inputs.linkerswitch.cache <- This is the marker file to track the
switch from linking to not linking and viceversa.
/obj/<<configuration>>/<<targetframework>>/blazor/inputs.linker.cache <- This is the marker file to track the inputs
to the linker.
/obj/<<configuration>>/<<targetframework>>/blazor/resolvedassemblies/ <- This will be used to store the resolved assemblies
before copying them to the output when linking is not enabled.
/obj/<<configuration>>/<<targetframework>>/blazor/resolved.assemblies.txt <- This keeps track of all the resolved assemblies.
/obj/<<configuration>>/<<targetframework>>/blazor/blazor.boot.json <- The generated boot json file
/obj/<<configuration>>/<<targetframework>>/blazor/inputs.bootjson.cache <- The marker file that track whether boot json needs to
be regenerated.
-->
<PropertyGroup Label="Build properties">
<_BlazorShouldLinkApplicationAssemblies Condition="$(BlazorLinkOnBuild) == 'false'"></_BlazorShouldLinkApplicationAssemblies>
<_BlazorShouldLinkApplicationAssemblies Condition="$(BlazorLinkOnBuild) == 'true'">true</_BlazorShouldLinkApplicationAssemblies>
<_BlazorBuiltInBclLinkerDescriptor>$(MSBuildThisFileDirectory)BuiltInBclLinkerDescriptor.xml</_BlazorBuiltInBclLinkerDescriptor>
</PropertyGroup>
<ItemGroup Label="Static content to copy to the output folder">
<MonoWasmFile Include="$(MonoWasmRuntimePath)**/*.*" />
<BlazorJsFile Include="$(BlazorJsPath)" />
<BlazorItemOutput Include="@(MonoWasmFile)">
<TargetOutputPath>$(TargetDir)$(BaseBlazorRuntimeWasmOutputPath)%(FileName)%(Extension)</TargetOutputPath>
<Type>WebAssembly</Type>
<IsStatic>true</IsStatic>
</BlazorItemOutput>
<BlazorItemOutput Include="@(BlazorJsFile)">
<TargetOutputPath>$(TargetDir)$(BaseBlazorJsOutputPath)%(FileName)%(Extension)</TargetOutputPath>
<Type>BlazorRuntime</Type>
<IsStatic>true</IsStatic>
</BlazorItemOutput>
<BlazorOutputWithTargetPath Include="@(MonoWasmFile)">
<TargetOutputPath>$(BlazorRuntimeWasmOutputPath)%(FileName)%(Extension)</TargetOutputPath>
</BlazorOutputWithTargetPath>
<BlazorOutputWithTargetPath Include="@(BlazorJSFile)">
<TargetOutputPath>$(BaseBlazorRuntimeOutputPath)%(FileName)%(Extension)</TargetOutputPath>
</BlazorOutputWithTargetPath>
</ItemGroup>
<Error Condition="'@(BlazorJsFile->Count())' == '0'" Text="No JS files found in '$(BlazorJsPath)'" />
<ItemGroup Label="Static content supplied by NuGet packages">
<_BlazorPackageContentOutput Include="@(BlazorPackageContentFile)" Condition="%(SourcePackage) != ''">
<TargetOutputPath>$(TargetDir)$(BaseBlazorPackageContentOutputPath)%(SourcePackage)\%(RecursiveDir)\%(Filename)%(Extension)</TargetOutputPath>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<TargetOutputPath>$(BaseBlazorPackageContentOutputPath)%(SourcePackage)\%(RecursiveDir)\%(Filename)%(Extension)</TargetOutputPath>
</_BlazorPackageContentOutput>
<BlazorItemOutput Include="@(_BlazorPackageContentOutput)" />
<BlazorOutputWithTargetPath Include="@(_BlazorPackageContentOutput)" />
</ItemGroup>
</Target>
<PropertyGroup Label="Intermediate output paths">
<Target Name="_ResolveBlazorInputs">
<PropertyGroup>
<!-- /obj/<<configuration>>/<<targetframework>>/blazor -->
<BlazorIntermediateOutputPath>$(IntermediateOutputPath)$(BaseBlazorIntermediateOutputPath)</BlazorIntermediateOutputPath>
<BlazorIntermediateOutputPath Condition="! $([System.IO.Path]::IsPathRooted($(BlazorIntermediateOutputPath)))">$([MSBuild]::Escape($([System.IO.Path]::GetFullPath('$([System.IO.Path]::Combine('$(MSBuildProjectDirectory)', '$(BlazorIntermediateOutputPath)'))'))))</BlazorIntermediateOutputPath>
<!-- Common marker files paths -->
<!-- /obj/<<configuration>>/<<targetframework>>/blazor/inputs.basic.cache -->
<BlazorBuildCommonInputsCache>$(BlazorIntermediateOutputPath)inputs.basic.cache</BlazorBuildCommonInputsCache>
<!-- /obj/<<configuration>>/<<targetframework>>/blazor/inputs.copylocal.txt -->
<BlazorLocalReferencesOutputPath>$(BlazorIntermediateOutputPath)inputs.copylocal.txt</BlazorLocalReferencesOutputPath>
<!-- /obj/<<configuration>>/<<targetframework>>/blazor/inputs.linkerswitch.cache -->
<BlazorBuildLinkerSwitchInputsCache>$(BlazorIntermediateOutputPath)inputs.linkerswitch.cache</BlazorBuildLinkerSwitchInputsCache>
<!-- Linker paths and marker files -->
<!-- /obj/<<configuration>>/<<targetframework>>/blazor/inputs.linker.cache -->
<BlazorBuildLinkerInputsCache>$(BlazorIntermediateOutputPath)inputs.linker.cache</BlazorBuildLinkerInputsCache>
<BlazorIntermediateOutputPath>$(IntermediateOutputPath)blazor\</BlazorIntermediateOutputPath>
<!-- /obj/<<configuration>>/<<targetframework>>/blazor/linker.descriptor.xml -->
<GeneratedBlazorLinkerDescriptor>$(BlazorIntermediateOutputPath)linker.descriptor.xml</GeneratedBlazorLinkerDescriptor>
@ -208,151 +87,58 @@
<!-- /obj/<<configuration>>/<<targetframework>>/blazor/linker/ -->
<BlazorIntermediateLinkerOutputPath>$(BlazorIntermediateOutputPath)linker/</BlazorIntermediateLinkerOutputPath>
<!-- /obj/<<configuration>>/<<targetframework>>/blazor/linked.assemblies.txt -->
<BlazorIntermediateLinkerResultFilePath>$(BlazorIntermediateOutputPath)linked.assemblies.txt</BlazorIntermediateLinkerResultFilePath>
<!-- Resolved assemblies paths and marker files -->
<!-- /obj/<<configuration>>/<<targetframework>>/blazor/resolvedassemblies/ -->
<BlazorIntermediateResolvedApplicationAssembliesOutputPath>$(BlazorIntermediateOutputPath)resolvedassemblies/</BlazorIntermediateResolvedApplicationAssembliesOutputPath>
<!-- /obj/<<configuration>>/<<targetframework>>/blazor/resolved.assemblies.txt -->
<BlazorResolvedAssembliesOutputPath>$(BlazorIntermediateOutputPath)resolved.assemblies.txt</BlazorResolvedAssembliesOutputPath>
<!-- boot json related paths and markers -->
<!-- /obj/<<configuration>>/<<targetframework>>/blazor/ -->
<BlazorBootJsonIntermediateOutputDir>$(BlazorIntermediateOutputPath)</BlazorBootJsonIntermediateOutputDir>
<!-- /obj/<<configuration>>/<<targetframework>>/blazor/blazor.boot.json -->
<BlazorBootJsonIntermediateOutputPath>$(BlazorBootJsonIntermediateOutputDir)$(BlazorBootJsonName)</BlazorBootJsonIntermediateOutputPath>
<BlazorBootJsonIntermediateOutputPath>$(BlazorIntermediateOutputPath)$(BlazorBootJsonName)</BlazorBootJsonIntermediateOutputPath>
<!-- /obj/<<configuration>>/<<targetframework>>/blazor/inputs.bootjson.cache -->
<BlazorBuildBootJsonInputsCache>$(BlazorIntermediateOutputPath)inputs.bootjson.cache</BlazorBuildBootJsonInputsCache>
<!-- /obj/<<configuration>>/<<targetframework>>/blazor/resolve-dependencies.txt -->
<BlazorResolveDependenciesFilePath>$(BlazorIntermediateOutputPath)resolve-dependencies.txt</BlazorResolveDependenciesFilePath>
<!-- /obj/<<configuration>>/<<targetframework>>/blazor/bootjson-references.txt -->
<BlazorBootJsonReferencesFilePath>$(BlazorIntermediateOutputPath)bootjson-references.txt</BlazorBootJsonReferencesFilePath>
<!-- /obj/<<configuration>>/<<targetframework>>/blazor/embedded.resources.txt -->
<BlazorEmbeddedResourcesConfigFilePath>$(BlazorIntermediateOutputPath)embedded.resources.txt</BlazorEmbeddedResourcesConfigFilePath>
<_BlazorLinkerOutputCache>$(BlazorIntermediateOutputPath)linker.output</_BlazorLinkerOutputCache>
<_BlazorApplicationAssembliesCacheFile>$(BlazorIntermediateOutputPath)unlinked.output</_BlazorApplicationAssembliesCacheFile>
</PropertyGroup>
<PropertyGroup Label="Final output paths">
<BlazorRuntimeBinOutputPath>$(TargetDir)$(BaseBlazorRuntimeBinOutputPath)</BlazorRuntimeBinOutputPath>
</PropertyGroup>
<MakeDir Directories="$(BlazorIntermediateOutputPath)" />
</Target>
<Target Name="_DefineBlazorCommonInputs">
<!-- If ResolveReferences hasn't yet run, we must be inside a VS publish process
that doesn't also do a build, so use the stored information. -->
<ReadLinesFromFile
Condition="'$(_BlazorResolveReferencesDidRun)'!='true'"
File="$(BlazorLocalReferencesOutputPath)">
<Output TaskParameter="Lines" ItemName="_BlazorDependencyInput"/>
</ReadLinesFromFile>
<ItemGroup Condition="'$(_BlazorResolveReferencesDidRun)'=='true'">
<!-- ... otherwise we can get the fresh info from @(ReferenceCopyLocalPaths) -->
<ItemGroup>
<_BlazorDependencyInput Include="@(ReferenceCopyLocalPaths->WithMetadataValue('Extension','.dll')->'%(FullPath)')" />
</ItemGroup>
<ItemGroup>
<_BlazorCommonInput Include="@(IntermediateAssembly)" />
<_BlazorCommonInput Include="@(_BlazorDependencyInput)" />
<_BlazorCommonInput Include="$(_BlazorShouldLinkApplicationAssemblies)" />
<_BlazorCommonInput Include="$(BlazorEnableDebugging)" />
<_BlazorLinkingOption Condition="'$(_BlazorShouldLinkApplicationAssemblies)' == ''" Include="false" />
<_BlazorLinkingOption Condition="'$(_BlazorShouldLinkApplicationAssemblies)' != ''" Include="true" />
</ItemGroup>
<Hash ItemsToHash="@(_BlazorCommonInput)">
<Output TaskParameter="HashResult" PropertyName="_BlazorBuildBasicInputHash" />
</Hash>
<WriteLinesToFile
Lines="$(_BlazorBuildBasicInputHash)"
File="$(BlazorBuildCommonInputsCache)"
Overwrite="True"
WriteOnlyWhenDifferent="True" />
<WriteLinesToFile
Lines="@(_BlazorDependencyInput)"
File="$(BlazorLocalReferencesOutputPath)"
Overwrite="True"
WriteOnlyWhenDifferent="True" />
<!-- Switch to detect when we switch from linking to not linking and viceversa -->
<WriteLinesToFile
Lines="@(_BlazorLinkingOption)"
File="$(BlazorBuildLinkerSwitchInputsCache)"
Overwrite="True"
WriteOnlyWhenDifferent="True" />
<ItemGroup>
<FileWrites Include="$(BlazorBuildLinkerSwitchInputsCache)" />
<FileWrites Include="$(BlazorBuildCommonInputsCache)" />
<FileWrites Include="$(BlazorLocalReferencesOutputPath)" />
</ItemGroup>
<MakeDir Directories="$(BlazorIntermediateOutputPath)" />
</Target>
<Target Name="_BlazorResolveOutputBinaries" DependsOnTargets="_CollectLinkerOutputs;_CollectResolvedAssemblies" />
<Target Name="_ResolveBlazorOutputs" DependsOnTargets="_ResolveBlazorOutputsWhenLinked;_ResolveBlazorOutputsWhenNotLinked">
<Error
Message="Unrecongnized value for BlazorLinkOnBuild: '$(BlazorLinkOnBuild)'. Valid values are 'true' or 'false'."
Condition="'$(BlazorLinkOnBuild)' != 'true' AND '$(BlazorLinkOnBuild)' != 'false'" />
</Target>
<!--
Linker enabled part of the pipeline:
* If there are no descriptors defined, generate a new linker descriptor.
* Collect the list of descriptors and produce a marker file to determine when the
inputs to the linker change in future builds.
* Invoke the linker if the linker inputs marker file is newer than the linker outputs.
* Read the outputs from the linker and add them to the list of blazor outputs.
* Invoke the linker and write linked files to a well-known directory.
* Collect the outputs of the linker.
-->
<PropertyGroup>
<_CollectLinkerOutputsDependsOn>
_GenerateLinkerDescriptor;
_CollectBlazorLinkerDescriptors;
_LinkBlazorApplication
</_CollectLinkerOutputsDependsOn>
</PropertyGroup>
<Target
Name="_CollectLinkerOutputs"
Condition="'$(_BlazorShouldLinkApplicationAssemblies)' != ''"
DependsOnTargets="$(_CollectLinkerOutputsDependsOn)">
<!--
Read the outputs from the linker (from this run or a previous run) and set them in an item group for
later use.
-->
<ReadLinesFromFile File="$(BlazorIntermediateLinkerResultFilePath)">
<Output TaskParameter="Lines" ItemName="_OptimizedFiles"/>
Name="_ResolveBlazorOutputsWhenLinked"
Condition="'$(BlazorLinkOnBuild)' == 'true'"
DependsOnTargets="_GenerateLinkerDescriptor;_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)">
<Output TaskParameter="Lines" ItemName="_BlazorLinkedFile"/>
</ReadLinesFromFile>
<ItemGroup>
<BlazorItemOutput Include="@(_OptimizedFiles->WithMetadataValue('Extension','.dll'))">
<BlazorOutputWithTargetPath Include="%(_BlazorLinkedFile.Identity)">
<TargetOutputPath>$(BlazorRuntimeBinOutputPath)%(FileName)%(Extension)</TargetOutputPath>
<Type>Assembly</Type>
<PrimaryOutput Condition="'%(FileName)' == @(IntermediateAssembly->'%(FileName)')">true</PrimaryOutput>
</BlazorItemOutput>
<BlazorItemOutput Include="@(_OptimizedFiles->WithMetadataValue('Extension','.pdb'))">
<TargetOutputPath>$(BlazorRuntimeBinOutputPath)%(FileName)%(Extension)</TargetOutputPath>
<Type>Pdb</Type>
</BlazorItemOutput>
<FileWrites Include="@(BlazorItemOutput->WithMetadataValue('Type','Assembly')->'%(TargetOutputPath)')" />
</BlazorOutputWithTargetPath>
</ItemGroup>
</Target>
<Target Name="_GenerateLinkerDescriptor"
Inputs="$(BlazorBuildCommonInputsCache)"
Inputs="@(IntermediateAssembly)"
Outputs="$(GeneratedBlazorLinkerDescriptor)"
Condition="$(_BlazorShouldLinkApplicationAssemblies) != '' and '@(BlazorLinkerDescriptor)' == ''">
Condition="'@(BlazorLinkerDescriptor)' == ''">
<!-- Generate linker descriptors if the project doesn't explicitly provide one. -->
<ItemGroup>
<_PrepareLinkerDescriptorAssemblyLine Include="@(IntermediateAssembly->'%(FileName)')" />
@ -367,60 +153,25 @@
Overwrite="true"
WriteOnlyWhenDifferent="True" />
</Target>
<Target Name="_CollectBlazorLinkerDescriptors">
<ItemGroup Condition="@(BlazorLinkerDescriptor) == ''">
<BlazorLinkerDescriptor Include="$(_BlazorBuiltInBclLinkerDescriptor)" />
<BlazorLinkerDescriptor Include="$(GeneratedBlazorLinkerDescriptor)" />
<ItemGroup>
<FileWrites Include="$(GeneratedBlazorLinkerDescriptor)" />
</ItemGroup>
<ItemGroup>
<_BlazorLinkerInput Include="@(IntermediateAssembly)" />
<_BlazorLinkerInput Include="@(_BlazorDependencyInput)" />
<_BlazorLinkerInput Include="@(BlazorLinkerDescriptor)" />
<_BlazorLinkerInput Include="$(AdditionalMonoLinkerOptions)" />
<BlazorLinkerDescriptor Include="$(_BlazorBuiltInBclLinkerDescriptor)" />
<BlazorLinkerDescriptor Include="$(GeneratedBlazorLinkerDescriptor)" />
</ItemGroup>
<Hash ItemsToHash="@(_BlazorLinkerInput)">
<Output TaskParameter="HashResult" PropertyName="_BlazorLinkerInputHash" />
</Hash>
<WriteLinesToFile
Lines="$(_BlazorLinkerInputHash)"
File="$(BlazorBuildLinkerInputsCache)"
Overwrite="True"
WriteOnlyWhenDifferent="True" />
<ItemGroup>
<FileWrites Include="$(BlazorBuildLinkerInputsCache)" />
</ItemGroup>
</Target>
<Target
Name="_LinkBlazorApplication"
Condition="$(_BlazorShouldLinkApplicationAssemblies) != ''"
Inputs="$(BlazorBuildLinkerInputsCache);
Inputs="$(ProjectAssetsFile);
@(IntermediateAssembly);
@(_BlazorDependencyInput);
@(BlazorLinkerDescriptor)"
Outputs="$(BlazorIntermediateLinkerResultFilePath)"
>
<!--
At this point we have decided to run the mono linker on the Blazor assembly and its dependencies.
The steps to run the mono linker are the following:
1) Clear the linker output directory if not clean before hand, as we don't know what the outputs of
the linker will be.
2) Run the linker on the main assembly, its dependencies and pass in the BCL folders to do the lookup
for framework assemblies.
3) Once we've run the linker we need to capture the produced output and generate a marker file containing
the list of produced files. This file will act as a marker to skip running the linker if none of the inputs
has changed.
4) Add the file we just created to the list of file writes, to support incremental builds.
-->
@(BlazorLinkerDescriptor);
$(MSBuildAllProjects)"
Outputs="$(_BlazorLinkerOutputCache)">
<ItemGroup>
<_BlazorDependencyAssembly Include="@(_BlazorDependencyInput)">
<RelativeDirNoTrailingSlash>$([System.String]::Copy('%(RelativeDir)').TrimEnd('\').TrimEnd('/'))</RelativeDirNoTrailingSlash>
@ -428,9 +179,9 @@
</_BlazorDependencyAssembly>
</ItemGroup>
<ItemGroup>
<_MonoBaseClassLibraryFolder Include="$(MonoBaseClassLibraryPath);$(MonoBaseClassLibraryFacadesPath);$(MonoWasmFrameworkPath)" />
<_WebAssemblyBCLFolder Include="$(DotNetWebAssemblyBCLPath);$(DotNetWebAssemblyBCLFacadesPath);$(DotNetWebAssemblyFrameworkPath)" />
<_BlazorAssembliesToCopy Include="@(IntermediateAssembly->'-a &quot;%(FullPath)&quot;')" />
<_BlazorFolderLookupPaths Include="@(_MonoBaseClassLibraryFolder->'-d &quot;%(Identity)&quot;')" />
<_BlazorFolderLookupPaths Include="@(_WebAssemblyBCLFolder->'-d &quot;%(Identity)&quot;')" />
<!-- For linkable assemblies, add their directories as lookup paths -->
<_BlazorFolderLookupPaths Condition="'%(_BlazorDependencyAssembly.IsLinkable)' == 'true'" Include="@(_BlazorDependencyAssembly->'-d &quot;%(RelativeDirNoTrailingSlash)&quot;')" />
@ -446,62 +197,46 @@
<_BlazorLinkerAdditionalOptions>-l $(MonoLinkerI18NAssemblies) $(AdditionalMonoLinkerOptions)</_BlazorLinkerAdditionalOptions>
</PropertyGroup>
<!-- Clear the contents of /obj/<<configuration>>/<<targetframework>>/blazor/blazor/linker -->
<Delete Files="$(BlazorIntermediateLinkerOutputPath)*.dll" />
<!-- 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 &quot;$(MonoLinkerPath)&quot; $(_BlazorLinkerAdditionalOptions) @(_BlazorFolderLookupPaths, ' ') -o &quot;$(BlazorIntermediateLinkerOutputPath)&quot; @(_BlazorAssemblyDescriptorFiles, ' ') @(_BlazorAssembliesToCopy, ' ')" />
<!-- Collect the contents of /obj/<<configuration>>/<<targetframework>>/blazor/blazor/linker/ -->
<ItemGroup>
<_BlazorLinkerOutput Include="$(BlazorIntermediateLinkerOutputPath)*.dll" />
<_BlazorLinkerOutput Include="$(BlazorIntermediateLinkerOutputPath)*.pdb" />
<_LinkerResult Include="$(BlazorIntermediateLinkerOutputPath)*.dll" />
</ItemGroup>
<!--
Write the list of files in /obj/<<configuration>>/<<targetframework>>/blazor/blazor/linker/ into
/obj/<<configuration>>/<<targetframework>>/blazor/blazor/linked.assemblies.txt
-->
<WriteLinesToFile
File="$(BlazorIntermediateLinkerResultFilePath)"
Lines="@(_BlazorLinkerOutput)"
Overwrite="true" />
<WriteLinesToFile File="$(_BlazorLinkerOutputCache)" Lines="@(_LinkerResult)" Overwrite="true" />
</Target>
<UsingTask TaskName="ResolveBlazorRuntimeDependencies" AssemblyFile="$(BlazorTasksPath)" />
<Target
Name="_ResolveBlazorOutputsWhenNotLinked"
DependsOnTargets="_ResolveBlazorRuntimeDependencies"
Condition="'$(BlazorLinkOnBuild)' != 'true'">
<ReadLinesFromFile File="$(_BlazorApplicationAssembliesCacheFile)" Condition="'@(_BlazorResolvedRuntimeDependencies->Count())' == '0'">
<Output TaskParameter="Lines" ItemName="_BlazorResolvedRuntimeDependencies"/>
</ReadLinesFromFile>
<!-- Add /obj/<<configuration>>/<<targetframework>>/blazor/blazor/linked.assemblies.txt to the list of written files. -->
<!-- Add /obj/<<configuration>>/<<targetframework>>/blazor/blazor/linker/*.dll to the list of written files. -->
<ItemGroup>
<FileWrites Include="$(BlazorIntermediateLinkerResultFilePath)" />
<FileWrites Include="@(_BlazorLinkerOutput)" />
<BlazorOutputWithTargetPath Include="@(_BlazorResolvedRuntimeDependencies)">
<TargetOutputPath>$(BlazorRuntimeBinOutputPath)%(FileName)%(Extension)</TargetOutputPath>
</BlazorOutputWithTargetPath>
</ItemGroup>
</Target>
<!--
Linker disabled part of the pipeline:
* Run a CLI tool to produce the transitive closure of application references using the main application
as entry point.
* Read the list of resolved application references from the file produced by the previous step.
* Copy the resolved application references into an intermediate folder.
* If we are switching from linking to not linking
Touch the files in the intermediate folder to ensure they are copied to the output and replace
the linked versions with the same name.
* Collect the list of resolved assemblies in the intermediate output folder and prepare them to be
copied to their final destination in the output folder.
-->
<PropertyGroup>
<_CollectResolvedAssembliesDependsOn>
_ResolveBlazorApplicationAssemblies;
_ReadResolvedBlazorApplicationAssemblies;
_IntermediateCopyBlazorApplicationAssemblies;
_TouchBlazorApplicationAssemblies
</_CollectResolvedAssembliesDependsOn>
</PropertyGroup>
<Target
Name="_CollectResolvedAssemblies"
DependsOnTargets="$(_CollectResolvedAssembliesDependsOn)"
Condition="'$(_BlazorShouldLinkApplicationAssemblies)' == ''">
Name="_ResolveBlazorRuntimeDependencies"
Inputs="$(ProjectAssetsFile);
@(IntermediateAssembly);
@(_BlazorDependencyInput)"
Outputs="$(_BlazorApplicationAssembliesCacheFile)">
<!--
At this point we have decided not to run the linker and instead to just copy the assemblies
@ -510,148 +245,44 @@
-->
<ItemGroup>
<BlazorItemOutput Include="@(_IntermediateResolvedRuntimeDependencies->WithMetadataValue('Extension','.dll'))">
<TargetOutputPath>$(BlazorRuntimeBinOutputPath)%(FileName)%(Extension)</TargetOutputPath>
<Type>Assembly</Type>
<PrimaryOutput Condition="'%(FileName)' == @(IntermediateAssembly->'%(FileName)')">true</PrimaryOutput>
</BlazorItemOutput>
<BlazorItemOutput Include="@(_IntermediateResolvedRuntimeDependencies->WithMetadataValue('Extension','.pdb'))">
<TargetOutputPath>$(BlazorRuntimeBinOutputPath)%(FileName)%(Extension)</TargetOutputPath>
<Type>Pdb</Type>
</BlazorItemOutput>
<FileWrites Include="@(BlazorItemOutput->WithMetadataValue('Type','Assembly')->'%(TargetOutputPath)')" />
<_WebAssemblyBCLFolder Include="$(DotNetWebAssemblyBCLPath);$(DotNetWebAssemblyBCLFacadesPath);$(DotNetWebAssemblyFrameworkPath)" />
<_WebAssemblyBCLAssembly Include="%(_WebAssemblyBCLFolder.Identity)*.dll" />
</ItemGroup>
</Target>
<Target
Name="_ResolveBlazorApplicationAssemblies"
Condition="'$(_BlazorShouldLinkApplicationAssemblies)' == ''"
Inputs="$(BlazorBuildCommonInputsCache);
@(IntermediateAssembly);
@(_BlazorDependencyInput)"
Outputs="$(BlazorResolvedAssembliesOutputPath)"
>
<ResolveBlazorRuntimeDependencies
EntryPoint="@(IntermediateAssembly)"
ApplicationDependencies="@(_BlazorDependencyInput)"
WebAssemblyBCLAssemblies="@(_WebAssemblyBCLAssembly)">
<PropertyGroup>
<_ReferencesArg Condition="'@(_BlazorDependencyInput)' != ''">--references &quot;$(BlazorResolveDependenciesFilePath)&quot;</_ReferencesArg>
<_BclParameter>--base-class-library &quot;$(MonoBaseClassLibraryPath)&quot; --base-class-library &quot;$(MonoBaseClassLibraryFacadesPath)&quot; --base-class-library &quot;$(MonoWasmFrameworkPath)&quot;</_BclParameter>
</PropertyGroup>
<WriteLinesToFile
File="$(BlazorResolveDependenciesFilePath)"
Lines="@(_BlazorDependencyInput)"
Overwrite="true" />
<Exec Command="$(BlazorBuildExe) resolve-dependencies &quot;@(IntermediateAssembly->'%(FullPath)')&quot; $(_ReferencesArg) $(_BclParameter) --output &quot;$(BlazorResolvedAssembliesOutputPath)&quot;" />
</Target>
<Target Name="_ReadResolvedBlazorApplicationAssemblies">
<ReadLinesFromFile File="$(BlazorResolvedAssembliesOutputPath)">
<Output TaskParameter="Lines" ItemName="_BlazorResolvedRuntimeDependencies"/>
</ReadLinesFromFile>
<Output TaskParameter="Dependencies" ItemName="_BlazorResolvedRuntimeDependencies" />
</ResolveBlazorRuntimeDependencies>
<ItemGroup>
<_IntermediateResolvedRuntimeDependencies Include="@(_BlazorResolvedRuntimeDependencies->'$(BlazorIntermediateResolvedApplicationAssembliesOutputPath)%(FileName)%(Extension)')" />
<FileWrites Include="$(_BlazorApplicationAssembliesCacheFile)" />
</ItemGroup>
<ItemGroup>
<FileWrites Include="$(BlazorResolvedAssembliesOutputPath)" />
<FileWrites Include="@(_IntermediateResolvedRuntimeDependencies)" />
</ItemGroup>
</Target>
<Target
Name="_IntermediateCopyBlazorApplicationAssemblies"
Inputs="@(_BlazorResolvedRuntimeDependencies)"
Outputs="@(_BlazorResolvedRuntimeDependencies->'$(BlazorIntermediateResolvedApplicationAssembliesOutputPath)%(FileName)%(Extension)')">
<Copy
SourceFiles="@(_BlazorResolvedRuntimeDependencies)"
DestinationFiles="@(_BlazorResolvedRuntimeDependencies->'$(BlazorIntermediateResolvedApplicationAssembliesOutputPath)%(FileName)%(Extension)')"
SkipUnchangedFiles="$(SkipCopyUnchangedFiles)"
OverwriteReadOnlyFiles="$(OverwriteReadOnlyFiles)"
Retries="$(CopyRetryCount)"
RetryDelayMilliseconds="$(CopyRetryDelayMilliseconds)"
UseHardlinksIfPossible="$(CreateHardLinksForCopyFilesToOutputDirectoryIfPossible)"
UseSymboliclinksIfPossible="$(CreateSymbolicLinksForCopyFilesToOutputDirectoryIfPossible)" />
</Target>
<Target
Name="_TouchBlazorApplicationAssemblies"
Inputs="$(BlazorBuildLinkerSwitchInputsCache)"
Outputs="@(_IntermediateResolvedRuntimeDependencies)">
<Touch Files="@(_IntermediateResolvedRuntimeDependencies)" ForceTouch="true" />
</Target>
<!--
Final part of the build pipeline:
* Collect the blazor application assemblies to be copied to the output and create a marker file.
* Call our CLI tool to generate the boot json if the list of assemblies has changed.
-->
<Target Name="_ResolveBlazorBootJsonInputs">
<ItemGroup>
<BlazorBootJsonInput Include="$(Configuration)" />
<BlazorBootJsonInput Include="@(BlazorItemOutput->WithMetadataValue('Type','Assembly')->'%(FullPath)')" />
<BlazorBootJsonInput Include="@(BlazorItemOutput->WithMetadataValue('Type','Pdb')->'%(FullPath)')" />
<BlazorBootJsonInput Include="@(_BlazorLinkingOption)" />
<BlazorBootJsonInput Include="$(BlazorEnableDebugging)" />
</ItemGroup>
<WriteLinesToFile
File="$(BlazorBuildBootJsonInputsCache)"
Lines="@(BlazorBootJsonInput)"
Overwrite="true"
WriteOnlyWhenDifferent="True" />
<ItemGroup>
<FileWrites Include="$(BlazorBuildBootJsonInputsCache)" />
</ItemGroup>
</Target>
<UsingTask TaskName="GenerateBlazorBootJson" AssemblyFile="$(BlazorTasksPath)" />
<Target
Name="_GenerateBlazorBootJson"
DependsOnTargets="_ResolveBlazorBootJsonInputs"
Inputs="$(BlazorBuildBootJsonInputsCache);@(_BlazorDependencyInput)"
Inputs="@(BlazorOutputWithTargetPath)"
Outputs="$(BlazorBootJsonIntermediateOutputPath)">
<ItemGroup>
<_AppReferences Include="@(BlazorItemOutput->WithMetadataValue('Type','Assembly')->'%(FileName)%(Extension)')" />
<_AppReferences Include="@(BlazorItemOutput->WithMetadataValue('Type','Pdb')->'%(FileName)%(Extension)')" Condition="'$(BlazorEnableDebugging)' == 'true'" />
<_AppReferences Include="@(BlazorOutputWithTargetPath->WithMetadataValue('Extension','.dll'))" />
<_AppReferences Include="@(BlazorOutputWithTargetPath->WithMetadataValue('Extension','.pdb'))" Condition="'$(BlazorEnableDebugging)' == 'true'" />
</ItemGroup>
<PropertyGroup>
<_LinkerEnabledFlag Condition="'$(_BlazorShouldLinkApplicationAssemblies)' != ''">--linker-enabled</_LinkerEnabledFlag>
<_ReferencesArg Condition="'@(_AppReferences)' != ''">--references &quot;$(BlazorBootJsonReferencesFilePath)&quot;</_ReferencesArg>
</PropertyGroup>
<WriteLinesToFile
File="$(BlazorBootJsonReferencesFilePath)"
Lines="@(_AppReferences)"
Overwrite="true" />
<GenerateBlazorBootJson
AssemblyPath="@(IntermediateAssembly)"
References="@(_AppReferences)"
LinkerEnabled="$(BlazorLinkOnBuild)"
OutputPath="$(BlazorBootJsonIntermediateOutputPath)" />
<Exec Command="$(BlazorBuildExe) write-boot-json &quot;@(IntermediateAssembly)&quot; $(_ReferencesArg) $(_LinkerEnabledFlag) --output &quot;$(BlazorBootJsonIntermediateOutputPath)&quot;" />
<ItemGroup Condition="Exists('$(BlazorBootJsonIntermediateOutputPath)')">
<_BlazorBootJson Include="$(BlazorBootJsonIntermediateOutputPath)" />
<_BlazorBootJsonEmbeddedContentFile Include="$(BlazorBootJsonIntermediateOutputDir)_content\**\*.*" />
<BlazorItemOutput Include="@(_BlazorBootJson)">
<TargetOutputPath>$(TargetDir)$(BlazorBootJsonOutputPath)</TargetOutputPath>
<Type>BootJson</Type>
</BlazorItemOutput>
<BlazorItemOutput Include="@(_BlazorBootJsonEmbeddedContentFile)">
<TargetOutputPath>$(TargetDir)dist/_content/%(RecursiveDir)%(FileName)%(Extension)</TargetOutputPath>
</BlazorItemOutput>
<ItemGroup>
<BlazorOutputWithTargetPath Include="$(BlazorBootJsonIntermediateOutputPath)" TargetOutputPath="$(BaseBlazorRuntimeOutputPath)$(BlazorBootJsonName)" />
<FileWrites Include="$(BlazorBootJsonIntermediateOutputPath)" />
<FileWrites Include="@(_BlazorBootJsonEmbeddedContentFile)" />
</ItemGroup>
</Target>
</Project>

View File

@ -26,9 +26,8 @@
</ContentWithTargetPath>
<!-- Publish all the 'dist' files -->
<_BlazorGCTPDIDistFiles Include="@(BlazorItemOutput->'%(TargetOutputPath)')" />
<_BlazorGCTPDI Include="@(_BlazorGCTPDIDistFiles)">
<TargetPath>$(BlazorPublishDistDir)$([MSBuild]::MakeRelative('$(TargetDir)dist\', %(Identity)))</TargetPath>
<_BlazorGCTPDI Include="%(BlazorOutputWithTargetPath.Identity)">
<TargetPath>$(AssemblyName)\%(TargetOutputPath)</TargetPath>
</_BlazorGCTPDI>
<ContentWithTargetPath Include="@(_BlazorGCTPDI)">

View File

@ -1,28 +1,41 @@
// 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 Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.IO;
using System.Text.Json;
using System.Threading.Tasks;
using Xunit;
namespace Microsoft.AspNetCore.Blazor.Build.Test
namespace Microsoft.AspNetCore.Blazor.Build
{
public class BootJsonWriterTest
{
[Fact]
public void ProducesJsonReferencingAssemblyAndDependencies()
public async Task ProducesJsonReferencingAssemblyAndDependencies()
{
// Arrange/Act
var assemblyReferences = new string[] { "MyApp.EntryPoint.dll", "System.Abc.dll", "MyApp.ClassLib.dll", };
var content = BootJsonWriter.GetBootJsonContent(
using var stream = new MemoryStream();
// Act
GenerateBlazorBootJson.WriteBootJson(
stream,
"MyApp.Entrypoint.dll",
assemblyReferences,
linkerEnabled: true);
// Assert
var parsedContent = JsonConvert.DeserializeObject<JObject>(content);
Assert.Equal("MyApp.Entrypoint.dll", parsedContent["entryAssembly"].Value<string>());
Assert.Equal(assemblyReferences, parsedContent["assemblies"].Values<string>());
stream.Position = 0;
using var parsedContent = await JsonDocument.ParseAsync(stream);
var rootElement = parsedContent.RootElement;
Assert.Equal("MyApp.Entrypoint.dll", rootElement.GetProperty("entryAssembly").GetString());
var assembliesElement = rootElement.GetProperty("assemblies");
Assert.Equal(assemblyReferences.Length, assembliesElement.GetArrayLength());
for (var i = 0; i < assemblyReferences.Length; i++)
{
Assert.Equal(assemblyReferences[i], assembliesElement[i].GetString());
}
Assert.True(rootElement.GetProperty("linkerEnabled").GetBoolean());
}
}
}

View File

@ -0,0 +1,950 @@
// 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.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Reflection.Metadata;
using System.Reflection.PortableExecutable;
using System.Text;
using System.Text.RegularExpressions;
namespace Microsoft.AspNetCore.Blazor.Build
{
internal class Assert : Xunit.Assert
{
// Matches `{filename}: error {code}: {message} [{project}]
// See https://stackoverflow.com/questions/3441452/msbuild-and-ignorestandarderrorwarningformat/5180353#5180353
private static readonly Regex ErrorRegex = new Regex(@"^(?'location'.+): error (?'errorcode'[A-Z0-9]+): (?'message'.+) \[(?'project'.+)\]$");
private static readonly Regex WarningRegex = new Regex(@"^(?'location'.+): warning (?'errorcode'[A-Z0-9]+): (?'message'.+) \[(?'project'.+)\]$");
private static readonly string[] AllowedBuildWarnings = new[]
{
"MSB3491" , // The process cannot access the file. As long as the build succeeds, we're ok.
"NETSDK1071", // "A PackageReference to 'Microsoft.NETCore.App' specified a Version ..."
};
public static void BuildPassed(MSBuildResult result, bool allowWarnings = false)
{
if (result == null)
{
throw new ArgumentNullException(nameof(result));
}
if (result.ExitCode != 0)
{
throw new BuildFailedException(result);
}
var buildWarnings = GetBuildWarnings(result)
.Where(m => !AllowedBuildWarnings.Contains(m.match.Groups["errorcode"].Value))
.Select(m => m.line);
if (!allowWarnings && buildWarnings.Any())
{
throw new BuildWarningsException(result, buildWarnings);
}
}
public static void BuildError(MSBuildResult result, string errorCode, string location = null)
{
if (result == null)
{
throw new ArgumentNullException(nameof(result));
}
// We don't really need to search line by line, I'm doing this so that it's possible/easy to debug.
var lines = result.Output.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
for (var i = 0; i < lines.Length; i++)
{
var line = lines[i];
var match = ErrorRegex.Match(line);
if (match.Success)
{
if (match.Groups["errorcode"].Value != errorCode)
{
continue;
}
if (location != null && match.Groups["location"].Value.Trim() != location)
{
continue;
}
// This is a match
return;
}
}
throw new BuildErrorMissingException(result, errorCode, location);
}
public static void BuildWarning(MSBuildResult result, string errorCode, string location = null)
{
if (result == null)
{
throw new ArgumentNullException(nameof(result));
}
// We don't really need to search line by line, I'm doing this so that it's possible/easy to debug.
foreach (var (_, match) in GetBuildWarnings(result))
{
if (match.Groups["errorcode"].Value != errorCode)
{
continue;
}
if (location != null && match.Groups["location"].Value.Trim() != location)
{
continue;
}
// This is a match
return;
}
throw new BuildErrorMissingException(result, errorCode, location);
}
private static IEnumerable<(string line, Match match)> GetBuildWarnings(MSBuildResult result)
{
var lines = result.Output.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
for (var i = 0; i < lines.Length; i++)
{
var line = lines[i];
var match = WarningRegex.Match(line);
if (match.Success)
{
yield return (line, match);
}
}
}
public static void BuildFailed(MSBuildResult result)
{
if (result == null)
{
throw new ArgumentNullException(nameof(result));
};
if (result.ExitCode == 0)
{
throw new BuildPassedException(result);
}
}
public static void BuildOutputContainsLine(MSBuildResult result, string match)
{
if (result == null)
{
throw new ArgumentNullException(nameof(result));
}
if (match == null)
{
throw new ArgumentNullException(nameof(match));
}
// We don't really need to search line by line, I'm doing this so that it's possible/easy to debug.
var lines = result.Output.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
for (var i = 0; i < lines.Length; i++)
{
var line = lines[i].Trim();
if (line == match)
{
return;
}
}
throw new BuildOutputMissingException(result, match);
}
public static void BuildOutputDoesNotContainLine(MSBuildResult result, string match)
{
if (result == null)
{
throw new ArgumentNullException(nameof(result));
}
if (match == null)
{
throw new ArgumentNullException(nameof(match));
}
// We don't really need to search line by line, I'm doing this so that it's possible/easy to debug.
var lines = result.Output.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
for (var i = 0; i < lines.Length; i++)
{
var line = lines[i].Trim();
if (line == match)
{
throw new BuildOutputContainsLineException(result, match);
}
}
}
public static void FileContains(MSBuildResult result, string filePath, string match)
{
if (result == null)
{
throw new ArgumentNullException(nameof(result));
}
filePath = Path.Combine(result.Project.DirectoryPath, filePath);
FileExists(result, filePath);
var text = File.ReadAllText(filePath);
if (text.Contains(match))
{
return;
}
throw new FileContentMissingException(result, filePath, File.ReadAllText(filePath), match);
}
public static void FileDoesNotContain(MSBuildResult result, string filePath, string match)
{
if (result == null)
{
throw new ArgumentNullException(nameof(result));
}
filePath = Path.Combine(result.Project.DirectoryPath, filePath);
FileExists(result, filePath);
var text = File.ReadAllText(filePath);
if (text.Contains(match))
{
throw new FileContentFoundException(result, filePath, File.ReadAllText(filePath), match);
}
}
public static void FileContentEquals(MSBuildResult result, string filePath, string expected)
{
if (result == null)
{
throw new ArgumentNullException(nameof(result));
}
filePath = Path.Combine(result.Project.DirectoryPath, filePath);
FileExists(result, filePath);
var actual = File.ReadAllText(filePath);
if (!actual.Equals(expected, StringComparison.Ordinal))
{
throw new FileContentNotEqualException(result, filePath, expected, actual);
}
}
public static void FileEquals(MSBuildResult result, string expected, string actual)
{
if (result == null)
{
throw new ArgumentNullException(nameof(result));
}
expected = Path.Combine(result.Project.DirectoryPath, expected);
actual = Path.Combine(result.Project.DirectoryPath, actual);
FileExists(result, expected);
FileExists(result, actual);
if (!Enumerable.SequenceEqual(File.ReadAllBytes(expected), File.ReadAllBytes(actual)))
{
throw new FilesNotEqualException(result, expected, actual);
}
}
public static void FileContainsLine(MSBuildResult result, string filePath, string match)
{
if (result == null)
{
throw new ArgumentNullException(nameof(result));
}
filePath = Path.Combine(result.Project.DirectoryPath, filePath);
FileExists(result, filePath);
var lines = File.ReadAllLines(filePath);
for (var i = 0; i < lines.Length; i++)
{
var line = lines[i].Trim();
if (line == match)
{
return;
}
}
throw new FileContentMissingException(result, filePath, File.ReadAllText(filePath), match);
}
public static void FileDoesNotContainLine(MSBuildResult result, string filePath, string match)
{
if (result == null)
{
throw new ArgumentNullException(nameof(result));
}
filePath = Path.Combine(result.Project.DirectoryPath, filePath);
FileExists(result, filePath);
var lines = File.ReadAllLines(filePath);
for (var i = 0; i < lines.Length; i++)
{
var line = lines[i].Trim();
if (line == match)
{
throw new FileContentFoundException(result, filePath, File.ReadAllText(filePath), match);
}
}
}
public static string FileExists(MSBuildResult result, params string[] paths)
{
if (result == null)
{
throw new ArgumentNullException(nameof(result));
}
var filePath = Path.Combine(result.Project.DirectoryPath, Path.Combine(paths));
if (!File.Exists(filePath))
{
throw new FileMissingException(result, filePath);
}
return filePath;
}
public static void FileCountEquals(MSBuildResult result, int expected, string directoryPath, string searchPattern)
{
if (result == null)
{
throw new ArgumentNullException(nameof(result));
}
if (directoryPath == null)
{
throw new ArgumentNullException(nameof(directoryPath));
}
if (searchPattern == null)
{
throw new ArgumentNullException(nameof(searchPattern));
}
directoryPath = Path.Combine(result.Project.DirectoryPath, directoryPath);
if (Directory.Exists(directoryPath))
{
var files = Directory.GetFiles(directoryPath, searchPattern, SearchOption.AllDirectories);
if (files.Length != expected)
{
throw new FileCountException(result, expected, directoryPath, searchPattern, files);
}
}
else if (expected > 0)
{
// directory doesn't exist, that's OK if we expected to find nothing.
throw new FileCountException(result, expected, directoryPath, searchPattern, Array.Empty<string>());
}
}
public static void FileDoesNotExist(MSBuildResult result, params string[] paths)
{
if (result == null)
{
throw new ArgumentNullException(nameof(result));
}
var filePath = Path.Combine(result.Project.DirectoryPath, Path.Combine(paths));
if (File.Exists(filePath))
{
throw new FileFoundException(result, filePath);
}
}
public static void NuspecContains(MSBuildResult result, string nuspecPath, string expected)
{
if (result == null)
{
throw new ArgumentNullException(nameof(result));
}
if (nuspecPath == null)
{
throw new ArgumentNullException(nameof(nuspecPath));
}
if (expected == null)
{
throw new ArgumentNullException(nameof(expected));
}
nuspecPath = Path.Combine(result.Project.DirectoryPath, nuspecPath);
FileExists(result, nuspecPath);
var content = File.ReadAllText(nuspecPath);
if (!content.Contains(expected))
{
throw new NuspecException(result, nuspecPath, content, expected);
}
}
public static void NuspecDoesNotContain(MSBuildResult result, string nuspecPath, string expected)
{
if (result == null)
{
throw new ArgumentNullException(nameof(result));
}
if (nuspecPath == null)
{
throw new ArgumentNullException(nameof(nuspecPath));
}
if (expected == null)
{
throw new ArgumentNullException(nameof(expected));
}
nuspecPath = Path.Combine(result.Project.DirectoryPath, nuspecPath);
FileExists(result, nuspecPath);
var content = File.ReadAllText(nuspecPath);
if (content.Contains(expected))
{
throw new NuspecFoundException(result, nuspecPath, content, expected);
}
}
// This method extracts the nupkg to a fixed directory path. To avoid the extra work of
// cleaning up after each invocation, this method accepts multiple files.
public static void NupkgContains(MSBuildResult result, string nupkgPath, params string[] filePaths)
{
if (result == null)
{
throw new ArgumentNullException(nameof(result));
}
if (nupkgPath == null)
{
throw new ArgumentNullException(nameof(nupkgPath));
}
if (filePaths == null)
{
throw new ArgumentNullException(nameof(filePaths));
}
nupkgPath = Path.Combine(result.Project.DirectoryPath, nupkgPath);
FileExists(result, nupkgPath);
var unzipped = Path.Combine(result.Project.DirectoryPath, Path.GetFileNameWithoutExtension(nupkgPath));
ZipFile.ExtractToDirectory(nupkgPath, unzipped);
foreach (var filePath in filePaths)
{
if (!File.Exists(Path.Combine(unzipped, filePath)))
{
throw new NupkgFileMissingException(result, nupkgPath, filePath);
}
}
}
// This method extracts the nupkg to a fixed directory path. To avoid the extra work of
// cleaning up after each invocation, this method accepts multiple files.
public static void NupkgDoesNotContain(MSBuildResult result, string nupkgPath, params string[] filePaths)
{
if (result == null)
{
throw new ArgumentNullException(nameof(result));
}
if (nupkgPath == null)
{
throw new ArgumentNullException(nameof(nupkgPath));
}
if (filePaths == null)
{
throw new ArgumentNullException(nameof(filePaths));
}
nupkgPath = Path.Combine(result.Project.DirectoryPath, nupkgPath);
FileExists(result, nupkgPath);
var unzipped = Path.Combine(result.Project.DirectoryPath, Path.GetFileNameWithoutExtension(nupkgPath));
ZipFile.ExtractToDirectory(nupkgPath, unzipped);
foreach (var filePath in filePaths)
{
if (File.Exists(Path.Combine(unzipped, filePath)))
{
throw new NupkgFileFoundException(result, nupkgPath, filePath);
}
}
}
public static void AssemblyContainsType(MSBuildResult result, string assemblyPath, string fullTypeName)
{
if (result == null)
{
throw new ArgumentNullException(nameof(result));
}
assemblyPath = Path.Combine(result.Project.DirectoryPath, Path.Combine(assemblyPath));
var typeNames = GetDeclaredTypeNames(assemblyPath);
Assert.Contains(fullTypeName, typeNames);
}
public static void AssemblyDoesNotContainType(MSBuildResult result, string assemblyPath, string fullTypeName)
{
if (result == null)
{
throw new ArgumentNullException(nameof(result));
}
assemblyPath = Path.Combine(result.Project.DirectoryPath, Path.Combine(assemblyPath));
var typeNames = GetDeclaredTypeNames(assemblyPath);
Assert.DoesNotContain(fullTypeName, typeNames);
}
private static IEnumerable<string> GetDeclaredTypeNames(string assemblyPath)
{
using (var file = File.OpenRead(assemblyPath))
{
var peReader = new PEReader(file);
var metadataReader = peReader.GetMetadataReader();
return metadataReader.TypeDefinitions.Where(t => !t.IsNil).Select(t =>
{
var type = metadataReader.GetTypeDefinition(t);
return metadataReader.GetString(type.Namespace) + "." + metadataReader.GetString(type.Name);
}).ToArray();
}
}
public static void AssemblyHasAttribute(MSBuildResult result, string assemblyPath, string fullTypeName)
{
if (result == null)
{
throw new ArgumentNullException(nameof(result));
}
assemblyPath = Path.Combine(result.Project.DirectoryPath, Path.Combine(assemblyPath));
var typeNames = GetAssemblyAttributes(assemblyPath);
Assert.Contains(fullTypeName, typeNames);
}
private static IEnumerable<string> GetAssemblyAttributes(string assemblyPath)
{
using (var file = File.OpenRead(assemblyPath))
{
var peReader = new PEReader(file);
var metadataReader = peReader.GetMetadataReader();
return metadataReader.CustomAttributes.Where(t => !t.IsNil).Select(t =>
{
var attribute = metadataReader.GetCustomAttribute(t);
var constructor = metadataReader.GetMemberReference((MemberReferenceHandle)attribute.Constructor);
var type = metadataReader.GetTypeReference((TypeReferenceHandle)constructor.Parent);
return metadataReader.GetString(type.Namespace) + "." + metadataReader.GetString(type.Name);
}).ToArray();
}
}
private abstract class MSBuildXunitException : Xunit.Sdk.XunitException
{
protected MSBuildXunitException(MSBuildResult result)
{
Result = result;
}
protected abstract string Heading { get; }
public MSBuildResult Result { get; }
public override string Message
{
get
{
var message = new StringBuilder();
message.AppendLine(Heading);
message.Append(Result.FileName);
message.Append(" ");
message.Append(Result.Arguments);
message.AppendLine();
message.AppendLine();
message.Append(Result.Output);
message.AppendLine();
message.Append("Exit Code:");
message.Append(Result.ExitCode);
return message.ToString();
}
}
}
private class BuildErrorMissingException : MSBuildXunitException
{
public BuildErrorMissingException(MSBuildResult result, string errorCode, string location)
: base(result)
{
ErrorCode = errorCode;
Location = location;
}
public string ErrorCode { get; }
public string Location { get; }
protected override string Heading
{
get
{
return
$"Error code '{ErrorCode}' was not found." + Environment.NewLine +
$"Looking for '{Location ?? ".*"}: error {ErrorCode}: .*'";
}
}
}
private class BuildFailedException : MSBuildXunitException
{
public BuildFailedException(MSBuildResult result)
: base(result)
{
}
protected override string Heading => "Build failed.";
}
private class BuildWarningsException : MSBuildXunitException
{
public BuildWarningsException(MSBuildResult result, IEnumerable<string> warnings)
: base(result)
{
Warnings = warnings.ToList();
}
public List<string> Warnings { get; }
protected override string Heading => "Build contains unexpected warnings: " + string.Join(Environment.NewLine, Warnings);
}
private class BuildPassedException : MSBuildXunitException
{
public BuildPassedException(MSBuildResult result)
: base(result)
{
}
protected override string Heading => "Build should have failed, but it passed.";
}
private class BuildOutputMissingException : MSBuildXunitException
{
public BuildOutputMissingException(MSBuildResult result, string match)
: base(result)
{
Match = match;
}
public string Match { get; }
protected override string Heading => $"Build did not contain the line: '{Match}'.";
}
private class BuildOutputContainsLineException : MSBuildXunitException
{
public BuildOutputContainsLineException(MSBuildResult result, string match)
: base(result)
{
Match = match;
}
public string Match { get; }
protected override string Heading => $"Build output contains the line: '{Match}'.";
}
private class FileContentFoundException : MSBuildXunitException
{
public FileContentFoundException(MSBuildResult result, string filePath, string content, string match)
: base(result)
{
FilePath = filePath;
Content = content;
Match = match;
}
public string Content { get; }
public string FilePath { get; }
public string Match { get; }
protected override string Heading
{
get
{
var builder = new StringBuilder();
builder.AppendFormat("File content of '{0}' should not contain line: '{1}'.", FilePath, Match);
builder.AppendLine();
builder.AppendLine();
builder.AppendLine(Content);
return builder.ToString();
}
}
}
private class FileContentMissingException : MSBuildXunitException
{
public FileContentMissingException(MSBuildResult result, string filePath, string content, string match)
: base(result)
{
FilePath = filePath;
Content = content;
Match = match;
}
public string Content { get; }
public string FilePath { get; }
public string Match { get; }
protected override string Heading
{
get
{
var builder = new StringBuilder();
builder.AppendFormat("File content of '{0}' did not contain the line: '{1}'.", FilePath, Match);
builder.AppendLine();
builder.AppendLine();
builder.AppendLine(Content);
return builder.ToString();
}
}
}
private class FileContentNotEqualException : MSBuildXunitException
{
public FileContentNotEqualException(MSBuildResult result, string filePath, string expected, string actual)
: base(result)
{
FilePath = filePath;
Expected = expected;
Actual = actual;
}
public string Actual { get; }
public string FilePath { get; }
public string Expected { get; }
protected override string Heading
{
get
{
var builder = new StringBuilder();
builder.AppendFormat("File content of '{0}' did not match the expected content: '{1}'.", FilePath, Expected);
builder.AppendLine();
builder.AppendLine();
builder.AppendLine(Actual);
return builder.ToString();
}
}
}
private class FilesNotEqualException : MSBuildXunitException
{
public FilesNotEqualException(MSBuildResult result, string expected, string actual)
: base(result)
{
Expected = expected;
Actual = actual;
}
public string Actual { get; }
public string Expected { get; }
protected override string Heading
{
get
{
var builder = new StringBuilder();
builder.AppendFormat("File content of '{0}' did not match the contents of '{1}'.", Expected, Actual);
builder.AppendLine();
builder.AppendLine();
builder.AppendLine(Actual);
return builder.ToString();
}
}
}
private class FileMissingException : MSBuildXunitException
{
public FileMissingException(MSBuildResult result, string filePath)
: base(result)
{
FilePath = filePath;
}
public string FilePath { get; }
protected override string Heading => $"File: '{FilePath}' was not found.";
}
private class FileCountException : MSBuildXunitException
{
public FileCountException(MSBuildResult result, int expected, string directoryPath, string searchPattern, string[] files)
: base(result)
{
Expected = expected;
DirectoryPath = directoryPath;
SearchPattern = searchPattern;
Files = files;
}
public string DirectoryPath { get; }
public int Expected { get; }
public string[] Files { get; }
public string SearchPattern { get; }
protected override string Heading
{
get
{
var heading = new StringBuilder();
heading.AppendLine($"Expected {Expected} files matching {SearchPattern} in {DirectoryPath}, found {Files.Length}.");
if (Files.Length > 0)
{
heading.AppendLine("Files:");
foreach (var file in Files)
{
heading.Append("\t");
heading.Append(file);
}
heading.AppendLine();
}
return heading.ToString();
}
}
}
private class FileFoundException : MSBuildXunitException
{
public FileFoundException(MSBuildResult result, string filePath)
: base(result)
{
FilePath = filePath;
}
public string FilePath { get; }
protected override string Heading => $"File: '{FilePath}' was found, but should not exist.";
}
private class NuspecException : MSBuildXunitException
{
public NuspecException(MSBuildResult result, string filePath, string content, string expected)
: base(result)
{
FilePath = filePath;
Content = content;
Expected = expected;
}
public string Content { get; }
public string Expected { get; }
public string FilePath { get; }
protected override string Heading
{
get
{
return
$"nuspec: '{FilePath}' did not contain the expected content." + Environment.NewLine +
Environment.NewLine +
$"expected: {Expected}" + Environment.NewLine +
Environment.NewLine +
$"actual: {Content}";
}
}
}
private class NuspecFoundException : MSBuildXunitException
{
public NuspecFoundException(MSBuildResult result, string filePath, string content, string expected)
: base(result)
{
FilePath = filePath;
Content = content;
Expected = expected;
}
public string Content { get; }
public string Expected { get; }
public string FilePath { get; }
protected override string Heading
{
get
{
return
$"nuspec: '{FilePath}' should not contain the content {Expected}." +
Environment.NewLine +
$"actual content: {Content}";
}
}
}
private class NupkgFileMissingException : MSBuildXunitException
{
public NupkgFileMissingException(MSBuildResult result, string nupkgPath, string filePath)
: base(result)
{
NupkgPath = nupkgPath;
FilePath = filePath;
}
public string FilePath { get; }
public string NupkgPath { get; }
protected override string Heading => $"File: '{FilePath}' was not found was not found in {NupkgPath}.";
}
private class NupkgFileFoundException : MSBuildXunitException
{
public NupkgFileFoundException(MSBuildResult result, string nupkgPath, string filePath)
: base(result)
{
NupkgPath = nupkgPath;
FilePath = filePath;
}
public string FilePath { get; }
public string NupkgPath { get; }
protected override string Heading => $"File: '{FilePath}' was found in {NupkgPath}.";
}
}
}

View File

@ -0,0 +1,40 @@
// 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.Threading.Tasks;
using Xunit;
namespace Microsoft.AspNetCore.Blazor.Build
{
public class BuildIncrementalismTest
{
[Fact]
public async Task Build_WithLinker_IsIncremental()
{
// Arrange
using var project = ProjectDirectory.Create("standalone");
var result = await MSBuildProcessManager.DotnetMSBuild(project);
Assert.BuildPassed(result);
var buildOutputDirectory = project.BuildOutputDirectory;
// Act
var thumbPrint = FileThumbPrint.CreateFolderThumbprint(project, project.BuildOutputDirectory);
// Assert
for (var i = 0; i < 3; i++)
{
result = await MSBuildProcessManager.DotnetMSBuild(project);
Assert.BuildPassed(result);
var newThumbPrint = FileThumbPrint.CreateFolderThumbprint(project, project.BuildOutputDirectory);
Assert.Equal(thumbPrint.Count, newThumbPrint.Count);
for (var j = 0; j < thumbPrint.Count; j++)
{
Assert.Equal(thumbPrint[j], newThumbPrint[j]);
}
}
}
}
}

View File

@ -0,0 +1,55 @@
// 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.Threading.Tasks;
using Xunit;
namespace Microsoft.AspNetCore.Blazor.Build
{
public class BuildIntegrationTest
{
[Fact]
public async Task Build_WithDefaultSettings_Works()
{
// Arrange
using var project = ProjectDirectory.Create("standalone");
var result = await MSBuildProcessManager.DotnetMSBuild(project);
Assert.BuildPassed(result);
var buildOutputDirectory = project.BuildOutputDirectory;
Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "blazor.boot.json");
Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "blazor.webassembly.js");
Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "wasm", "mono.wasm");
Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "wasm", "mono.js");
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_WithLinkOnBuildDisabled_Works()
{
// Arrange
using var project = ProjectDirectory.Create("standalone");
project.AddProjectFileContent(
@"<PropertyGroup>
<BlazorLinkOnBuild>false</BlazorLinkOnBuild>
</PropertyGroup>");
var result = await MSBuildProcessManager.DotnetMSBuild(project);
Assert.BuildPassed(result);
var buildOutputDirectory = project.BuildOutputDirectory;
Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "blazor.boot.json");
Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "blazor.webassembly.js");
Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "wasm", "mono.wasm");
Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "wasm", "mono.js");
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.
}
}
}

View File

@ -0,0 +1,74 @@
// 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.Linq;
using System.Security.Cryptography;
namespace Microsoft.AspNetCore.Blazor.Build
{
internal class FileThumbPrint : IEquatable<FileThumbPrint>
{
private FileThumbPrint(string path, DateTime lastWriteTimeUtc, string hash)
{
FilePath = path;
LastWriteTimeUtc = lastWriteTimeUtc;
Hash = hash;
}
public string FilePath { get; }
public DateTime LastWriteTimeUtc { get; }
public string Hash { get; }
public override string ToString()
{
return $"{Path.GetFileName(FilePath)}, {LastWriteTimeUtc.ToString("u")}, {Hash}";
}
/// <summary>
/// Returns a list of thumbprints for all files (recursive) in the specified directory, sorted by file paths.
/// </summary>
public static List<FileThumbPrint> CreateFolderThumbprint(ProjectDirectory project, string directoryPath, params string[] filesToIgnore)
{
directoryPath = Path.Combine(project.DirectoryPath, directoryPath);
var files = Directory.GetFiles(directoryPath).Where(p => !filesToIgnore.Contains(p));
var thumbprintLookup = new List<FileThumbPrint>();
foreach (var file in files)
{
var thumbprint = Create(file);
thumbprintLookup.Add(thumbprint);
}
thumbprintLookup.Sort(Comparer<FileThumbPrint>.Create((a, b) => StringComparer.Ordinal.Compare(a.FilePath, b.FilePath)));
return thumbprintLookup;
}
public static FileThumbPrint Create(string path)
{
byte[] hashBytes;
using (var sha1 = SHA1.Create())
using (var fileStream = File.OpenRead(path))
{
hashBytes = sha1.ComputeHash(fileStream);
}
var hash = Convert.ToBase64String(hashBytes);
var lastWriteTimeUtc = File.GetLastWriteTimeUtc(path);
return new FileThumbPrint(path, lastWriteTimeUtc, hash);
}
public bool Equals(FileThumbPrint other)
{
return
string.Equals(FilePath, other.FilePath, StringComparison.Ordinal) &&
LastWriteTimeUtc == other.LastWriteTimeUtc &&
string.Equals(Hash, other.Hash, StringComparison.Ordinal);
}
public override int GetHashCode() => LastWriteTimeUtc.GetHashCode();
}
}

View File

@ -0,0 +1,282 @@
// 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.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.CommandLineUtils;
namespace Microsoft.AspNetCore.Blazor.Build
{
internal static class MSBuildProcessManager
{
public static Task<MSBuildResult> DotnetMSBuild(
ProjectDirectory project,
string target = "Build",
string args = null)
{
var buildArgumentList = new List<string>
{
// Disable node-reuse. We don't want msbuild processes to stick around
// once the test is completed.
"/nr:false",
// Always generate a bin log for debugging purposes
"/bl",
};
buildArgumentList.Add($"/t:{target}");
buildArgumentList.Add($"/p:Configuration={project.Configuration}");
buildArgumentList.Add(args);
var buildArguments = string.Join(" ", buildArgumentList);
return MSBuildProcessManager.RunProcessAsync(project, buildArguments);
}
public static async Task<MSBuildResult> RunProcessAsync(
ProjectDirectory project,
string arguments,
TimeSpan? timeout = null)
{
var processStartInfo = new ProcessStartInfo()
{
WorkingDirectory = project.DirectoryPath,
UseShellExecute = false,
RedirectStandardError = true,
RedirectStandardOutput = true,
};
processStartInfo.FileName = DotNetMuxer.MuxerPathOrDefault();
processStartInfo.Arguments = $"msbuild {arguments}";
// Suppresses the 'Welcome to .NET Core!' output that times out tests and causes locked file issues.
// When using dotnet we're not guarunteed to run in an environment where the dotnet.exe has had its first run experience already invoked.
processStartInfo.EnvironmentVariables["DOTNET_SKIP_FIRST_TIME_EXPERIENCE"] = "true";
var processResult = await RunProcessCoreAsync(processStartInfo, timeout);
return new MSBuildResult(project, processResult.FileName, processResult.Arguments, processResult.ExitCode, processResult.Output);
}
internal static Task<ProcessResult> RunProcessCoreAsync(
ProcessStartInfo processStartInfo,
TimeSpan? timeout = null)
{
timeout = timeout ?? TimeSpan.FromSeconds(5 * 60);
var process = new Process()
{
StartInfo = processStartInfo,
EnableRaisingEvents = true,
};
var output = new StringBuilder();
var outputLock = new object();
var diagnostics = new StringBuilder();
diagnostics.AppendLine("Process execution diagnostics:");
process.ErrorDataReceived += Process_ErrorDataReceived;
process.OutputDataReceived += Process_OutputDataReceived;
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
var timeoutTask = GetTimeoutForProcess(process, timeout, diagnostics);
var waitTask = Task.Run(() =>
{
// We need to use two WaitForExit calls to ensure that all of the output/events are processed. Previously
// this code used Process.Exited, which could result in us missing some output due to the ordering of
// events.
//
// See the remarks here: https://msdn.microsoft.com/en-us/library/ty0d8k56(v=vs.110).aspx
if (!process.WaitForExit(Int32.MaxValue))
{
// unreachable - the timeoutTask will kill the process before this happens.
throw new TimeoutException();
}
process.WaitForExit();
string outputString;
lock (outputLock)
{
// This marks the end of the diagnostic info which we collect when something goes wrong.
diagnostics.AppendLine("Process output:");
// Expected output
// Process execution diagnostics:
// ...
// Process output:
outputString = diagnostics.ToString();
outputString += output.ToString();
}
var result = new ProcessResult(process.StartInfo.FileName, process.StartInfo.Arguments, process.ExitCode, outputString);
return result;
});
return Task.WhenAny<ProcessResult>(waitTask, timeoutTask).Unwrap();
void Process_ErrorDataReceived(object sender, DataReceivedEventArgs e)
{
lock (outputLock)
{
output.AppendLine(e.Data);
}
}
void Process_OutputDataReceived(object sender, DataReceivedEventArgs e)
{
lock (outputLock)
{
output.AppendLine(e.Data);
}
}
async Task<ProcessResult> GetTimeoutForProcess(Process process, TimeSpan? timeout, StringBuilder diagnostics)
{
await Task.Delay(timeout.Value);
// Don't timeout during debug sessions
while (Debugger.IsAttached)
{
Thread.Sleep(TimeSpan.FromSeconds(1));
}
if (!process.HasExited)
{
await CollectDumps(process, timeout, diagnostics);
// This is a timeout.
process.Kill();
}
throw new TimeoutException($"command '${process.StartInfo.FileName} {process.StartInfo.Arguments}' timed out after {timeout}. Output: {output.ToString()}");
}
static async Task CollectDumps(Process process, TimeSpan? timeout, StringBuilder diagnostics)
{
var procDumpProcess = await CaptureDump(process, timeout, diagnostics);
var allDotNetProcesses = Process.GetProcessesByName("dotnet");
var allDotNetChildProcessCandidates = allDotNetProcesses
.Where(p => p.StartTime >= process.StartTime && p.Id != process.Id);
if (!allDotNetChildProcessCandidates.Any())
{
diagnostics.AppendLine("Couldn't find any candidate child process.");
foreach (var dotnetProcess in allDotNetProcesses)
{
diagnostics.AppendLine($"Found dotnet process with PID {dotnetProcess.Id} and start time {dotnetProcess.StartTime}.");
}
}
foreach (var childProcess in allDotNetChildProcessCandidates)
{
diagnostics.AppendLine($"Found child process candidate '{childProcess.Id}'.");
}
var childrenProcessDumpProcesses = await Task.WhenAll(allDotNetChildProcessCandidates.Select(d => CaptureDump(d, timeout, diagnostics)));
foreach (var childProcess in childrenProcessDumpProcesses)
{
if (childProcess != null && childProcess.HasExited)
{
diagnostics.AppendLine($"ProcDump failed to run for child dotnet process candidate '{process.Id}'.");
childProcess.Kill();
}
}
if (procDumpProcess != null && procDumpProcess.HasExited)
{
diagnostics.AppendLine($"ProcDump failed to run for '{process.Id}'.");
procDumpProcess.Kill();
}
}
static async Task<Process> CaptureDump(Process process, TimeSpan? timeout, StringBuilder diagnostics)
{
var metadataAttributes = Assembly.GetExecutingAssembly()
.GetCustomAttributes<AssemblyMetadataAttribute>();
var procDumpPath = metadataAttributes
.SingleOrDefault(ama => ama.Key == "ProcDumpToolPath")?.Value;
if (string.IsNullOrEmpty(procDumpPath))
{
diagnostics.AppendLine("ProcDumpPath not defined.");
return null;
}
var procDumpExePath = Path.Combine(procDumpPath, "procdump.exe");
if (!File.Exists(procDumpExePath))
{
diagnostics.AppendLine($"Can't find procdump.exe in '{procDumpPath}'.");
return null;
}
var dumpDirectory = metadataAttributes
.SingleOrDefault(ama => ama.Key == "ArtifactsLogDir")?.Value;
if (string.IsNullOrEmpty(dumpDirectory))
{
diagnostics.AppendLine("ArtifactsLogDir not defined.");
return null;
}
if (!Directory.Exists(dumpDirectory))
{
diagnostics.AppendLine($"'{dumpDirectory}' does not exist.");
return null;
}
var procDumpPattern = Path.Combine(dumpDirectory, "HangingProcess_PROCESSNAME_PID_YYMMDD_HHMMSS.dmp");
var procDumpStartInfo = new ProcessStartInfo(
procDumpExePath,
$"-accepteula -ma {process.Id} {procDumpPattern}")
{
CreateNoWindow = true,
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true
};
var procDumpProcess = Process.Start(procDumpStartInfo);
var tcs = new TaskCompletionSource<object>();
procDumpProcess.Exited += (s, a) => tcs.TrySetResult(null);
procDumpProcess.EnableRaisingEvents = true;
await Task.WhenAny(tcs.Task, Task.Delay(timeout ?? TimeSpan.FromSeconds(30)));
return procDumpProcess;
}
}
internal class ProcessResult
{
public ProcessResult(string fileName, string arguments, int exitCode, string output)
{
FileName = fileName;
Arguments = arguments;
ExitCode = exitCode;
Output = output;
}
public string Arguments { get; }
public string FileName { get; }
public int ExitCode { get; }
public string Output { get; }
}
}
}

View File

@ -0,0 +1,28 @@
// 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.
namespace Microsoft.AspNetCore.Blazor.Build
{
internal class MSBuildResult
{
public MSBuildResult(ProjectDirectory project, string fileName, string arguments, int exitCode, string output)
{
Project = project;
FileName = fileName;
Arguments = arguments;
ExitCode = exitCode;
Output = output;
}
public ProjectDirectory Project { get; }
public string Arguments { get; }
public string FileName { get; }
public int ExitCode { get; }
public string Output { get; }
}
}

View File

@ -0,0 +1,211 @@
// 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.Linq;
using System.Reflection;
using System.Threading;
namespace Microsoft.AspNetCore.Blazor.Build
{
internal class ProjectDirectory : IDisposable
{
public bool PreserveWorkingDirectory { get; set; } = false;
private static readonly string RepoRoot = GetTestAttribute("Testing.RepoRoot");
public static ProjectDirectory Create(string projectName, string baseDirectory = "", string[] additionalProjects = null)
{
var destinationPath = Path.Combine(Path.GetTempPath(), "BlazorBuild", baseDirectory, Path.GetRandomFileName());
Directory.CreateDirectory(destinationPath);
try
{
if (Directory.EnumerateFiles(destinationPath).Any())
{
throw new InvalidOperationException($"{destinationPath} should be empty");
}
if (string.IsNullOrEmpty(RepoRoot))
{
throw new InvalidOperationException("RepoRoot was not specified.");
}
var testAppsRoot = Path.Combine(RepoRoot, "src", "Components", "Blazor", "Build", "testassets");
foreach (var project in new string[] { projectName, }.Concat(additionalProjects ?? Array.Empty<string>()))
{
var projectRoot = Path.Combine(testAppsRoot, project);
if (!Directory.Exists(projectRoot))
{
throw new InvalidOperationException($"Could not find project at '{projectRoot}'");
}
var projectDestination = Path.Combine(destinationPath, project);
var projectDestinationDir = Directory.CreateDirectory(projectDestination);
CopyDirectory(new DirectoryInfo(projectRoot), projectDestinationDir);
SetupDirectoryBuildFiles(RepoRoot, testAppsRoot, projectDestination);
}
var directoryPath = Path.Combine(destinationPath, projectName);
var projectPath = Path.Combine(directoryPath, projectName + ".csproj");
CopyRepositoryAssets(destinationPath);
return new ProjectDirectory(
destinationPath,
directoryPath,
projectPath);
}
catch
{
CleanupDirectory(destinationPath);
throw;
}
static void CopyDirectory(DirectoryInfo source, DirectoryInfo destination, bool recursive = true)
{
foreach (var file in source.EnumerateFiles())
{
file.CopyTo(Path.Combine(destination.FullName, file.Name));
}
if (!recursive)
{
return;
}
foreach (var directory in source.EnumerateDirectories())
{
if (directory.Name == "bin")
{
// Just in case someone has opened the project in an IDE or built it. We don't want to copy
// these folders.
continue;
}
var created = destination.CreateSubdirectory(directory.Name);
if (directory.Name == "obj")
{
// Copy NuGet restore assets (viz all the files at the root of the obj directory, but stop there.)
CopyDirectory(directory, created, recursive: false);
}
else
{
CopyDirectory(directory, created);
}
}
}
static void SetupDirectoryBuildFiles(string repoRoot, string testAppsRoot, string projectDestination)
{
var beforeDirectoryPropsContent =
$@"<Project>
<PropertyGroup>
<RepoRoot>{repoRoot}</RepoRoot>
</PropertyGroup>
</Project>";
File.WriteAllText(Path.Combine(projectDestination, "Before.Directory.Build.props"), beforeDirectoryPropsContent);
new List<string> { "Directory.Build.props", "Directory.Build.targets", }
.ForEach(file =>
{
var source = Path.Combine(testAppsRoot, file);
var destination = Path.Combine(projectDestination, file);
File.Copy(source, destination);
});
}
static void CopyRepositoryAssets(string projectRoot)
{
const string GlobalJsonFileName = "global.json";
var globalJsonPath = Path.Combine(RepoRoot, GlobalJsonFileName);
var destinationFile = Path.Combine(projectRoot, GlobalJsonFileName);
File.Copy(globalJsonPath, destinationFile);
}
}
protected ProjectDirectory(string solutionPath, string directoryPath, string projectFilePath)
{
SolutionPath = solutionPath;
DirectoryPath = directoryPath;
ProjectFilePath = projectFilePath;
}
public string DirectoryPath { get; }
public string ProjectFilePath { get; }
public string SolutionPath { get; }
public string TargetFramework { get; set; } = "netstandard2.1";
#if DEBUG
public string Configuration => "Debug";
#elif RELEASE
public string Configuration => "Release";
#else
#error Configuration not supported
#endif
public string IntermediateOutputDirectory => Path.Combine("obj", Configuration, TargetFramework);
public string BuildOutputDirectory => Path.Combine("bin", Configuration, TargetFramework);
public string PublishOutputDirectory => Path.Combine(BuildOutputDirectory, "publish");
internal void AddProjectFileContent(string content)
{
if (content == null)
{
throw new ArgumentNullException(nameof(content));
}
var existing = File.ReadAllText(ProjectFilePath);
var updated = existing.Replace("<!-- Test Placeholder -->", content);
File.WriteAllText(ProjectFilePath, updated);
}
public void Dispose()
{
if (PreserveWorkingDirectory)
{
Console.WriteLine($"Skipping deletion of working directory {SolutionPath}");
}
else
{
CleanupDirectory(SolutionPath);
}
}
internal static void CleanupDirectory(string filePath)
{
var tries = 5;
var sleep = TimeSpan.FromSeconds(3);
for (var i = 0; i < tries; i++)
{
try
{
Directory.Delete(filePath, recursive: true);
return;
}
catch when (i < tries - 1)
{
Console.WriteLine($"Failed to delete directory {filePath}, trying again.");
Thread.Sleep(sleep);
}
}
}
private static string GetTestAttribute(string key)
{
return typeof(ProjectDirectory).Assembly
.GetCustomAttributes<AssemblyMetadataAttribute>()
.FirstOrDefault(f => f.Key == key)
?.Value;
}
}
}

View File

@ -0,0 +1,21 @@
// 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 Xunit;
namespace Microsoft.AspNetCore.Blazor.Build
{
public class ProjectDirectoryTest
{
[Fact]
public void ProjectDirectory_IsNotSetToBePreserved()
{
// Arrange
using var project = ProjectDirectory.Create("standalone");
// Act & Assert
// This flag is only meant for local debugging and should not be set when checking in.
Assert.False(project.PreserveWorkingDirectory);
}
}
}

View File

@ -0,0 +1,69 @@
// 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.Threading.Tasks;
using Xunit;
namespace Microsoft.AspNetCore.Blazor.Build
{
public class PublishIntegrationTest
{
[Fact]
public async Task Publish_WithDefaultSettings_Works()
{
// Arrange
using var project = ProjectDirectory.Create("standalone");
var result = await MSBuildProcessManager.DotnetMSBuild(project, "Publish");
Assert.BuildPassed(result);
var publishDirectory = project.PublishOutputDirectory;
var blazorPublishDirectory = Path.Combine(publishDirectory, Path.GetFileNameWithoutExtension(project.ProjectFilePath));
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "blazor.boot.json");
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "blazor.webassembly.js");
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "wasm", "mono.wasm");
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "wasm", "mono.js");
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "_bin", "standalone.dll");
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "_bin", "Microsoft.Extensions.Logging.Abstractions.dll"); // Verify dependencies are part of the output.
// Verify static assets are in the publish directory
Assert.FileExists(result, blazorPublishDirectory, "dist", "index.html");
// Verify web.config
Assert.FileExists(result, publishDirectory, "web.config");
}
[Fact]
public async Task Publish_WithLinkOnBuildDisabled_Works()
{
// Arrange
using var project = ProjectDirectory.Create("standalone");
project.AddProjectFileContent(
@"<PropertyGroup>
<BlazorLinkOnBuild>false</BlazorLinkOnBuild>
</PropertyGroup>");
var result = await MSBuildProcessManager.DotnetMSBuild(project, "Publish");
Assert.BuildPassed(result);
var publishDirectory = project.PublishOutputDirectory;
var blazorPublishDirectory = Path.Combine(publishDirectory, Path.GetFileNameWithoutExtension(project.ProjectFilePath));
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "blazor.boot.json");
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "blazor.webassembly.js");
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "wasm", "mono.wasm");
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "wasm", "mono.js");
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "_bin", "standalone.dll");
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "_bin", "Microsoft.Extensions.Logging.Abstractions.dll"); // Verify dependencies are part of the output.
// Verify static assets are in the publish directory
Assert.FileExists(result, blazorPublishDirectory, "dist", "index.html");
// Verify web.config
Assert.FileExists(result, publishDirectory, "web.config");
}
}
}

View File

@ -25,16 +25,39 @@
<Reference Include="Microsoft.AspNetCore.Blazor.Mono" />
<Reference Include="Microsoft.AspNetCore.Mvc.Razor.Extensions" />
<Reference Include="Microsoft.AspNetCore.Razor.Language" />
<Reference Include="Microsoft.Build.Framework" />
<Reference Include="Microsoft.Build.Utilities.Core" />
<Reference Include="Microsoft.CodeAnalysis.Razor" />
<Reference Include="Microsoft.Extensions.CommandLineUtils.Sources" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\testassets\StandaloneApp\StandaloneApp.csproj" />
<Compile Include="$(SharedSourceRoot)test\SkipOnHelixAttribute.cs" />
<Compile Include="$(ComponentsSharedSourceRoot)test\**\*.cs" LinkBase="Helpers" />
</ItemGroup>
<ItemGroup>
<AssemblyAttribute Include="System.Reflection.AssemblyMetadataAttribute">
<_Parameter1>Testing.RepoRoot</_Parameter1>
<_Parameter2>$(RepoRoot)</_Parameter2>
</AssemblyAttribute>
</ItemGroup>
<Target Name="RestoreTestAssets" AfterTargets="Restore;Build" Condition="'$(DotNetBuildFromSource)' != 'true'">
<ItemGroup>
<_TestAsset Include="..\testassets\standalone\standalone.csproj" />
</ItemGroup>
<MSBuild
Projects="@(_TestAsset)"
Targets="Restore"
Properties="
RepoRoot=$(RepoRoot);
MicrosoftNetCompilersToolsetPackageVersion=$(MicrosoftNetCompilersToolsetPackageVersion);
MicrosoftNETSdkRazorPackageVersion=$(MicrosoftNETSdkRazorPackageVersion)" />
</Target>
<!-- A bit of msbuild magic to support reference resolver tests -->
<Target Name="CreateReferenceHintPathsList" AfterTargets="Build">
<ItemGroup>

View File

@ -9,7 +9,7 @@ using System.Text;
using Microsoft.AspNetCore.Testing;
using Xunit;
namespace Microsoft.AspNetCore.Blazor.Build.Test
namespace Microsoft.AspNetCore.Blazor.Build
{
public class RuntimeDependenciesResolverTest
{
@ -109,7 +109,7 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test
// Act
var paths = RuntimeDependenciesResolver
var paths = ResolveBlazorRuntimeDependencies
.ResolveRuntimeDependenciesCore(
mainAssemblyLocation,
references,

View File

@ -0,0 +1,31 @@
<Project>
<Import Project="Before.Directory.Build.props" Condition="Exists('Before.Directory.Build.props')" />
<PropertyGroup>
<RepoRoot Condition="'$(RepoRoot)' ==''">$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), global.json))\</RepoRoot>
<ComponentsRoot>$(RepoRoot)src\Components\</ComponentsRoot>
<BlazorBuildRoot>$(ComponentsRoot)Blazor\Build\src\</BlazorBuildRoot>
<ReferenceBlazorBuildFromSourceProps>$(BlazorBuildRoot)ReferenceBlazorBuildFromSource.props</ReferenceBlazorBuildFromSourceProps>
<!-- Workaround for https://github.com/aspnet/AspNetCore/issues/17308 -->
<DefaultNetCoreTargetFramework>netcoreapp3.1</DefaultNetCoreTargetFramework>
<EnableSourceLink>false</EnableSourceLink>
<DeterministicSourcePaths>false</DeterministicSourcePaths>
</PropertyGroup>
<Import Project="$(RepoRoot)eng\Versions.props" />
<ItemGroup>
<!-- Use the sample compiler \ SDK that the rest of our build uses-->
<PackageReference Include="Microsoft.Net.Compilers.Toolset"
Version="$(MicrosoftNetCompilersToolsetPackageVersion)"
PrivateAssets="all"
IsImplicitlyDefined="true" />
<PackageReference Include="Microsoft.NET.Sdk.Razor"
Version="$(MicrosoftNETSdkRazorPackageVersion)"
PrivateAssets="All"
IsImplicitlyDefined="true" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,2 @@
<Project>
</Project>

View File

@ -0,0 +1,8 @@
<Router AppAssembly="@typeof(Program).Assembly">
<Found Context="routeData">
<RouteView RouteData="@routeData"/>
</Found>
<NotFound>
<p>Sorry, there's nothing at this address.</p>
</NotFound>
</Router>

View File

@ -0,0 +1,5 @@
@page "/"
<h1>Hello, world!</h1>
Welcome to your new app.

View File

@ -0,0 +1,10 @@

namespace standalone
{
public class Program
{
public static void Main(string[] args)
{
}
}
}

View File

@ -0,0 +1,2 @@
@using Microsoft.AspNetCore.Components.Routing
@using standalone

View File

@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<Import Project="$(ReferenceBlazorBuildFromSourceProps)" />
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<RazorLangVersion>3.0</RazorLangVersion>
</PropertyGroup>
<!-- Test Placeholder -->
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components" Version="3.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Blazor.Mono" Version="$(MicrosoftAspNetCoreBlazorMonoPackageVersion)" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,24 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>standalone</title>
<base href="/" />
<link href="css/bootstrap/bootstrap.min.css" rel="stylesheet" />
<link href="css/site.css" rel="stylesheet" />
</head>
<body>
<app>Loading...</app>
<div id="blazor-error-ui">
An unhandled error has occurred.
<a href="" class="reload">Reload</a>
<a class="dismiss">🗙</a>
</div>
<script src="_framework/blazor.webassembly.js"></script>
</body>
</html>

View File

@ -1,3 +1,2 @@
node_modules/
dist/Debug/
dist/Release/blazor.webassembly.js

View File

@ -21,6 +21,8 @@
Private="false" />
</ItemGroup>
<Target Name="GetTargetPath" />
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), Directory.Build.targets))\Directory.Build.targets" />
</Project>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long