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:
parent
a42a40d487
commit
d6c88a36ff
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="..\..\..\Shared\src\JsonSerializerOptionsProvider.cs" />
|
||||
</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>
|
||||
<_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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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.
|
||||
|
|
@ -6,8 +6,10 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<BlazorToolsDir Condition="'$(BlazorToolsDir)' == ''">$(MSBuildThisFileDirectory)../tools/</BlazorToolsDir>
|
||||
<BlazorBuildExe>dotnet "$(BlazorToolsDir)Microsoft.AspNetCore.Blazor.Build.dll"</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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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 "%(FullPath)"')" />
|
||||
<_BlazorFolderLookupPaths Include="@(_MonoBaseClassLibraryFolder->'-d "%(Identity)"')" />
|
||||
<_BlazorFolderLookupPaths Include="@(_WebAssemblyBCLFolder->'-d "%(Identity)"')" />
|
||||
|
||||
<!-- For linkable assemblies, add their directories as lookup paths -->
|
||||
<_BlazorFolderLookupPaths Condition="'%(_BlazorDependencyAssembly.IsLinkable)' == 'true'" Include="@(_BlazorDependencyAssembly->'-d "%(RelativeDirNoTrailingSlash)"')" />
|
||||
|
|
@ -446,212 +197,92 @@
|
|||
<_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 "$(MonoLinkerPath)" $(_BlazorLinkerAdditionalOptions) @(_BlazorFolderLookupPaths, ' ') -o "$(BlazorIntermediateLinkerOutputPath)" @(_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
|
||||
from the BCL referenced by the app the nuget package into the _framework/_bin folder.
|
||||
The only thing we need to do here is collect the list of items that will go into _framework/_bin.
|
||||
-->
|
||||
|
||||
<ItemGroup>
|
||||
<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)')" />
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
|
||||
<Target
|
||||
Name="_ResolveBlazorApplicationAssemblies"
|
||||
Condition="'$(_BlazorShouldLinkApplicationAssemblies)' == ''"
|
||||
Inputs="$(BlazorBuildCommonInputsCache);
|
||||
@(IntermediateAssembly);
|
||||
@(_BlazorDependencyInput)"
|
||||
Outputs="$(BlazorResolvedAssembliesOutputPath)"
|
||||
>
|
||||
|
||||
<PropertyGroup>
|
||||
<_ReferencesArg Condition="'@(_BlazorDependencyInput)' != ''">--references "$(BlazorResolveDependenciesFilePath)"</_ReferencesArg>
|
||||
<_BclParameter>--base-class-library "$(MonoBaseClassLibraryPath)" --base-class-library "$(MonoBaseClassLibraryFacadesPath)" --base-class-library "$(MonoWasmFrameworkPath)"</_BclParameter>
|
||||
</PropertyGroup>
|
||||
|
||||
<WriteLinesToFile
|
||||
File="$(BlazorResolveDependenciesFilePath)"
|
||||
Lines="@(_BlazorDependencyInput)"
|
||||
Overwrite="true" />
|
||||
|
||||
<Exec Command="$(BlazorBuildExe) resolve-dependencies "@(IntermediateAssembly->'%(FullPath)')" $(_ReferencesArg) $(_BclParameter) --output "$(BlazorResolvedAssembliesOutputPath)"" />
|
||||
|
||||
</Target>
|
||||
|
||||
<Target Name="_ReadResolvedBlazorApplicationAssemblies">
|
||||
|
||||
<ReadLinesFromFile File="$(BlazorResolvedAssembliesOutputPath)">
|
||||
<Output TaskParameter="Lines" ItemName="_BlazorResolvedRuntimeDependencies"/>
|
||||
</ReadLinesFromFile>
|
||||
|
||||
<ItemGroup>
|
||||
<_IntermediateResolvedRuntimeDependencies Include="@(_BlazorResolvedRuntimeDependencies->'$(BlazorIntermediateResolvedApplicationAssembliesOutputPath)%(FileName)%(Extension)')" />
|
||||
</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)" />
|
||||
<_WebAssemblyBCLFolder Include="$(DotNetWebAssemblyBCLPath);$(DotNetWebAssemblyBCLFacadesPath);$(DotNetWebAssemblyFrameworkPath)" />
|
||||
<_WebAssemblyBCLAssembly Include="%(_WebAssemblyBCLFolder.Identity)*.dll" />
|
||||
</ItemGroup>
|
||||
|
||||
<WriteLinesToFile
|
||||
File="$(BlazorBuildBootJsonInputsCache)"
|
||||
Lines="@(BlazorBootJsonInput)"
|
||||
Overwrite="true"
|
||||
WriteOnlyWhenDifferent="True" />
|
||||
<ResolveBlazorRuntimeDependencies
|
||||
EntryPoint="@(IntermediateAssembly)"
|
||||
ApplicationDependencies="@(_BlazorDependencyInput)"
|
||||
WebAssemblyBCLAssemblies="@(_WebAssemblyBCLAssembly)">
|
||||
|
||||
<Output TaskParameter="Dependencies" ItemName="_BlazorResolvedRuntimeDependencies" />
|
||||
</ResolveBlazorRuntimeDependencies>
|
||||
|
||||
<ItemGroup>
|
||||
<FileWrites Include="$(BlazorBuildBootJsonInputsCache)" />
|
||||
<FileWrites Include="$(_BlazorApplicationAssembliesCacheFile)" />
|
||||
</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 "$(BlazorBootJsonReferencesFilePath)"</_ReferencesArg>
|
||||
</PropertyGroup>
|
||||
|
||||
<WriteLinesToFile
|
||||
File="$(BlazorBootJsonReferencesFilePath)"
|
||||
Lines="@(_AppReferences)"
|
||||
Overwrite="true" />
|
||||
<GenerateBlazorBootJson
|
||||
AssemblyPath="@(IntermediateAssembly)"
|
||||
References="@(_AppReferences)"
|
||||
LinkerEnabled="$(BlazorLinkOnBuild)"
|
||||
OutputPath="$(BlazorBootJsonIntermediateOutputPath)" />
|
||||
|
||||
<Exec Command="$(BlazorBuildExe) write-boot-json "@(IntermediateAssembly)" $(_ReferencesArg) $(_LinkerEnabledFlag) --output "$(BlazorBootJsonIntermediateOutputPath)"" />
|
||||
|
||||
<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>
|
||||
|
|
|
|||
|
|
@ -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)">
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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}.";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
<Project>
|
||||
</Project>
|
||||
|
|
@ -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>
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
@page "/"
|
||||
|
||||
<h1>Hello, world!</h1>
|
||||
|
||||
Welcome to your new app.
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
|
||||
namespace standalone
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
@using Microsoft.AspNetCore.Components.Routing
|
||||
@using standalone
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -1,3 +1,2 @@
|
|||
node_modules/
|
||||
dist/Debug/
|
||||
dist/Release/blazor.webassembly.js
|
||||
|
|
@ -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
Loading…
Reference in New Issue