[Fixes #66] Adds linking support to the build

* Switch the tasks used to generate the blazor output to be MSBuild based.
* Package the optimized mono runtime and the BCL inside a nuget package.
* Add opt-in support for linking the application on build.
* Make the whole build process incremental.
This commit is contained in:
Javier Calvarro Nelson 2018-02-07 13:44:30 -08:00 committed by Steve Sanderson
parent f56fc1be3d
commit 700c2203c6
49 changed files with 1146 additions and 1643 deletions

View File

@ -16,13 +16,13 @@ test: 'off'
deploy: 'off'
os: Visual Studio 2017 Preview
build_script:
- build.cmd /p:SkipTests=true
- build.cmd /p:SkipTests=true /p:BlazorOutputStatistics=true
before_test:
- choco install googlechrome --ignore-checksum
- npm install -g selenium-standalone
- selenium-standalone install
- ps: $SeleniumProcess = Start-Process "selenium-standalone" -ArgumentList "start" -PassThru
test_script:
- build.cmd /t:Test /p:BlazorAllTests=true
- build.cmd /t:Test /p:BlazorAllTests=true /p:BlazorOutputStatistics=true
after_test:
- ps: Stop-Process -Id $SeleniumProcess.Id

1
.gitignore vendored
View File

@ -5,3 +5,4 @@ obj/
launchSettings.json
artifacts/
msbuild.binlog
.vscode/

View File

@ -36,6 +36,6 @@ install:
- curl -sSL https://dot.net/v1/dotnet-install.sh | bash /dev/stdin --channel Current --version latest --install-dir "$DOTNET_INSTALL_DIR"
- export PATH="$DOTNET_INSTALL_DIR:$PATH"
script:
- ./build.sh /p:SkipTests=true
- ./build.sh /p:SkipTests=true /p:BlazorOutputStatistics=true
- selenium-standalone start &
- ./build.sh /t:Test /p:BlazorAllTests=true

View File

@ -12,16 +12,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "mono", "mono", "{7B5CAAB1-A
src\mono\mono.targets = src\mono\mono.targets
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Blazor.Mono", "src\Microsoft.AspNetCore.Blazor.Mono\Microsoft.AspNetCore.Blazor.Mono.csproj", "{39FEC72D-AF52-47A3-B63D-7BF0E4335248}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MonoSanity", "samples\MonoSanity\MonoSanity.csproj", "{7C53BB6B-5906-4753-B507-C9FCC2F7E5B7}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Blazor.Server", "src\Microsoft.AspNetCore.Blazor.Server\Microsoft.AspNetCore.Blazor.Server.csproj", "{5A694793-3257-4D37-BB74-4A41B3894685}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{ADA3AE29-F6DE-49F6-8C7C-B321508CAE8E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Blazor.Mono.Test", "test\Microsoft.AspNetCore.Blazor.Mono.Test\Microsoft.AspNetCore.Blazor.Mono.Test.csproj", "{118484D3-3993-45CE-97C1-6F28A517529B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Blazor.E2ETest", "test\Microsoft.AspNetCore.Blazor.E2ETest\Microsoft.AspNetCore.Blazor.E2ETest.csproj", "{5BC2A10D-B6CA-43AE-B73C-2A41AE1039F9}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MonoSanityClient", "samples\MonoSanityClient\MonoSanityClient.csproj", "{06AAAE9E-96DE-4574-97DA-9C4C7D9FE990}"
@ -40,10 +36,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StandaloneApp", "samples\St
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "MonoSanity", "MonoSanity", "{2A076721-6081-4517-8329-B9E5110D6DAC}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Blazor.Common", "src\Microsoft.AspNetCore.Blazor.Common\Microsoft.AspNetCore.Blazor.Common.csproj", "{21EF76A4-63CC-455D-907C-F86C9E442CEC}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Blazor.Common.Test", "test\Microsoft.AspNetCore.Blazor.Common.Test\Microsoft.AspNetCore.Blazor.Common.Test.csproj", "{7F0BF3EA-6985-49F6-8070-0BBA41448BB0}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Blazor.Build.Test", "test\Microsoft.AspNetCore.Blazor.Build.Test\Microsoft.AspNetCore.Blazor.Build.Test.csproj", "{709C7EBE-EB93-4F6D-9491-D714B0D2E898}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Blazor.Build", "src\Microsoft.AspNetCore.Blazor.Build\Microsoft.AspNetCore.Blazor.Build.csproj", "{8B3D0F1C-0E38-4E6D-BFF1-C4FDA0CD9815}"
@ -103,14 +95,6 @@ Global
ReleaseNoVSIX|Any CPU = ReleaseNoVSIX|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{39FEC72D-AF52-47A3-B63D-7BF0E4335248}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{39FEC72D-AF52-47A3-B63D-7BF0E4335248}.Debug|Any CPU.Build.0 = Debug|Any CPU
{39FEC72D-AF52-47A3-B63D-7BF0E4335248}.DebugNoVSIX|Any CPU.ActiveCfg = Debug|Any CPU
{39FEC72D-AF52-47A3-B63D-7BF0E4335248}.DebugNoVSIX|Any CPU.Build.0 = Debug|Any CPU
{39FEC72D-AF52-47A3-B63D-7BF0E4335248}.Release|Any CPU.ActiveCfg = Release|Any CPU
{39FEC72D-AF52-47A3-B63D-7BF0E4335248}.Release|Any CPU.Build.0 = Release|Any CPU
{39FEC72D-AF52-47A3-B63D-7BF0E4335248}.ReleaseNoVSIX|Any CPU.ActiveCfg = Release|Any CPU
{39FEC72D-AF52-47A3-B63D-7BF0E4335248}.ReleaseNoVSIX|Any CPU.Build.0 = Release|Any CPU
{7C53BB6B-5906-4753-B507-C9FCC2F7E5B7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7C53BB6B-5906-4753-B507-C9FCC2F7E5B7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7C53BB6B-5906-4753-B507-C9FCC2F7E5B7}.DebugNoVSIX|Any CPU.ActiveCfg = Debug|Any CPU
@ -127,14 +111,6 @@ Global
{5A694793-3257-4D37-BB74-4A41B3894685}.Release|Any CPU.Build.0 = Release|Any CPU
{5A694793-3257-4D37-BB74-4A41B3894685}.ReleaseNoVSIX|Any CPU.ActiveCfg = Release|Any CPU
{5A694793-3257-4D37-BB74-4A41B3894685}.ReleaseNoVSIX|Any CPU.Build.0 = Release|Any CPU
{118484D3-3993-45CE-97C1-6F28A517529B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{118484D3-3993-45CE-97C1-6F28A517529B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{118484D3-3993-45CE-97C1-6F28A517529B}.DebugNoVSIX|Any CPU.ActiveCfg = Debug|Any CPU
{118484D3-3993-45CE-97C1-6F28A517529B}.DebugNoVSIX|Any CPU.Build.0 = Debug|Any CPU
{118484D3-3993-45CE-97C1-6F28A517529B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{118484D3-3993-45CE-97C1-6F28A517529B}.Release|Any CPU.Build.0 = Release|Any CPU
{118484D3-3993-45CE-97C1-6F28A517529B}.ReleaseNoVSIX|Any CPU.ActiveCfg = Release|Any CPU
{118484D3-3993-45CE-97C1-6F28A517529B}.ReleaseNoVSIX|Any CPU.Build.0 = Release|Any CPU
{5BC2A10D-B6CA-43AE-B73C-2A41AE1039F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5BC2A10D-B6CA-43AE-B73C-2A41AE1039F9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5BC2A10D-B6CA-43AE-B73C-2A41AE1039F9}.DebugNoVSIX|Any CPU.ActiveCfg = Debug|Any CPU
@ -191,22 +167,6 @@ Global
{B241434A-1642-44CC-AE9A-2012B5C5BD02}.Release|Any CPU.Build.0 = Release|Any CPU
{B241434A-1642-44CC-AE9A-2012B5C5BD02}.ReleaseNoVSIX|Any CPU.ActiveCfg = Release|Any CPU
{B241434A-1642-44CC-AE9A-2012B5C5BD02}.ReleaseNoVSIX|Any CPU.Build.0 = Release|Any CPU
{21EF76A4-63CC-455D-907C-F86C9E442CEC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{21EF76A4-63CC-455D-907C-F86C9E442CEC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{21EF76A4-63CC-455D-907C-F86C9E442CEC}.DebugNoVSIX|Any CPU.ActiveCfg = Debug|Any CPU
{21EF76A4-63CC-455D-907C-F86C9E442CEC}.DebugNoVSIX|Any CPU.Build.0 = Debug|Any CPU
{21EF76A4-63CC-455D-907C-F86C9E442CEC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{21EF76A4-63CC-455D-907C-F86C9E442CEC}.Release|Any CPU.Build.0 = Release|Any CPU
{21EF76A4-63CC-455D-907C-F86C9E442CEC}.ReleaseNoVSIX|Any CPU.ActiveCfg = Release|Any CPU
{21EF76A4-63CC-455D-907C-F86C9E442CEC}.ReleaseNoVSIX|Any CPU.Build.0 = Release|Any CPU
{7F0BF3EA-6985-49F6-8070-0BBA41448BB0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7F0BF3EA-6985-49F6-8070-0BBA41448BB0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7F0BF3EA-6985-49F6-8070-0BBA41448BB0}.DebugNoVSIX|Any CPU.ActiveCfg = Debug|Any CPU
{7F0BF3EA-6985-49F6-8070-0BBA41448BB0}.DebugNoVSIX|Any CPU.Build.0 = Debug|Any CPU
{7F0BF3EA-6985-49F6-8070-0BBA41448BB0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7F0BF3EA-6985-49F6-8070-0BBA41448BB0}.Release|Any CPU.Build.0 = Release|Any CPU
{7F0BF3EA-6985-49F6-8070-0BBA41448BB0}.ReleaseNoVSIX|Any CPU.ActiveCfg = Release|Any CPU
{7F0BF3EA-6985-49F6-8070-0BBA41448BB0}.ReleaseNoVSIX|Any CPU.Build.0 = Release|Any CPU
{709C7EBE-EB93-4F6D-9491-D714B0D2E898}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{709C7EBE-EB93-4F6D-9491-D714B0D2E898}.Debug|Any CPU.Build.0 = Debug|Any CPU
{709C7EBE-EB93-4F6D-9491-D714B0D2E898}.DebugNoVSIX|Any CPU.ActiveCfg = Debug|Any CPU
@ -347,10 +307,8 @@ Global
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{7B5CAAB1-A3EB-44F7-87E3-A13ED89FC17D} = {B867E038-B3CE-43E3-9292-61568C46CDEB}
{39FEC72D-AF52-47A3-B63D-7BF0E4335248} = {B867E038-B3CE-43E3-9292-61568C46CDEB}
{7C53BB6B-5906-4753-B507-C9FCC2F7E5B7} = {2A076721-6081-4517-8329-B9E5110D6DAC}
{5A694793-3257-4D37-BB74-4A41B3894685} = {B867E038-B3CE-43E3-9292-61568C46CDEB}
{118484D3-3993-45CE-97C1-6F28A517529B} = {ADA3AE29-F6DE-49F6-8C7C-B321508CAE8E}
{5BC2A10D-B6CA-43AE-B73C-2A41AE1039F9} = {ADA3AE29-F6DE-49F6-8C7C-B321508CAE8E}
{06AAAE9E-96DE-4574-97DA-9C4C7D9FE990} = {2A076721-6081-4517-8329-B9E5110D6DAC}
{BB34336F-E68E-4411-9805-CAAA919F5EA1} = {B867E038-B3CE-43E3-9292-61568C46CDEB}
@ -360,8 +318,6 @@ Global
{7FD8C650-74B3-4153-AEA1-00F4F6AF393D} = {B867E038-B3CE-43E3-9292-61568C46CDEB}
{B241434A-1642-44CC-AE9A-2012B5C5BD02} = {F5FDD4E5-6A52-4A86-BE5E-5E42CB1DC8DA}
{2A076721-6081-4517-8329-B9E5110D6DAC} = {F5FDD4E5-6A52-4A86-BE5E-5E42CB1DC8DA}
{21EF76A4-63CC-455D-907C-F86C9E442CEC} = {B867E038-B3CE-43E3-9292-61568C46CDEB}
{7F0BF3EA-6985-49F6-8070-0BBA41448BB0} = {ADA3AE29-F6DE-49F6-8C7C-B321508CAE8E}
{709C7EBE-EB93-4F6D-9491-D714B0D2E898} = {ADA3AE29-F6DE-49F6-8C7C-B321508CAE8E}
{8B3D0F1C-0E38-4E6D-BFF1-C4FDA0CD9815} = {B867E038-B3CE-43E3-9292-61568C46CDEB}
{8A19B1CE-9B62-4440-93B3-152DDBB39D0A} = {B867E038-B3CE-43E3-9292-61568C46CDEB}

View File

@ -3,6 +3,7 @@
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<OutputType>Exe</OutputType>
<BlazorLinkOnBuild>true</BlazorLinkOnBuild>
</PropertyGroup>
<ItemGroup>

View File

@ -1,8 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk.Razor">
<Project Sdk="Microsoft.NET.Sdk.Razor" TreatAsLocalProperty="BlazorLinkOnBuild">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<IsPackable>false</IsPackable>
<BlazorLinkOnBuild>false</BlazorLinkOnBuild>
</PropertyGroup>
<!-- Local alternative to <PackageReference Include="Microsoft.AspNetCore.Blazor.Build" /> -->

View File

@ -2,7 +2,7 @@
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<BlazorLinkOnBuild>true</BlazorLinkOnBuild>
<!-- Local alternative to <RunArguments>blazor serve</RunArguments> -->
<RunCommand>dotnet</RunCommand>
<RunArguments>run --project ../../src/Microsoft.AspNetCore.Blazor.Cli serve</RunArguments>

View File

@ -1,15 +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.Internal.Common.FileProviders;
using Microsoft.Extensions.FileProviders;
namespace Microsoft.AspNetCore.Blazor.Browser.JS
{
public static class BlazorBrowserFileProvider
{
public static IFileProvider Instance = new EmbeddedResourceFileProvider(
typeof(BlazorBrowserFileProvider).Assembly,
"blazor.");
}
}

View File

@ -15,10 +15,6 @@
<WebpackInputs Include="**\*.ts" Exclude="node_modules\**" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Microsoft.AspNetCore.Blazor.Common\Microsoft.AspNetCore.Blazor.Common.csproj" />
</ItemGroup>
<Import Project="..\Microsoft.AspNetCore.Blazor.BuildTools\ReferenceFromSource.props" />
<Target Name="EnsureNpmRestored" Condition="!Exists('node_modules')">

View File

@ -1830,15 +1830,6 @@
"xtend": "4.0.1"
}
},
"string_decoder": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
"integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==",
"dev": true,
"requires": {
"safe-buffer": "5.1.1"
}
},
"string-width": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
@ -1872,6 +1863,15 @@
}
}
},
"string_decoder": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
"integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==",
"dev": true,
"requires": {
"safe-buffer": "5.1.1"
}
},
"strip-ansi": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",

View File

@ -1,43 +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.Core;
using Microsoft.Extensions.CommandLineUtils;
using System;
namespace Microsoft.AspNetCore.Blazor.Build.Cli.Commands
{
internal class BuildCommand
{
public static void Command(CommandLineApplication command)
{
var clientAssemblyPath = command.Argument("assembly",
"Specifies the assembly for the Blazor application.");
var webRootPath = command.Option("--webroot",
"Specifies the path to the directory containing static files to be served",
CommandOptionType.SingleValue);
command.OnExecute(() =>
{
if (string.IsNullOrEmpty(clientAssemblyPath.Value))
{
Console.WriteLine($"ERROR: No value specified for required argument '{clientAssemblyPath.Name}'.");
return 1;
}
try
{
Console.WriteLine($"Building Blazor app from {clientAssemblyPath.Value}...");
AppBuilder.Execute(clientAssemblyPath.Value, webRootPath.HasValue() ? webRootPath.Value() : null);
return 0;
}
catch (Exception ex)
{
Console.WriteLine($"ERROR: {ex.Message}");
Console.WriteLine(ex.StackTrace);
return 1;
}
});
}
}
}

View File

@ -0,0 +1,51 @@
// 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 Microsoft.Extensions.CommandLineUtils;
namespace Microsoft.AspNetCore.Blazor.Build.Cli.Commands
{
internal class BuildIndexHtmlCommand
{
public static void Command(CommandLineApplication command)
{
var clientPage = command.Option("--html-page",
"Path to the HTML Page containing the Blazor bootstrap script tag.",
CommandOptionType.SingleValue);
var references = command.Option("--reference",
"The path from the _bin folder to a given referenced dll file (Typically just the dll name)",
CommandOptionType.MultipleValue);
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.");
command.OnExecute(() =>
{
if (string.IsNullOrEmpty(mainAssemblyPath.Value) ||
!clientPage.HasValue() || !references.HasValue() || !outputPath.HasValue())
{
command.ShowHelp(command.Name);
return 1;
}
try
{
IndexHtmlWriter.UpdateIndex(clientPage.Value(), mainAssemblyPath.Value, references.Values.ToArray(), outputPath.Value());
return 0;
}
catch (Exception ex)
{
Console.WriteLine($"ERROR: {ex.Message}");
Console.WriteLine(ex.StackTrace);
return 1;
}
});
}
}
}

View File

@ -0,0 +1,56 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.Extensions.CommandLineUtils;
namespace Microsoft.AspNetCore.Blazor.Build.Cli.Commands
{
class ResolveRuntimeDependenciesCommand
{
public static void Command(CommandLineApplication command)
{
var references = command.Option("--reference",
"Full path to a referenced assembly file",
CommandOptionType.MultipleValue);
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
{
RuntimeDependenciesResolver.ResolveRuntimeDependencies(
mainAssemblyPath.Value,
references.Values.ToArray(),
baseClassLibrary.Values.ToArray(),
outputPath.Value());
return 0;
}
catch (Exception ex)
{
Console.WriteLine($"ERROR: {ex.Message}");
Console.WriteLine(ex.StackTrace);
return 1;
}
});
}
}
}

View File

@ -16,7 +16,8 @@ namespace Microsoft.AspNetCore.Blazor.Build
};
app.HelpOption("-?|-h|--help");
app.Command("build", BuildCommand.Command);
app.Command("build", BuildIndexHtmlCommand.Command);
app.Command("resolve-dependencies", ResolveRuntimeDependenciesCommand.Command);
if (args.Length > 0)
{

View File

@ -1,18 +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.Core.FileSystem;
using System.IO;
namespace Microsoft.AspNetCore.Blazor.Build.Core
{
internal static class AppBuilder
{
internal static void Execute(string assemblyPath, string webRootPath)
{
var clientFileSystem = new ClientFileProvider(assemblyPath, webRootPath);
var distDirPath = Path.Combine(Path.GetDirectoryName(assemblyPath), "dist");
FileUtil.WriteFileProviderToDisk(clientFileSystem, distDirPath, clean: true);
}
}
}

View File

@ -1,80 +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.Internal.Common.FileProviders;
using Microsoft.Extensions.FileProviders;
using Mono.Cecil;
using System;
using System.Collections.Generic;
using System.IO;
namespace Microsoft.AspNetCore.Blazor.Build.Core.FileSystem
{
internal class ClientFileProvider : CompositeMountedFileProvider
{
public ClientFileProvider(string clientAssemblyPath, string webRootPath)
: base(GetContents(clientAssemblyPath, webRootPath))
{
}
private static (string, IFileProvider)[] GetContents(string clientAssemblyPath, string webRootPath)
{
var fileProviders = new List<(string, IFileProvider)>();
// There must always be a client assembly, and we always supply a /_framework
// directory containing everything needed to execute it
if (!File.Exists(clientAssemblyPath))
{
throw new FileNotFoundException($"Could not find client assembly file at '{clientAssemblyPath}'.", clientAssemblyPath);
}
var frameworkFileProvider = new FrameworkFileProvider(clientAssemblyPath);
fileProviders.Add(("/_framework", frameworkFileProvider));
// The web root directory is optional. If it exists and contains /index.html, then
// we will inject the relevant <script> tag and supply that file. Otherwise, we just
// don't supply an /index.html file.
if (TryCreateIndexHtmlFileProvider(
webRootPath, clientAssemblyPath, frameworkFileProvider, out var indexHtmlFileProvider))
{
fileProviders.Add(("/", indexHtmlFileProvider));
}
return fileProviders.ToArray();
}
private static bool TryCreateIndexHtmlFileProvider(
string webRootPath, string assemblyPath, IFileProvider frameworkFileProvider, out IFileProvider result)
{
if (!string.IsNullOrEmpty(webRootPath))
{
var path = Path.Combine(webRootPath, "index.html");
if (File.Exists(path))
{
var template = File.ReadAllText(path);
var assemblyName = Path.GetFileNameWithoutExtension(assemblyPath);
var assemblyEntryPoint = GetAssemblyEntryPoint(assemblyPath);
var binFiles = frameworkFileProvider.GetDirectoryContents("/_bin");
result = new IndexHtmlFileProvider(template, assemblyName, assemblyEntryPoint, binFiles);
return true;
}
}
result = null;
return false;
}
private static string GetAssemblyEntryPoint(string assemblyPath)
{
using (var assemblyDefinition = AssemblyDefinition.ReadAssembly(assemblyPath))
{
var entryPoint = assemblyDefinition.EntryPoint;
if (entryPoint == null)
{
throw new ArgumentException($"The assembly at {assemblyPath} has no specified entry point.");
}
return $"{entryPoint.DeclaringType.FullName}::{entryPoint.Name}";
}
}
}
}

View File

@ -1,36 +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.Browser.JS;
using Microsoft.AspNetCore.Blazor.Internal.Common.FileProviders;
using Microsoft.AspNetCore.Blazor.Mono;
using Microsoft.Extensions.FileProviders;
using System.IO;
namespace Microsoft.AspNetCore.Blazor.Build.Core.FileSystem
{
internal class FrameworkFileProvider : CompositeMountedFileProvider
{
public FrameworkFileProvider(string clientAssemblyPath)
: base(GetContents(clientAssemblyPath))
{
}
private static (string, IFileProvider)[] GetContents(string clientAssemblyPath)
{
return new[]
{
("/", MonoStaticFileProvider.JsFiles),
("/", BlazorBrowserFileProvider.Instance),
("/_bin", CreateBinDirFileProvider(clientAssemblyPath))
};
}
private static IFileProvider CreateBinDirFileProvider(string clientAssemblyPath)
=> new ReferencedAssemblyFileProvider(
Path.GetFileNameWithoutExtension(clientAssemblyPath),
new ReferencedAssemblyResolver(
MonoStaticFileProvider.BclFiles,
Path.GetDirectoryName(clientAssemblyPath)));
}
}

View File

@ -1,68 +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.IO;
using System.Collections.Generic;
using System.Linq;
using Mono.Cecil;
using Microsoft.AspNetCore.Blazor.Internal.Common.FileProviders;
namespace Microsoft.AspNetCore.Blazor.Build.Core.FileSystem
{
internal class ReferencedAssemblyFileProvider : InMemoryFileProvider
{
public ReferencedAssemblyFileProvider(string rootAssemblyName, ReferencedAssemblyResolver resolver)
: base(ComputeContents(rootAssemblyName, resolver))
{
}
private static IEnumerable<(string, byte[])> ComputeContents(
string rootAssemblyName,
ReferencedAssemblyResolver resolver)
{
var foundAssemblies = new Dictionary<string, ReferencedAssemblyInfo>();
AddWithReferencesRecursive(rootAssemblyName, resolver, foundAssemblies);
return foundAssemblies.Values.Select(assembly => (
$"/{assembly.Definition.Name.Name}.dll",
assembly.Data));
}
private static void AddWithReferencesRecursive(
string name,
ReferencedAssemblyResolver resolver,
IDictionary<string, ReferencedAssemblyInfo> results)
{
if (resolver.TryResolve(name, out var assemblyBytes))
{
var assemblyInfo = new ReferencedAssemblyInfo(assemblyBytes);
results.Add(assemblyInfo.Definition.Name.Name, assemblyInfo);
var childReferencesToAdd = assemblyInfo.Definition.Modules
.SelectMany(module => module.AssemblyReferences)
.Select(childReference => childReference.Name)
.Where(childReferenceName => !results.ContainsKey(childReferenceName));
foreach (var childReferenceName in childReferencesToAdd)
{
AddWithReferencesRecursive(childReferenceName, resolver, results);
}
}
}
private class ReferencedAssemblyInfo
{
public byte[] Data { get; }
public AssemblyDefinition Definition { get; }
public ReferencedAssemblyInfo(byte[] rawData)
{
Data = rawData;
using (var ms = new MemoryStream(rawData))
{
Definition = AssemblyDefinition.ReadAssembly(ms);
}
}
}
}
}

View File

@ -1,74 +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.FileProviders;
using System.IO;
using System.Linq;
namespace Microsoft.AspNetCore.Blazor.Build.Core.FileSystem
{
internal class ReferencedAssemblyResolver
{
private readonly IFileProvider _bcl;
private readonly string _searchDirectory;
public ReferencedAssemblyResolver(IFileProvider bcl, string searchDirectory)
{
_bcl = bcl;
_searchDirectory = searchDirectory;
}
public bool TryResolve(string name, out byte[] assemblyBytes)
{
var filename = $"{name}.dll";
if (SearchInFileProvider(_bcl, string.Empty, filename, out var fileInfo))
{
using (var ms = new MemoryStream())
using (var fileInfoStream = fileInfo.CreateReadStream())
{
fileInfoStream.CopyTo(ms);
assemblyBytes = ms.ToArray();
return true;
}
}
else
{
var searchDirPath = Path.Combine(_searchDirectory, filename);
if (File.Exists(searchDirPath))
{
assemblyBytes = File.ReadAllBytes(searchDirPath);
return true;
}
else
{
assemblyBytes = null;
return false;
}
}
}
private static bool SearchInFileProvider(IFileProvider fileProvider, string searchRootDirNoTrailingSlash, string name, out IFileInfo file)
{
var possibleFullPath = $"{searchRootDirNoTrailingSlash}/{name}";
var possibleResult = fileProvider.GetFileInfo(possibleFullPath);
if (possibleResult.Exists)
{
file = possibleResult;
return true;
}
var childDirs = fileProvider.GetDirectoryContents(searchRootDirNoTrailingSlash)
.Where(item => item.IsDirectory);
foreach (var childDir in childDirs)
{
if (SearchInFileProvider(fileProvider, childDir.PhysicalPath, name, out file))
{
return true;
}
}
file = null;
return false;
}
}
}

View File

@ -1,60 +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.FileProviders;
using System.IO;
namespace Microsoft.AspNetCore.Blazor.Build.Core
{
internal static class FileUtil
{
public static void WriteFileProviderToDisk(IFileProvider fileProvider, string outputDir, bool clean)
{
Directory.CreateDirectory(outputDir);
if (clean)
{
CleanDirectory(outputDir);
}
WriteFileProviderToDisk(fileProvider, string.Empty, outputDir);
}
private static void WriteFileProviderToDisk(IFileProvider fileProvider, string subpath, string outputDir)
{
foreach (var item in fileProvider.GetDirectoryContents(subpath))
{
var itemOutputPath = Path.Combine(outputDir, item.Name);
if (item.IsDirectory)
{
Directory.CreateDirectory(itemOutputPath);
WriteFileProviderToDisk(fileProvider, item.PhysicalPath, itemOutputPath);
}
else
{
using (var stream = item.CreateReadStream())
using (var fileStream = File.Open(itemOutputPath, FileMode.Create))
{
stream.CopyTo(fileStream);
}
}
}
}
private static void CleanDirectory(string path)
{
foreach (var itemName in Directory.GetFileSystemEntries(path))
{
var itemPath = Path.Combine(path, itemName);
if (File.GetAttributes(itemPath).HasFlag(FileAttributes.Directory))
{
Directory.Delete(itemPath, recursive: true);
}
else
{
File.Delete(itemPath);
}
}
}
}
}

View File

@ -1,39 +1,73 @@
// 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.Internal.Common.FileProviders;
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Extensions.FileProviders;
using System.IO;
using System.Linq;
using AngleSharp.Parser.Html;
using System.Text;
using AngleSharp;
using AngleSharp.Html;
using System;
using AngleSharp.Parser.Html;
using Mono.Cecil;
namespace Microsoft.AspNetCore.Blazor.Build.Core.FileSystem
namespace Microsoft.AspNetCore.Blazor.Build
{
internal class IndexHtmlFileProvider : InMemoryFileProvider
internal class IndexHtmlWriter
{
public IndexHtmlFileProvider(
string htmlTemplate,
string assemblyName,
string assemblyEntryPoint,
IEnumerable<IFileInfo> binFiles) : base(ComputeContents(htmlTemplate, assemblyName, assemblyEntryPoint, binFiles))
public static void UpdateIndex(string path, string assemblyPath, IEnumerable<string> references, string outputPath)
{
var template = GetTemplate(path);
if (template == null)
{
return;
}
var assemblyName = Path.GetFileNameWithoutExtension(assemblyPath);
var entryPoint = GetAssemblyEntryPoint(assemblyPath);
var updatedContent = GetIndexHtmlContents(template, assemblyName, entryPoint, references);
var normalizedOutputPath = Normalize(outputPath);
Console.WriteLine("Writing index to: " + normalizedOutputPath);
File.WriteAllText(normalizedOutputPath, updatedContent);
}
private static IEnumerable<(string, byte[])> ComputeContents(
string htmlTemplate,
string assemblyName,
string assemblyEntryPoint,
IEnumerable<IFileInfo> binFiles)
private static string Normalize(string outputPath) =>
Path.Combine(Path.GetDirectoryName(outputPath), Path.GetFileName(outputPath).ToLowerInvariant());
private static string GetTemplate(string path)
{
if (htmlTemplate != null)
var fileName = Path.GetFileName(path);
if (File.Exists(path))
{
var html = GetIndexHtmlContents(htmlTemplate, assemblyName, assemblyEntryPoint, binFiles);
var htmlBytes = Encoding.UTF8.GetBytes(html);
yield return ("/index.html", htmlBytes);
return File.ReadAllText(path);
}
if (Directory.Exists(Path.GetDirectoryName(path)))
{
var files = Directory.EnumerateFiles(Path.GetDirectoryName(path))
.OrderBy(f => f);
foreach (var file in files)
{
if (string.Equals(fileName, Path.GetFileName(file), StringComparison.OrdinalIgnoreCase))
{
return File.ReadAllText(file);
}
}
}
return null;
}
private static string GetAssemblyEntryPoint(string assemblyPath)
{
using (var assemblyDefinition = AssemblyDefinition.ReadAssembly(assemblyPath))
{
var entryPoint = assemblyDefinition.EntryPoint;
if (entryPoint == null)
{
throw new ArgumentException($"The assembly at {assemblyPath} has no specified entry point.");
}
return $"{entryPoint.DeclaringType.FullName}::{entryPoint.Name}";
}
}
@ -55,11 +89,11 @@ namespace Microsoft.AspNetCore.Blazor.Build.Core.FileSystem
/// responsible for completing the Blazor boot process.
/// </para>
/// </remarks>
private static string GetIndexHtmlContents(
public static string GetIndexHtmlContents(
string htmlTemplate,
string assemblyName,
string assemblyEntryPoint,
IEnumerable<IFileInfo> binFiles)
IEnumerable<string> binFiles)
{
var resultBuilder = new StringBuilder();
@ -107,7 +141,7 @@ namespace Microsoft.AspNetCore.Blazor.Build.Core.FileSystem
}
break;
}
case HtmlTokenType.EndTag:
// If this is an end tag corresponding to the Blazor boot script tag, we
// can switch back into the mode of emitting the original source text
@ -136,14 +170,12 @@ namespace Microsoft.AspNetCore.Blazor.Build.Core.FileSystem
StringBuilder resultBuilder,
string assemblyName,
string assemblyEntryPoint,
IEnumerable<IFileInfo> binFiles,
IEnumerable<string> binFiles,
List<KeyValuePair<string, string>> attributes)
{
var assemblyNameWithExtension = $"{assemblyName}.dll";
var referenceNames = binFiles
.Where(file => !string.Equals(file.Name, assemblyNameWithExtension))
.Select(file => file.Name);
var referencesAttribute = string.Join(",", referenceNames.ToArray());
var referencesAttribute = string.Join(",", binFiles.ToArray());
var attributesDict = attributes.ToDictionary(x => x.Key, x => x.Value);
attributesDict.Remove("type");

View File

@ -0,0 +1,148 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using Mono.Cecil;
namespace Microsoft.AspNetCore.Blazor.Build
{
internal class RuntimeDependenciesResolver
{
public static void ResolveRuntimeDependencies(
string entryPoint,
string[] applicationDependencies,
string[] monoBclDirectories,
string outputFile)
{
var paths = ResolveRuntimeDependenciesCore(entryPoint, applicationDependencies, monoBclDirectories);
File.WriteAllLines(outputFile, paths);
}
public static IEnumerable<string> ResolveRuntimeDependenciesCore(
string entryPoint,
string[] applicationDependencies,
string[] monoBclDirectories)
{
var assembly = new AssemblyEntry(entryPoint, AssemblyDefinition.ReadAssembly(entryPoint));
var dependencies = applicationDependencies
.Select(a => new AssemblyEntry(a, AssemblyDefinition.ReadAssembly(a)))
.ToArray();
var bcl = monoBclDirectories
.SelectMany(d => Directory.EnumerateFiles(d, "*.dll").Select(f => Path.Combine(d, f)))
.Select(a => new AssemblyEntry(a, AssemblyDefinition.ReadAssembly(a)))
.ToArray();
var assemblyResolutionContext = new AssemblyResolutionContext(
assembly,
dependencies,
bcl);
assemblyResolutionContext.ResolveAssemblies();
var paths = assemblyResolutionContext.Results.Select(r => r.Path);
return paths;
}
public class AssemblyResolutionContext
{
public AssemblyResolutionContext(
AssemblyEntry assembly,
AssemblyEntry[] dependencies,
AssemblyEntry[] bcl)
{
Assembly = assembly;
Dependencies = dependencies;
Bcl = bcl;
}
public AssemblyEntry Assembly { get; }
public AssemblyEntry[] Dependencies { get; }
public AssemblyEntry[] Bcl { get; }
public IList<AssemblyEntry> Results { get; } = new List<AssemblyEntry>();
internal void ResolveAssemblies()
{
var visitedAssemblies = new Dictionary<string, AssemblyEntry>();
var pendingAssemblies = new Stack<AssemblyNameReference>();
pendingAssemblies.Push(Assembly.Definition.Name);
ResolveAssembliesCore();
void ResolveAssembliesCore()
{
while (pendingAssemblies.TryPop(out var current))
{
if (!visitedAssemblies.ContainsKey(current.Name))
{
var resolved = Resolve(current);
visitedAssemblies[current.Name] = resolved;
Results.Add(resolved);
var references = GetAssemblyReferences(resolved);
foreach (var reference in references)
{
pendingAssemblies.Push(reference);
}
}
}
}
IEnumerable<AssemblyNameReference> GetAssemblyReferences(AssemblyEntry current) =>
current.Definition.Modules.SelectMany(m => m.AssemblyReferences);
AssemblyEntry Resolve(AssemblyNameReference current)
{
if (Assembly.Definition.Name.Name == current.Name)
{
return Assembly;
}
var referencedAssemblyCandidate = FindCandidate(current, Dependencies);
var bclAssemblyCandidate = FindCandidate(current, Bcl);
// Resolution logic. For right now, we will prefer the mono BCL version of a given
// assembly if there is a candidate assembly and an equivalent mono assembly.
if (bclAssemblyCandidate != null)
{
return bclAssemblyCandidate;
}
return referencedAssemblyCandidate;
}
AssemblyEntry FindCandidate(AssemblyNameReference current, AssemblyEntry[] candidates)
{
// Do simple name match. Assume no duplicates.
foreach (var candidate in candidates)
{
if (current.Name == candidate.Definition.Name.Name)
{
return candidate;
}
}
return null;
}
}
}
[DebuggerDisplay("{ToString(),nq}")]
public class AssemblyEntry
{
public AssemblyEntry(string path, AssemblyDefinition definition)
{
Path = path;
Definition = definition;
}
public string Path { get; set; }
public AssemblyDefinition Definition { get; set; }
public override string ToString() => Definition.FullName;
}
}
}

View File

@ -62,7 +62,6 @@
<ItemGroup>
<ProjectReference Include="..\Microsoft.AspNetCore.Blazor.Browser.JS\Microsoft.AspNetCore.Blazor.Browser.JS.csproj" />
<ProjectReference Include="..\Microsoft.AspNetCore.Blazor.Mono\Microsoft.AspNetCore.Blazor.Mono.csproj" />
<ProjectReference Include="..\Microsoft.AspNetCore.Blazor.Razor.Extensions\Microsoft.AspNetCore.Blazor.Razor.Extensions.csproj" />
<ProjectReference Include="..\Microsoft.AspNetCore.Blazor\Microsoft.AspNetCore.Blazor.csproj" />
<PackageReference Include="Microsoft.Extensions.CommandLineUtils" Version="1.1.1" />

View File

@ -11,5 +11,8 @@
<file src="build\**" target="build" />
<file src="targets\**" target="targets" />
<file src="$publishdir$\netcoreapp2.0\**\*" target="tools/" />
<file src="..\mono\dist\optimized\**\*" target="tools\mono" />
<file src="..\mono\tools\binaries\illink\**\*.*" target="tools\illink" />
<file src="..\Microsoft.AspNetCore.Blazor.Browser.JS\dist\blazor.js" target="tools\blazor\blazor.js" />
</files>
</package>

View File

@ -1,4 +1,5 @@
<Project>
<!--
Importing this file is equivalent to having:
<PackageDependency Include="Microsoft.AspNetCore.Blazor.Build" />
@ -7,8 +8,22 @@
This is only intended for use by other projects in this repo.
-->
<Import Project="$(MSBuildThisFileDirectory)targets\All.targets" />
<PropertyGroup>
<BlazorBuildReferenceFromSource>true</BlazorBuildReferenceFromSource>
<BlazorMonoRuntimeBasePath>$(MSBuildThisFileDirectory)../mono/</BlazorMonoRuntimeBasePath>
<MonoLinkerPath>$(BlazorMonoRuntimeBasePath)tools/binaries/illink/illink.dll</MonoLinkerPath>
<MonoBaseClassLibraryPath>$(BlazorMonoRuntimeBasePath)dist/optimized/bcl/</MonoBaseClassLibraryPath>
<MonoBaseClassLibraryFacadesPath>$(BlazorMonoRuntimeBasePath)dist/optimized/bcl/Facades/</MonoBaseClassLibraryFacadesPath>
<MonoAsmjsRuntimePath>$(BlazorMonoRuntimeBasePath)dist/optimized/asmjs/</MonoAsmjsRuntimePath>
<MonoWasmRuntimePath>$(BlazorMonoRuntimeBasePath)dist/optimized/wasm/</MonoWasmRuntimePath>
<BlazorJsPath>$(MSBuildThisFileDirectory)../Microsoft.AspNetCore.Blazor.Browser.JS/dist/blazor.js</BlazorJsPath>
</PropertyGroup>
<Import Project="$(MSBuildThisFileDirectory)targets/All.props" />
<Import Project="$(MSBuildThisFileDirectory)targets/All.targets" />
<Import Project="$(MSBuildThisFileDirectory)../mono/mono.targets" />
<Target Name="ProvisionMono" DependsOnTargets="OptimizeMono" BeforeTargets="BuildBlazorBuildBinary" />
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Razor.Design" Version="$(RazorPackageVersion)"/>

View File

@ -0,0 +1,3 @@
<Project>
<Import Project="$(MSBuildThisFileDirectory)..\..\targets\All.props" />
</Project>

View File

@ -0,0 +1,3 @@
<Project>
<Import Project="Blazor.MonoRuntime.props" />
</Project>

View File

@ -13,6 +13,7 @@
</PropertyGroup>
<Import Project="RazorCompilation.targets" />
<Import Project="Blazor.MonoRuntime.targets" />
<Target Name="GenerateBlazorMetadataFile" BeforeTargets="GetCopyToOutputDirectoryItems">
<PropertyGroup>
@ -25,14 +26,4 @@
<ContentWithTargetPath Include="$(BlazorMetadataFilePath)" TargetPath="$(BlazorMetadataFileName)" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
</Target>
<Target Name="BuildBlazorApp" AfterTargets="Build" Inputs="$(ProjectDir)**" Outputs="$(ProjectDir)$(OutDir)dist\**">
<PropertyGroup>
<WebRootName>wwwroot</WebRootName>
<WebRootPath>$(ProjectDir)$(WebRootName)</WebRootPath>
<WebRootParam Condition="Exists('$(WebRootPath)')">--webroot &quot;$(WebRootPath)&quot;</WebRootParam>
</PropertyGroup>
<!-- TODO: Find the correct time to run this (right after assemblies were written) -->
<Exec Command="$(BlazorBuildExe) build &quot;$(ProjectDir)$(OutDir)$(AssemblyName).dll&quot; $(WebRootParam)" />
</Target>
</Project>

View File

@ -0,0 +1,34 @@
<Project>
<PropertyGroup Condition="$(BlazorBuildReferenceFromSource) == ''" Label="Blazor build Inputs">
<BlazorMonoRuntimeBasePath>$(MSBuildThisFileDirectory)../</BlazorMonoRuntimeBasePath>
<MonoLinkerPath>$(BlazorMonoRuntimeBasePath)tools/illink/illink.dll</MonoLinkerPath>
<MonoBaseClassLibraryPath>$(BlazorMonoRuntimeBasePath)tools/mono/bcl/</MonoBaseClassLibraryPath>
<MonoBaseClassLibraryFacadesPath>$(BlazorMonoRuntimeBasePath)tools/mono/bcl/Facades/</MonoBaseClassLibraryFacadesPath>
<MonoAsmjsRuntimePath>$(BlazorMonoRuntimeBasePath)tools/mono/asmjs/</MonoAsmjsRuntimePath>
<MonoWasmRuntimePath>$(BlazorMonoRuntimeBasePath)tools/mono/wasm/</MonoWasmRuntimePath>
<BlazorJsPath>$(BlazorMonoRuntimeBasePath)tools/blazor/</BlazorJsPath>
</PropertyGroup>
<PropertyGroup Label="Blazor build outputs">
<AdditionalMonoLinkerOptions>-c link -u link -t --verbose </AdditionalMonoLinkerOptions>
<BaseBlazorRuntimeOutputPath>dist/_framework/</BaseBlazorRuntimeOutputPath>
<BaseBlazorRuntimeBinOutputPath>$(BaseBlazorRuntimeOutputPath)_bin/</BaseBlazorRuntimeBinOutputPath>
<BaseBlazorRuntimeAsmjsOutputPath>$(BaseBlazorRuntimeOutputPath)asmjs/</BaseBlazorRuntimeAsmjsOutputPath>
<BaseBlazorRuntimeWasmOutputPath>$(BaseBlazorRuntimeOutputPath)wasm/</BaseBlazorRuntimeWasmOutputPath>
<BaseBlazorJsOutputPath>$(BaseBlazorRuntimeOutputPath)</BaseBlazorJsOutputPath>
<BaseBlazorIntermediateOutputPath>blazor/</BaseBlazorIntermediateOutputPath>
<BlazorWebRootName>wwwroot/</BlazorWebRootName>
<BlazorIndexHtmlName>Index.html</BlazorIndexHtmlName>
<BlazorOutputIndexHtmlName>$(BlazorIndexHtmlName.ToLowerInvariant())</BlazorOutputIndexHtmlName>
</PropertyGroup>
<ItemGroup>
<MonoBCLFile Include="$(MonoBaseClassLibraryPath)*.dll" />
<MonoBCLFacadeFile Include="$(MonoBaseClassLibraryFacadesPath)*.dll" />
<MonoAsmjsFile Include="$(MonoAsmjsRuntimePath)**/*.*" />
<MonoWasmFile Include="$(MonoWasmRuntimePath)**/*.*" />
<BlazorJsFile Include="$(BlazorJsPath)" />
<MonoBaseClassLibraryFolder Include="$(MonoBaseClassLibraryPath);$(MonoBaseClassLibraryFacadesPath)" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,561 @@
<Project>
<Target
Name="_BlazorCopyFilesToOutputDirectory"
DependsOnTargets="PrepareBlazorOutputs"
Inputs="@(BlazorItemOutput)"
Outputs="@(BlazorItemOutput->'%(TargetOutputPath)')"
AfterTargets="CopyFilesToOutputDirectory">
<!-- Copy the blazor output files -->
<Copy
SourceFiles="@(BlazorItemOutput)"
DestinationFiles="@(BlazorItemOutput->'%(TargetOutputPath)')"
SkipUnchangedFiles="$(SkipCopyUnchangedFiles)"
OverwriteReadOnlyFiles="$(OverwriteReadOnlyFiles)"
Retries="$(CopyRetryCount)"
RetryDelayMilliseconds="$(CopyRetryDelayMilliseconds)"
UseHardlinksIfPossible="$(CreateHardLinksForCopyFilesToOutputDirectoryIfPossible)"
UseSymboliclinksIfPossible="$(CreateSymbolicLinksForCopyFilesToOutputDirectoryIfPossible)"
Condition="'@(BlazorItemOutput)' != '' and '$(CopyBuildOutputToOutputDirectory)' == 'true' and '$(SkipCopyBuildProduct)' != 'true'">
</Copy>
<ItemGroup>
<FileWrites Include="@(BlazorItemOutput->'%(TargetOutputPath)')" />
</ItemGroup>
</Target>
<Target Name="_BlazorBuildReport"
AfterTargets="_BlazorCopyFilesToOutputDirectory">
<ItemGroup>
<_BlazorStatisticsOutput Include="@(BlazorItemOutput->'%(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 $(ProjectDir)$(OutputPath)dist" />
<Message Importance="$(_BlazorStatisticsReportImportance)" Text="%(_BlazorStatisticsOutput.Identity)" />
</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
_GenerateBlazorIndexHtml
_BlazorCopyFilesToOutputDirectory
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.
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 index html file as inputs.
2) If the marker file is newer than the index.html in the output folder, regenerate the
index.html
Once all the outputs are resolved (static content + binary outputs + index.html)
Copy all the files to the output folder.
-->
<PropertyGroup>
<PrepareBlazorOutputs>
_PrepareBlazorOutputConfiguration;
_DefineBlazorCommonInputs;
_BlazorResolveOutputBinaries;
_GenerateBlazorIndexHtml;
</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/asmjs <- This will contain the asmjs runtime copied from the nuget package.
/_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.
/index.html <- This is the optional index.html file generated from wwwroot/Index.html in case it's present. It
will be canonicalized to index.html
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.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/index.html <- The generated index.html with the updated blazor script tag.
/obj/<<configuration>>/<<targetframework>>/blazor/inputs.index.cache <- The marker file that track whether index.html needs to
be regenerated.
-->
<PropertyGroup Label="Build properties">
<_BlazorShouldLinkApplicationAssemblies Condition="$(BlazorLinkOnBuild) == 'false'"></_BlazorShouldLinkApplicationAssemblies>
<_BlazorShouldLinkApplicationAssemblies Condition="$(BlazorLinkOnBuild) == 'true'">true</_BlazorShouldLinkApplicationAssemblies>
</PropertyGroup>
<PropertyGroup Label="Blazor HTML inputs">
<BlazorWebRootPath>$(ProjectDir)$(BlazorWebRootName)</BlazorWebRootPath>
<BlazorIndexHtml>$(BlazorWebRootPath)$(BlazorIndexHtmlName)</BlazorIndexHtml>
</PropertyGroup>
<ItemGroup Label="Static content to copy to the output folder">
<BlazorItemOutput Include="@(MonoAsmjsFile)">
<TargetOutputPath>$(ProjectDir)$(OutputPath)$(BaseBlazorRuntimeAsmjsOutputPath)%(FileName)%(Extension)</TargetOutputPath>
<Type>AsmJs</Type>
<IsStatic>true</IsStatic>
</BlazorItemOutput>
<BlazorItemOutput Include="@(MonoWasmFile)">
<TargetOutputPath>$(ProjectDir)$(OutputPath)$(BaseBlazorRuntimeWasmOutputPath)%(FileName)%(Extension)</TargetOutputPath>
<Type>WebAssembly</Type>
<IsStatic>true</IsStatic>
</BlazorItemOutput>
<BlazorItemOutput Include="@(BlazorJsFile)">
<TargetOutputPath>$(ProjectDir)$(OutputPath)$(BaseBlazorJsOutputPath)%(FileName)%(Extension)</TargetOutputPath>
<Type>BlazorRuntime</Type>
<IsStatic>true</IsStatic>
</BlazorItemOutput>
</ItemGroup>
<PropertyGroup Label="Intermediate output paths">
<!-- /obj/<<configuration>>/<<targetframework>>/blazor -->
<BlazorIntermediateOutputPath>$(ProjectDir)$(IntermediateOutputPath)$(BaseBlazorIntermediateOutputPath)</BlazorIntermediateOutputPath>
<!-- Common marker files paths -->
<!-- /obj/<<configuration>>/<<targetframework>>/blazor/inputs.basic.cache -->
<BlazorBuildCommonInputsCache>$(BlazorIntermediateOutputPath)inputs.basic.cache</BlazorBuildCommonInputsCache>
<!-- /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>
<!-- /obj/<<configuration>>/<<targetframework>>/blazor/linker.descriptor.xml -->
<GeneratedBlazorLinkerDescriptor>$(BlazorIntermediateOutputPath)linker.descriptor.xml</GeneratedBlazorLinkerDescriptor>
<!-- /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>
<!-- Index.html related paths and markers -->
<!-- /obj/<<configuration>>/<<targetframework>>/blazor/index.html -->
<BlazorIndexHtmlOutputPath>$(BlazorIntermediateOutputPath)$(BlazorOutputIndexHtmlName)</BlazorIndexHtmlOutputPath>
<!-- /obj/<<configuration>>/<<targetframework>>/blazor/inputs.index.cache -->
<BlazorBuildIndexInputsCache>$(BlazorIntermediateOutputPath)inputs.index.cache</BlazorBuildIndexInputsCache>
</PropertyGroup>
<PropertyGroup Label="Final output paths">
<BlazorRuntimeBinOutputPath>$(ProjectDir)$(OutputPath)$(BaseBlazorRuntimeBinOutputPath)</BlazorRuntimeBinOutputPath>
</PropertyGroup>
<MakeDir Directories="$(BlazorIntermediateOutputPath)" />
</Target>
<Target Name="_DefineBlazorCommonInputs">
<ItemGroup>
<_BlazorCommonInput Include="@(IntermediateAssembly)" />
<_BlazorCommonInput Include="@(ReferenceCopyLocalPaths->WithMetadataValue('Extension','.dll'))" />
<_BlazorCommonInput Include="$(_BlazorShouldLinkApplicationAssemblies)" />
<_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" />
<!-- 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)" />
</ItemGroup>
</Target>
<Target Name="_BlazorResolveOutputBinaries" DependsOnTargets="_CollectLinkerOutputs;_CollectResolvedAssemblies" />
<!--
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.
-->
<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"/>
</ReadLinesFromFile>
<ItemGroup>
<BlazorItemOutput Include="@(_OptimizedFiles)">
<TargetOutputPath>$(BlazorRuntimeBinOutputPath)%(FileName)%(Extension)</TargetOutputPath>
<Type>Assembly</Type>
<PrimaryOutput Condition="'%(FileName)' == @(IntermediateAssembly->'%(FileName)')">true</PrimaryOutput>
</BlazorItemOutput>
<FileWrites Include="@(BlazorItemOutput->WithMetadataValue('Type','Assembly')->'%(TargetOutputPath)')" />
</ItemGroup>
</Target>
<Target Name="_GenerateLinkerDescriptor"
Inputs="$(BlazorBuildCommonInputsCache)"
Outputs="$(GeneratedBlazorLinkerDescriptor)"
Condition="$(_BlazorShouldLinkApplicationAssemblies) != '' and '@(BlazorLinkerDescriptor)' == ''">
<ItemGroup>
<_PrepareLinkerDescriptorAssemblyLine Include="@(IntermediateAssembly->'%(FileName)')" />
<_GeneratedLinkerDescriptorLine Include="&lt;linker&gt;" />
<_GeneratedLinkerDescriptorLine Include="@(_PrepareLinkerDescriptorAssemblyLine->'&lt;assembly fullname=&quot;%(Identity)&quot; /&gt;')" />
<_GeneratedLinkerDescriptorLine Include="&lt;/linker&gt;" />
</ItemGroup>
<WriteLinesToFile
Lines="@(_GeneratedLinkerDescriptorLine)"
File="$(GeneratedBlazorLinkerDescriptor)"
Overwrite="true"
WriteOnlyWhenDifferent="True" />
</Target>
<Target Name="_CollectBlazorLinkerDescriptors">
<ItemGroup Condition="@(BlazorLinkerDescriptor) == ''">
<BlazorLinkerDescriptor Include="$(GeneratedBlazorLinkerDescriptor)" />
<FileWrites Include="$(GeneratedBlazorLinkerDescriptor)" />
</ItemGroup>
<ItemGroup>
<_BlazorLinkerInput Include="@(IntermediateAssembly)" />
<_BlazorLinkerInput Include="@(ReferenceCopyLocalPaths->WithMetadataValue('Extension','.dll'))" />
<_BlazorLinkerInput Include="@(BlazorLinkerDescriptor)" />
<_BlazorLinkerInput Include="$(AdditionalLinkerOptions)" />
</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)"
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.
-->
<ItemGroup>
<_BlazorAssembliesToLink Include="@(ReferenceCopyLocalPaths->WithMetadataValue('Extension','.dll')->'-a &quot;%(FullPath)&quot;')" />
<_BlazorAssembliesToLink Include="@(IntermediateAssembly->'-a &quot;%(FullPath)&quot;')" />
<_BlazorFolderLookupPaths Include="@(MonoBaseClassLibraryFolder->'-d &quot;%(Identity)&quot;')" />
<_BlazorAssemblyDescriptorFiles
Include="@(BlazorLinkerDescriptor->'-x &quot;%(FullPath)&quot;')" Condition="'@(BlazorLinkerDescriptor)' != ''" />
</ItemGroup>
<PropertyGroup>
<_BlazorLinkerAdditionalOptions>$(AdditionalMonoLinkerOptions)</_BlazorLinkerAdditionalOptions>
</PropertyGroup>
<!-- Clear the contents of /obj/<<configuration>>/<<targetframework>>/blazor/blazor/linker -->
<Delete Files="$(BlazorIntermediateLinkerOutputPath)*.dll" />
<!-- Run the linker and put the results in /obj/<<configuration>>/<<targetframework>>/blazor/blazor/linker -->
<Exec Command="dotnet $(MonoLinkerPath) $(_BlazorLinkerAdditionalOptions) @(_BlazorFolderLookupPaths, ' ') -o $(BlazorIntermediateLinkerOutputPath) @(_BlazorAssemblyDescriptorFiles, ' ') @(_BlazorAssembliesToLink, ' ')" />
<!-- Collect the contents of /obj/<<configuration>>/<<targetframework>>/blazor/blazor/linker/ -->
<ItemGroup>
<_BlazorLinkerOutput 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" />
<!-- 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)" />
</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)' == ''">
<!--
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)">
<TargetOutputPath>$(BlazorRuntimeBinOutputPath)%(FileName)%(Extension)</TargetOutputPath>
<Type>Assembly</Type>
<PrimaryOutput Condition="'%(FileName)' == @(IntermediateAssembly->'%(FileName)')">true</PrimaryOutput>
</BlazorItemOutput>
<FileWrites Include="@(BlazorItemOutput->WithMetadataValue('Type','Assembly')->'%(TargetOutputPath)')" />
</ItemGroup>
</Target>
<Target
Name="_ResolveBlazorApplicationAssemblies"
Condition="'$(_BlazorShouldLinkApplicationAssemblies)' == ''"
Inputs="$(BlazorBuildCommonInputsCache)"
Outputs="$(BlazorResolvedAssembliesOutputPath)"
>
<ItemGroup>
<_DependenciesParameter Include="@(ReferenceCopyLocalPaths->WithMetadataValue('Extension','.dll')->'--reference &quot;%(FullPath)&quot;')" />
</ItemGroup>
<PropertyGroup>
<_BclParameter>--base-class-library &quot;$(MonoBaseClassLibraryPath)&quot; --base-class-library &quot;$(MonoBaseClassLibraryFacadesPath)&quot;</_BclParameter>
</PropertyGroup>
<Exec Command="$(BlazorBuildExe) resolve-dependencies &quot;@(IntermediateAssembly->'%(FullPath)')&quot; @(_DependenciesParameter, ' ') $(_BclParameter) --output &quot;$(BlazorResolvedAssembliesOutputPath)&quot;" />
</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 index html if the list of assemblies has changed.
-->
<Target Name="_ResolveBlazorIndexHtmlInputs">
<ItemGroup>
<BlazorIndexHtmlInput Include="$(BlazorIndexHtml)" />
<BlazorIndexHtmlInput Include="@(BlazorItemOutput->WithMetadataValue('Type','Assembly')->'%(FullPath)')" />
</ItemGroup>
<WriteLinesToFile
File="$(BlazorBuildIndexInputsCache)"
Lines="@(BlazorIndexHtmlInput)"
Overwrite="true"
WriteOnlyWhenDifferent="True" />
<ItemGroup>
<FileWrites Include="$(BlazorBuildIndexInputsCache)" />
</ItemGroup>
</Target>
<Target
Name="_GenerateBlazorIndexHtml"
DependsOnTargets="_ResolveBlazorIndexHtmlInputs"
Inputs="$(BlazorBuildIndexInputsCache)"
Outputs="$(BlazorIndexHtmlOutputPath)">
<ItemGroup>
<_AppReferences Include="@(BlazorItemOutput->WithMetadataValue('Type','Assembly')->WithMetadataValue('PrimaryOutput','')->'%(FileName)%(Extension)')" />
</ItemGroup>
<Exec Command="$(BlazorBuildExe) build @(IntermediateAssembly) --html-page &quot;$(BlazorIndexHtml)&quot; @(_AppReferences->'--reference %(Identity)', ' ') --output &quot;$(BlazorIndexHtmlOutputPath)&quot;" />
<ItemGroup Condition="Exists('$(BlazorIndexHtmlOutputPath)')">
<_BlazorIndex Include="$(BlazorIndexHtmlOutputPath)" />
<BlazorItemOutput Include="@(_BlazorIndex)">
<TargetOutputPath>$(ProjectDir)$(OutputPath)dist/%(FileName)%(Extension)</TargetOutputPath>
<Type>EntryPoint</Type>
</BlazorItemOutput>
<FileWrites Include="$(BlazorIndexHtmlOutputPath)" />
</ItemGroup>
</Target>
</Project>

View File

@ -1,87 +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.FileProviders;
using System.Collections.Generic;
using System.Linq;
using System;
using System.IO;
namespace Microsoft.AspNetCore.Blazor.Internal.Common.FileProviders
{
// This is like a CompositeFileProvider, except that:
// (a) the child providers can be mounted at non-root locations
// (b) the set of contents is immutable and fully indexed at construction time
// so that all subsequent reads are "O(dictionary lookup)" time
public class CompositeMountedFileProvider : InMemoryFileProvider
{
public CompositeMountedFileProvider(params (string, IFileProvider)[] providers)
: base(GetCompositeContents(providers))
{
}
private static IEnumerable<IFileInfo> GetCompositeContents(
IEnumerable<(string, IFileProvider)> providers)
{
return providers
.Select(pair => new { MountPoint = pair.Item1, Files = ReadAllFiles(pair.Item2, string.Empty) })
.SelectMany(info => info.Files.Select(file => (IFileInfo)new MountedFileInfo(info.MountPoint, file)));
}
private static IEnumerable<IFileInfo> ReadAllFiles(IFileProvider provider, string subpath)
{
return provider.GetDirectoryContents(subpath).SelectMany(
item => item.IsDirectory
? ReadAllFiles(provider, item.PhysicalPath)
: new[] { item });
}
private class MountedFileInfo : IFileInfo
{
private readonly IFileInfo _file;
public MountedFileInfo(string mountPoint, IFileInfo file)
{
_file = file;
if (!file.PhysicalPath.StartsWith("/"))
{
throw new ArgumentException($"For mounted files, {nameof(file.PhysicalPath)} must start with '/'. Value supplied: '{file.PhysicalPath}'.");
}
if (!mountPoint.StartsWith("/"))
{
throw new ArgumentException("The path must start with '/'", nameof(mountPoint));
}
if (mountPoint == "/")
{
PhysicalPath = file.PhysicalPath;
}
else
{
if (mountPoint.EndsWith("/"))
{
throw new ArgumentException("Non-root paths must not end with '/'", nameof(mountPoint));
}
PhysicalPath = mountPoint + file.PhysicalPath;
}
}
public bool Exists => _file.Exists;
public long Length => _file.Length;
public string PhysicalPath { get; }
public string Name => _file.Name;
public DateTimeOffset LastModified => _file.LastModified;
public bool IsDirectory => _file.IsDirectory;
public Stream CreateReadStream() => _file.CreateReadStream();
}
}
}

View File

@ -1,39 +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.Linq;
using System.Reflection;
namespace Microsoft.AspNetCore.Blazor.Internal.Common.FileProviders
{
public class EmbeddedResourceFileProvider : InMemoryFileProvider
{
public EmbeddedResourceFileProvider(Assembly assembly, string resourceNamePrefix)
: base(ReadEmbeddedResources(assembly, resourceNamePrefix))
{
}
private static IEnumerable<(string, byte[])> ReadEmbeddedResources(
Assembly assembly, string resourceNamePrefix)
{
return assembly.GetManifestResourceNames()
.Where(name => name.StartsWith(resourceNamePrefix, StringComparison.Ordinal))
.Select(name => (
name.Substring(resourceNamePrefix.Length),
ReadManifestResource(assembly, name)));
}
private static byte[] ReadManifestResource(Assembly assembly, string name)
{
using (var ms = new MemoryStream())
using (var resourceStream = assembly.GetManifestResourceStream(name))
{
resourceStream.CopyTo(ms);
return ms.ToArray();
}
}
}
}

View File

@ -1,44 +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.Linq;
using Microsoft.Extensions.FileProviders;
using System.Collections.Generic;
using System.Collections;
using System;
namespace Microsoft.AspNetCore.Blazor.Internal.Common.FileProviders
{
internal class InMemoryDirectoryContents : IDirectoryContents
{
private readonly Dictionary<string, object> _names; // Just to ensure there are no duplicate names
private readonly List<IFileInfo> _contents;
public InMemoryDirectoryContents(IEnumerable<IFileInfo> contents)
{
if (contents != null)
{
_contents = contents.ToList();
_names = _contents.ToDictionary(item => item.Name, item => (object)null);
}
}
public bool Exists => _contents != null;
public IEnumerator<IFileInfo> GetEnumerator() => GetEnumeratorIfExists();
IEnumerator IEnumerable.GetEnumerator() => GetEnumeratorIfExists();
internal void AddItem(IFileInfo item)
{
_names.Add(item.Name, null); // Asserts uniqueness
_contents.Add(item);
}
internal bool ContainsName(string name)
=> _names.ContainsKey(name);
private IEnumerator<IFileInfo> GetEnumeratorIfExists() => Exists
? _contents.GetEnumerator()
: throw new InvalidOperationException("The directory does not exist");
}
}

View File

@ -1,76 +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.FileProviders;
using System.IO;
using System;
namespace Microsoft.AspNetCore.Blazor.Internal.Common.FileProviders
{
internal class InMemoryFileInfo : IFileInfo
{
private readonly bool _exists;
private readonly bool _isDirectory;
private readonly byte[] _fileData;
private readonly string _name;
private readonly string _physicalPath;
private readonly DateTimeOffset _lastModified;
public static IFileInfo ForExistingDirectory(string path)
=> new InMemoryFileInfo(path, isDir: true, exists: true);
public static IFileInfo ForExistingFile(string path, byte[] data, DateTimeOffset lastModified)
=> new InMemoryFileInfo(path, isDir: false, exists: true, data: data, lastModified: lastModified);
public static IFileInfo ForNonExistingFile(string path)
=> new InMemoryFileInfo(path, isDir: false, exists: false);
private InMemoryFileInfo(string physicalPath, bool isDir, bool exists, byte[] data = null, DateTimeOffset lastModified = default(DateTimeOffset))
{
_exists = exists;
_isDirectory = isDir;
_name = Path.GetFileName(physicalPath);
_physicalPath = physicalPath ?? throw new ArgumentNullException(nameof(physicalPath));
_lastModified = lastModified;
if (_exists)
{
if (!_physicalPath.StartsWith(InMemoryFileProvider.DirectorySeparatorChar))
{
throw new ArgumentException($"Must start with '{InMemoryFileProvider.DirectorySeparatorChar}'",
nameof(physicalPath));
}
if (_physicalPath.EndsWith(InMemoryFileProvider.DirectorySeparatorChar))
{
throw new ArgumentException($"Must not end with '{InMemoryFileProvider.DirectorySeparatorChar}'",
nameof(physicalPath));
}
}
_fileData = data;
}
public bool Exists => _exists;
public long Length => Exists && !IsDirectory
? _fileData.Length
: throw new InvalidOperationException(IsDirectory
? "The item is a directory."
: "The item does not exist.");
public string PhysicalPath => _physicalPath;
public string Name => _name;
public DateTimeOffset LastModified => _lastModified;
public bool IsDirectory => _isDirectory;
public Stream CreateReadStream() => Exists && !IsDirectory
? new MemoryStream(_fileData)
: throw new InvalidOperationException(IsDirectory
? "The item is a directory."
: "The item does not exist.");
}
}

View File

@ -1,96 +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.FileProviders;
using Microsoft.Extensions.Primitives;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace Microsoft.AspNetCore.Blazor.Internal.Common.FileProviders
{
public class InMemoryFileProvider : IFileProvider
{
// Since the IFileProvider APIs don't include any way of asking for parent or
// child directories, it's efficient to store everything by full path
private readonly IDictionary<string, IFileInfo> _filesByFullPath;
private readonly IDictionary<string, InMemoryDirectoryContents> _directoriesByFullPath;
// It's convenient to use forward slash, because it matches URL conventions
public const string DirectorySeparatorChar = "/";
public InMemoryFileProvider(IEnumerable<(string, byte[])> contents) : this(
contents.Select(pair => InMemoryFileInfo
.ForExistingFile(pair.Item1, pair.Item2, DateTime.Now)))
{
}
public InMemoryFileProvider(IEnumerable<IFileInfo> contents)
{
_filesByFullPath = contents
.ToDictionary(
fileInfo => fileInfo.PhysicalPath,
fileInfo => (IFileInfo)fileInfo);
_directoriesByFullPath = _filesByFullPath.Values
.GroupBy(file => GetDirectoryName(file.PhysicalPath))
.ToDictionary(
group => group.Key,
group => new InMemoryDirectoryContents(group));
foreach (var dirToInsert in _directoriesByFullPath.Keys.ToList())
{
AddSubdirectoryEntry(dirToInsert);
}
}
private void AddSubdirectoryEntry(string dirPath)
{
// If this is the root directory, there's no parent
if (dirPath.Length == 0)
{
return;
}
// Ensure parent directory exists
var parentDirPath = GetDirectoryName(dirPath);
if (!_directoriesByFullPath.ContainsKey(parentDirPath))
{
_directoriesByFullPath.Add(
parentDirPath,
new InMemoryDirectoryContents(Enumerable.Empty<IFileInfo>()));
}
var parentDir = _directoriesByFullPath[parentDirPath];
if (!parentDir.ContainsName(Path.GetFileName(dirPath)))
{
parentDir.AddItem(
InMemoryFileInfo.ForExistingDirectory(dirPath));
}
// Doing this recursively creates all ancestor directories
AddSubdirectoryEntry(parentDirPath);
}
private static string GetDirectoryName(string fullPath)
=> fullPath.Substring(0, fullPath.LastIndexOf(DirectorySeparatorChar));
public IDirectoryContents GetDirectoryContents(string subpath)
=> _directoriesByFullPath.TryGetValue(StripTrailingSeparator(subpath), out var result)
? result
: new InMemoryDirectoryContents(null);
public IFileInfo GetFileInfo(string subpath)
=> _filesByFullPath.TryGetValue(subpath, out var fileInfo)
? fileInfo
: InMemoryFileInfo.ForNonExistingFile(subpath);
public IChangeToken Watch(string filter)
=> throw new NotImplementedException();
private string StripTrailingSeparator(string path) => path.EndsWith(DirectorySeparatorChar)
? path.Substring(0, path.Length - 1)
: path;
}
}

View File

@ -1,11 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.FileProviders.Abstractions" Version="2.0.0" />
</ItemGroup>
</Project>

View File

@ -1,30 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\mono\mono.targets" />
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Microsoft.AspNetCore.Blazor.Common\Microsoft.AspNetCore.Blazor.Common.csproj" />
</ItemGroup>
<Target Name="EmbedMonoResources" BeforeTargets="PreBuildEvent" DependsOnTargets="OptimizeMono" Inputs="$(MonoUnoptimizedDir)\**" Outputs="$(MonoOptimizedDir)\**">
<ItemGroup>
<MonoWasmResourcesToEmbed Include="$(MonoOptimizedDir)wasm/**" />
<MonoAsmJsResourcesToEmbed Include="$(MonoOptimizedDir)asmjs/**" />
<MonoBclResourcesToEmbed Include="$(MonoOptimizedDir)bcl/**" />
<EmbeddedResource Include="@(MonoWasmResourcesToEmbed)">
<LogicalName>mono.js./wasm$([System.String]::Copy('/%(RecursiveDir)%(FileName)%(Extension)').Replace('\', '/'))</LogicalName>
</EmbeddedResource>
<EmbeddedResource Include="@(MonoAsmJsResourcesToEmbed)">
<LogicalName>mono.js./asmjs$([System.String]::Copy('/%(RecursiveDir)%(FileName)%(Extension)').Replace('\', '/'))</LogicalName>
</EmbeddedResource>
<EmbeddedResource Include="@(MonoBclResourcesToEmbed)">
<LogicalName>mono.bcl./bcl$([System.String]::Copy('/%(RecursiveDir)%(FileName)%(Extension)').Replace('\', '/'))</LogicalName>
</EmbeddedResource>
</ItemGroup>
</Target>
</Project>

View File

@ -1,17 +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.Internal.Common.FileProviders;
using Microsoft.Extensions.FileProviders;
namespace Microsoft.AspNetCore.Blazor.Mono
{
public static class MonoStaticFileProvider
{
public readonly static IFileProvider JsFiles = new EmbeddedResourceFileProvider(
typeof(MonoStaticFileProvider).Assembly, "mono.js.");
public readonly static IFileProvider BclFiles = new EmbeddedResourceFileProvider(
typeof(MonoStaticFileProvider).Assembly, "mono.bcl.");
}
}

View File

@ -1,164 +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.FileProviders;
using System.IO;
using System.Linq;
using Xunit;
using System;
using AngleSharp.Parser.Html;
using Microsoft.AspNetCore.Blazor.Build.Core.FileSystem;
namespace Microsoft.AspNetCore.Blazor.Server.Test
{
public class IndexHtmlFileProviderTest
{
[Fact]
public void SuppliesNoIndexHtmlFileGivenNoTemplate()
{
// Arrange
var instance = new IndexHtmlFileProvider(
null, "fakeassembly", "MyNamespace.MyType::MyMethod", Enumerable.Empty<IFileInfo>());
// Act
var file = instance.GetFileInfo("/index.html");
// Assert
Assert.False(file.Exists);
}
[Fact]
public void SuppliesIndexHtmlFileGivenTemplate()
{
// Arrange
var htmlTemplate = "test";
var instance = new IndexHtmlFileProvider(
htmlTemplate, "fakeassembly", "MyNamespace.MyType::MyMethod", Enumerable.Empty<IFileInfo>());
// Act
var file = instance.GetFileInfo("/index.html");
// Assert
Assert.True(file.Exists);
Assert.False(file.IsDirectory);
Assert.Equal("/index.html", file.PhysicalPath);
Assert.Equal("index.html", file.Name);
Assert.Equal(htmlTemplate, ReadString(file));
}
[Fact]
public void RootDirectoryContainsOnlyIndexHtml()
{
// Arrange
var htmlTemplate = "test";
var instance = new IndexHtmlFileProvider(
htmlTemplate, "fakeassembly", "MyNamespace.MyType::MyMethod", Enumerable.Empty<IFileInfo>());
// Act
var directory = instance.GetDirectoryContents(string.Empty);
// Assert
Assert.True(directory.Exists);
Assert.Collection(directory,
item => Assert.Equal("/index.html", item.PhysicalPath));
}
[Fact]
public void InjectsScriptTagReferencingAssemblyAndDependencies()
{
// Arrange
var htmlTemplatePrefix = @"
<html>
<body>
<h1>Hello</h1>
Some text
<script>alert(1)</script>";
var htmlTemplateSuffix = @"
</body>
</html>";
var htmlTemplate =
$@"{htmlTemplatePrefix}
<script type='blazor-boot' custom1 custom2=""value"">some text that should be removed</script>
{htmlTemplateSuffix}";
var dependencies = new IFileInfo[]
{
new TestFileInfo("System.Abc.dll"),
new TestFileInfo("MyApp.ClassLib.dll"),
};
var instance = new IndexHtmlFileProvider(
htmlTemplate, "MyApp.Entrypoint", "MyNamespace.MyType::MyMethod", dependencies);
// Act
var file = instance.GetFileInfo("/index.html");
var fileContents = ReadString(file);
// Assert: Start and end is not modified (including formatting)
Assert.StartsWith(htmlTemplatePrefix, fileContents);
Assert.EndsWith(htmlTemplateSuffix, fileContents);
// Assert: Boot tag is correct
var scriptTagText = fileContents.Substring(htmlTemplatePrefix.Length, fileContents.Length - htmlTemplatePrefix.Length - htmlTemplateSuffix.Length);
var parsedHtml = new HtmlParser().Parse("<html><body>" + scriptTagText + "</body></html>");
var scriptElem = parsedHtml.Body.QuerySelector("script");
Assert.False(scriptElem.HasChildNodes);
Assert.Equal("_framework/blazor.js", scriptElem.GetAttribute("src"));
Assert.Equal("MyApp.Entrypoint.dll", scriptElem.GetAttribute("main"));
Assert.Equal("MyNamespace.MyType::MyMethod", scriptElem.GetAttribute("entrypoint"));
Assert.Equal("System.Abc.dll,MyApp.ClassLib.dll", scriptElem.GetAttribute("references"));
Assert.False(scriptElem.HasAttribute("type"));
Assert.Equal(string.Empty, scriptElem.Attributes["custom1"].Value);
Assert.Equal("value", scriptElem.Attributes["custom2"].Value);
}
[Fact]
public void SuppliesHtmlTemplateUnchangedIfNoBootScriptPresent()
{
// Arrange
var htmlTemplate = "<!DOCTYPE html><html><body><h1 style='color:red'>Hello</h1>Some text<script type='irrelevant'>blah</script></body></html>";
var dependencies = new IFileInfo[]
{
new TestFileInfo("System.Abc.dll"),
new TestFileInfo("MyApp.ClassLib.dll"),
};
var instance = new IndexHtmlFileProvider(
htmlTemplate, "MyApp.Entrypoint", "MyNamespace.MyType::MyMethod", dependencies);
// Act
var file = instance.GetFileInfo("/index.html");
// Assert
Assert.Equal(htmlTemplate, ReadString(file));
}
private static string ReadString(IFileInfo file)
{
using (var stream = file.CreateReadStream())
using (var sr = new StreamReader(stream))
{
return sr.ReadToEnd();
}
}
class TestFileInfo : IFileInfo
{
public TestFileInfo(string physicalPath)
{
PhysicalPath = physicalPath;
}
public bool Exists => true;
public long Length => throw new NotImplementedException();
public string PhysicalPath { get; }
public string Name => Path.GetFileName(PhysicalPath);
public DateTimeOffset LastModified => throw new NotImplementedException();
public bool IsDirectory => false;
public Stream CreateReadStream() => throw new NotImplementedException();
}
}
}

View File

@ -0,0 +1,74 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using AngleSharp.Parser.Html;
using Xunit;
namespace Microsoft.AspNetCore.Blazor.Build.Test
{
public class IndexHtmlWriterTest
{
[Fact]
public void InjectsScriptTagReferencingAssemblyAndDependencies()
{
// Arrange
var htmlTemplatePrefix = @"
<html>
<body>
<h1>Hello</h1>
Some text
<script>alert(1)</script>";
var htmlTemplateSuffix = @"
</body>
</html>";
var htmlTemplate =
$@"{htmlTemplatePrefix}
<script type='blazor-boot' custom1 custom2=""value"">some text that should be removed</script>
{htmlTemplateSuffix}";
var dependencies = new string[]
{
"System.Abc.dll",
"MyApp.ClassLib.dll",
};
var instance = IndexHtmlWriter.GetIndexHtmlContents(
htmlTemplate,
"MyApp.Entrypoint",
"MyNamespace.MyType::MyMethod", dependencies);
// Act & Assert: Start and end is not modified (including formatting)
Assert.StartsWith(htmlTemplatePrefix, instance);
Assert.EndsWith(htmlTemplateSuffix, instance);
// Assert: Boot tag is correct
var scriptTagText = instance.Substring(htmlTemplatePrefix.Length, instance.Length - htmlTemplatePrefix.Length - htmlTemplateSuffix.Length);
var parsedHtml = new HtmlParser().Parse("<html><body>" + scriptTagText + "</body></html>");
var scriptElem = parsedHtml.Body.QuerySelector("script");
Assert.False(scriptElem.HasChildNodes);
Assert.Equal("_framework/blazor.js", scriptElem.GetAttribute("src"));
Assert.Equal("MyApp.Entrypoint.dll", scriptElem.GetAttribute("main"));
Assert.Equal("MyNamespace.MyType::MyMethod", scriptElem.GetAttribute("entrypoint"));
Assert.Equal("System.Abc.dll,MyApp.ClassLib.dll", scriptElem.GetAttribute("references"));
Assert.False(scriptElem.HasAttribute("type"));
Assert.Equal(string.Empty, scriptElem.Attributes["custom1"].Value);
Assert.Equal("value", scriptElem.Attributes["custom2"].Value);
}
[Fact]
public void SuppliesHtmlTemplateUnchangedIfNoBootScriptPresent()
{
// Arrange
var htmlTemplate = "<!DOCTYPE html><html><body><h1 style='color:red'>Hello</h1>Some text<script type='irrelevant'>blah</script></body></html>";
var dependencies = new string[]
{
"System.Abc.dll",
"MyApp.ClassLib.dll",
};
var content = IndexHtmlWriter.GetIndexHtmlContents(
htmlTemplate, "MyApp.Entrypoint", "MyNamespace.MyType::MyMethod", dependencies);
// Assert
Assert.Equal(htmlTemplate, content);
}
}
}

View File

@ -22,8 +22,13 @@
<Compile Include="..\shared\**\*.cs" Link="Helpers\%(Filename)%(Extension)" />
</ItemGroup>
<!-- A bit of msbuild magic to support reference resolver tests -->
<Target Name="CreateReferenceHintPathsList" AfterTargets="Build">
<WriteLinesToFile Lines="@(ReferencePath)" File="$(ProjectDir)$(OutputPath)referenceHints.txt" WriteOnlyWhenDifferent="true" Overwrite="true" />
</Target>
<ItemGroup>
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Blazor.Mono\Microsoft.AspNetCore.Blazor.Mono.csproj" />
<ProjectReference Include="..\..\src\anglesharp\AngleSharpBuilder\AngleSharpBuilder.csproj" ReferenceOutputAssembly="false" />
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Blazor.Razor.Extensions\Microsoft.AspNetCore.Blazor.Razor.Extensions.csproj" />
<Reference Include="Microsoft.AspNetCore.Blazor.AngleSharp" HintPath="..\..\src\anglesharp\AngleSharpBuilder\dist\Microsoft.AspNetCore.Blazor.AngleSharp.dll" />
</ItemGroup>

View File

@ -1,128 +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.Core.FileSystem;
using Microsoft.AspNetCore.Blazor.Mono;
using Mono.Cecil;
using System;
using System.IO;
using System.Linq;
using Xunit;
namespace Microsoft.AspNetCore.Blazor.Server.Test
{
public class ReferencedAssemblyFileProviderTest
{
[Fact]
public void RootDirContainsOnlyDlls()
{
var provider = new ReferencedAssemblyFileProvider(
"mscorlib",
new ReferencedAssemblyResolver(MonoStaticFileProvider.BclFiles, string.Empty));
foreach (var item in provider.GetDirectoryContents("/"))
{
Assert.False(item.IsDirectory);
Assert.EndsWith(".dll", item.Name);
}
}
[Fact]
public void FindsReferencedAssemblyGraphSimple()
{
var provider = new ReferencedAssemblyFileProvider(
"System.Linq.Expressions",
new ReferencedAssemblyResolver(MonoStaticFileProvider.BclFiles, string.Empty));
var contents = provider.GetDirectoryContents("").OrderBy(i => i.Name).ToList();
Assert.Collection(contents,
item => { Assert.Equal("/Mono.Security.dll", item.PhysicalPath); },
item => { Assert.Equal("/mscorlib.dll", item.PhysicalPath); },
item => { Assert.Equal("/System.Core.dll", item.PhysicalPath); },
item => { Assert.Equal("/System.dll", item.PhysicalPath); },
item => { Assert.Equal("/System.Linq.Expressions.dll", item.PhysicalPath); },
item => { Assert.Equal("/System.Xml.dll", item.PhysicalPath); });
}
[Fact]
public void FindsReferencedAssemblyGraphRealistic()
{
// Arrange
var standaloneAppAssembly = typeof(StandaloneApp.Program).Assembly;
var provider = new ReferencedAssemblyFileProvider(
standaloneAppAssembly.GetName().Name,
new ReferencedAssemblyResolver(
MonoStaticFileProvider.BclFiles,
Path.GetDirectoryName(standaloneAppAssembly.Location)));
var expectedContents = new[]
{
/*
The current Mono WASM BCL forwards from netstandard.dll to various facade assemblies
in which small bits of implementation live, such as System.Xml.XPath.XDocument. So
if you reference netstandard, then you also reference System.Xml.XPath.XDocument.dll,
even though you're very unlikely to be calling it at runtime. That's why the following
list (for a very basic Blazor app) is longer than you'd expect.
These redundant references could be stripped out during publishing, but it's still
unfortunate that in development mode you'd see all these unexpected assemblies get
fetched from the server. We should try to get the Mono WASM BCL reorganized so that
all the implementation goes into mscorlib.dll, with the facade assemblies existing only
in case someone (or some 3rd party assembly) references them directly, but with their
implementations 100% forwarding to mscorlib.dll. Then in development you'd fetch far
fewer assemblies from the server, and during publishing, illink would remove all the
uncalled implementation code from mscorlib.dll anyway.
*/
"/Microsoft.AspNetCore.Blazor.Browser.dll",
"/Microsoft.AspNetCore.Blazor.dll",
"/Mono.Security.dll",
"/mscorlib.dll",
"/netstandard.dll",
"/StandaloneApp.dll",
"/System.ComponentModel.Composition.dll",
"/System.Core.dll",
"/System.Data.dll",
"/System.Diagnostics.StackTrace.dll",
"/System.dll",
"/System.Drawing.dll",
"/System.Globalization.Extensions.dll",
"/System.IO.Compression.dll",
"/System.IO.Compression.FileSystem.dll",
"/System.Net.Http.dll",
"/System.Numerics.dll",
"/System.Runtime.Serialization.dll",
"/System.Runtime.Serialization.Primitives.dll",
"/System.Runtime.Serialization.Xml.dll",
"/System.Security.Cryptography.Algorithms.dll",
"/System.Security.SecureString.dll",
"/System.ServiceModel.Internals.dll",
"/System.Transactions.dll",
"/System.Web.Services.dll",
"/System.Xml.dll",
"/System.Xml.Linq.dll",
"/System.Xml.XPath.XDocument.dll",
};
// Act
var contents = provider.GetDirectoryContents("")
.OrderBy(i => i.Name, StringComparer.InvariantCulture).ToList();
// Assert
Assert.Equal(expectedContents.Length, contents.Count);
for (var i = 0; i < expectedContents.Length; i++)
{
Assert.Equal(expectedContents[i], contents[i].PhysicalPath);
}
}
private static (AssemblyDefinition, byte[]) GetBclAssemblyForTest(string name)
{
var possibleFilenames = new[] { $"/bcl/{name}.dll", $"/bcl/Facades/{name}.dll" };
var fileInfo = possibleFilenames
.Select(MonoStaticFileProvider.BclFiles.GetFileInfo)
.First(item => item.Exists);
using (var data = new MemoryStream())
{
fileInfo.CreateReadStream().CopyTo(data);
return (AssemblyDefinition.ReadAssembly(fileInfo.CreateReadStream()), data.ToArray());
}
}
}
}

View File

@ -0,0 +1,108 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.IO;
using System.Linq;
using Xunit;
namespace Microsoft.AspNetCore.Blazor.Build.Test
{
public class RuntimeDependenciesResolverTest
{
[Fact]
public void FindsReferenceAssemblyGraph_ForStandaloneApp()
{
// Arrange
var standaloneAppAssembly = typeof(StandaloneApp.Program).Assembly;
var mainAssemblyLocation = standaloneAppAssembly.Location;
// This list of hints is populated by MSBuild so it will be on the output
// folder.
var hintPaths = File.ReadAllLines(Path.Combine(
Path.GetDirectoryName(mainAssemblyLocation),
"referenceHints.txt"));
var references = new[]
{
"Microsoft.AspNetCore.Blazor.Browser.dll",
"Microsoft.AspNetCore.Blazor.dll",
"Microsoft.Extensions.DependencyInjection.Abstractions.dll",
"Microsoft.Extensions.DependencyInjection.dll"
}.Select(a => hintPaths.Single(p => Path.GetFileName(p) == a))
.ToArray();
var basePath = Path.GetDirectoryName(typeof(RuntimeDependenciesResolverTest).Assembly.Location);
var bclLocations = new []
{
Path.Combine(basePath, "../../../../../src/mono/dist/optimized/bcl/"),
Path.Combine(basePath, "../../../../../src/mono/dist/optimized/bcl/Facades/"),
};
var expectedContents = new[]
{
/*
The current Mono WASM BCL forwards from netstandard.dll to various facade assemblies
in which small bits of implementation live, such as System.Xml.XPath.XDocument. So
if you reference netstandard, then you also reference System.Xml.XPath.XDocument.dll,
even though you're very unlikely to be calling it at runtime. That's why the following
list (for a very basic Blazor app) is longer than you'd expect.
These redundant references could be stripped out during publishing, but it's still
unfortunate that in development mode you'd see all these unexpected assemblies get
fetched from the server. We should try to get the Mono WASM BCL reorganized so that
all the implementation goes into mscorlib.dll, with the facade assemblies existing only
in case someone (or some 3rd party assembly) references them directly, but with their
implementations 100% forwarding to mscorlib.dll. Then in development you'd fetch far
fewer assemblies from the server, and during publishing, illink would remove all the
uncalled implementation code from mscorlib.dll anyway.
*/
"Microsoft.AspNetCore.Blazor.Browser.dll",
"Microsoft.AspNetCore.Blazor.dll",
"Microsoft.Extensions.DependencyInjection.Abstractions.dll",
"Microsoft.Extensions.DependencyInjection.dll",
"Mono.Security.dll",
"mscorlib.dll",
"netstandard.dll",
"StandaloneApp.dll",
"System.ComponentModel.Composition.dll",
"System.Core.dll",
"System.Data.dll",
"System.Diagnostics.StackTrace.dll",
"System.dll",
"System.Drawing.dll",
"System.Globalization.Extensions.dll",
"System.IO.Compression.dll",
"System.IO.Compression.FileSystem.dll",
"System.Net.Http.dll",
"System.Numerics.dll",
"System.Runtime.Serialization.dll",
"System.Runtime.Serialization.Primitives.dll",
"System.Runtime.Serialization.Xml.dll",
"System.Security.Cryptography.Algorithms.dll",
"System.Security.SecureString.dll",
"System.ServiceModel.Internals.dll",
"System.Transactions.dll",
"System.Web.Services.dll",
"System.Xml.dll",
"System.Xml.Linq.dll",
"System.Xml.XPath.XDocument.dll",
}.OrderBy(i => i, StringComparer.Ordinal)
.ToArray();
// Act
var paths = RuntimeDependenciesResolver
.ResolveRuntimeDependenciesCore(
mainAssemblyLocation,
references,
bclLocations);
var contents = paths
.Select(p => Path.GetFileName(p))
.OrderBy(i => i, StringComparer.Ordinal)
.ToArray();
// Assert
Assert.Equal(expectedContents, contents);
}
}
}

View File

@ -1,154 +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.Internal.Common.FileProviders;
using Microsoft.Extensions.FileProviders;
using System;
using System.IO;
using System.Linq;
using System.Text;
using Xunit;
namespace Microsoft.AspNetCore.Blazor.Common.Test
{
public class CompositeMountedFileProviderTest
{
private (string, byte[]) TestItem(string name) => (name, Array.Empty<byte>());
private (string, byte[]) TestItem(string name, string data) => (name, Encoding.UTF8.GetBytes(data));
private IFileProvider TestFileProvider(params string[] paths) => new InMemoryFileProvider(paths.Select(TestItem));
[Fact]
public void MountPointsMustStartWithSlash()
{
Assert.Throws<ArgumentException>(() =>
{
new CompositeMountedFileProvider(
("test", TestFileProvider("/something.txt")));
});
}
[Fact]
public void NonRootMountPointsMustNotEndWithSlash()
{
Assert.Throws<ArgumentException>(() =>
{
new CompositeMountedFileProvider(
("/test/", TestFileProvider("/something.txt")));
});
}
[Fact]
public void MountedFilePathsMustStartWithSlash()
{
Assert.Throws<ArgumentException>(() =>
{
new CompositeMountedFileProvider(
("/test", TestFileProvider("something.txt")));
});
}
[Fact]
public void CanMountFileProviderAtRoot()
{
// Arrange
IFileProvider childProvider = new InMemoryFileProvider(new[]
{
TestItem("/rootitem.txt", "Root item contents"),
TestItem("/subdir/another", "Another test item"),
});
var instance = new CompositeMountedFileProvider(("/", childProvider));
// Act
var rootContents = instance.GetDirectoryContents(string.Empty);
var subdirContents = instance.GetDirectoryContents("/subdir");
// Assert
Assert.Collection(rootContents,
item =>
{
Assert.Equal("/rootitem.txt", item.PhysicalPath);
Assert.False(item.IsDirectory);
Assert.Equal("Root item contents", new StreamReader(item.CreateReadStream()).ReadToEnd());
},
item =>
{
Assert.Equal("/subdir", item.PhysicalPath);
Assert.True(item.IsDirectory);
});
Assert.Collection(subdirContents,
item =>
{
Assert.Equal("/subdir/another", item.PhysicalPath);
Assert.False(item.IsDirectory);
Assert.Equal("Another test item", new StreamReader(item.CreateReadStream()).ReadToEnd());
});
}
[Fact]
public void CanMountFileProvidersAtSubPaths()
{
// Arrange
var instance = new CompositeMountedFileProvider(
("/dir", TestFileProvider("/first", "/A/second", "/A/third")),
("/dir/sub", TestFileProvider("/X", "/B/Y", "/B/Z")),
("/other", TestFileProvider("/final")));
// Act
var rootContents = instance.GetDirectoryContents("/");
var rootDirContents = instance.GetDirectoryContents("/dir");
var rootDirAContents = instance.GetDirectoryContents("/dir/A");
var rootDirSubContents = instance.GetDirectoryContents("/dir/sub");
var rootDirSubBContents = instance.GetDirectoryContents("/dir/sub/B");
var otherContents = instance.GetDirectoryContents("/other");
// Assert
Assert.Collection(rootContents,
item => Assert.Equal("/dir", item.PhysicalPath),
item => Assert.Equal("/other", item.PhysicalPath));
Assert.Collection(rootDirContents,
item => Assert.Equal("/dir/first", item.PhysicalPath),
item => Assert.Equal("/dir/A", item.PhysicalPath),
item => Assert.Equal("/dir/sub", item.PhysicalPath));
Assert.Collection(rootDirAContents,
item => Assert.Equal("/dir/A/second", item.PhysicalPath),
item => Assert.Equal("/dir/A/third", item.PhysicalPath));
Assert.Collection(rootDirSubContents,
item => Assert.Equal("/dir/sub/X", item.PhysicalPath),
item => Assert.Equal("/dir/sub/B", item.PhysicalPath));
Assert.Collection(rootDirSubBContents,
item => Assert.Equal("/dir/sub/B/Y", item.PhysicalPath),
item => Assert.Equal("/dir/sub/B/Z", item.PhysicalPath));
Assert.Collection(otherContents,
item => Assert.Equal("/other/final", item.PhysicalPath));
}
[Fact]
public void CanMountMultipleFileProvidersAtSameLocation()
{
// Arrange
var instance = new CompositeMountedFileProvider(
("/dir", TestFileProvider("/first")),
("/dir", TestFileProvider("/second")));
// Act
var contents = instance.GetDirectoryContents("/dir");
// Assert
Assert.Collection(contents,
item => Assert.Equal("/dir/first", item.PhysicalPath),
item => Assert.Equal("/dir/second", item.PhysicalPath));
}
[Fact]
public void DisallowsOverlappingFiles()
{
Assert.Throws<ArgumentException>(() =>
{
new CompositeMountedFileProvider(
("/dir", TestFileProvider("/file")),
("/", TestFileProvider("/dir/file")));
});
}
}
}

View File

@ -1,210 +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.Internal.Common.FileProviders;
using System;
using System.IO;
using System.Text;
using Xunit;
namespace Microsoft.AspNetCore.Blazor.Common.Test
{
public class InMemoryFileProviderTest
{
private (string, byte[]) TestItem(string name) => (name, Array.Empty<byte>());
[Fact]
public void RequiresPathsToStartWithSlash()
{
Assert.Throws<ArgumentException>(() =>
{
new InMemoryFileProvider(new[] { TestItem("item") });
});
}
[Fact]
public void RequiresPathsNotToEndWithSlash()
{
Assert.Throws<ArgumentException>(() =>
{
new InMemoryFileProvider(new[] { TestItem("/item/") });
});
}
[Fact]
public void ReturnsFileInfosForExistingFiles()
{
// Arrange
var instance = new InMemoryFileProvider(new[]
{
("/dirA/item", Encoding.UTF8.GetBytes("Contents of /dirA/item")),
("/dirB/item", Encoding.UTF8.GetBytes("Contents of /dirB/item"))
});
// Act
var dirAItem = instance.GetFileInfo("/dirA/item");
var dirBItem = instance.GetFileInfo("/dirB/item");
// Assert
Assert.Equal(
"Contents of /dirA/item",
new StreamReader(dirAItem.CreateReadStream()).ReadToEnd());
Assert.Equal(
"Contents of /dirB/item",
new StreamReader(dirBItem.CreateReadStream()).ReadToEnd());
Assert.True(dirAItem.Exists);
Assert.False(dirAItem.IsDirectory);
Assert.True((DateTime.Now - dirAItem.LastModified).TotalDays < 1); // Exact behaviour doesn't need to be defined (at least not yet) but it should be a valid date
Assert.Equal(22, dirAItem.Length);
Assert.Equal("item", dirAItem.Name);
Assert.Equal("/dirA/item", dirAItem.PhysicalPath);
}
[Fact]
public void ReturnsFileInfosForNonExistingFiles()
{
// Arrange
var instance = new InMemoryFileProvider(new[] { TestItem("/dirA/item") });
// Act
var mismatchedCaseItem = instance.GetFileInfo("/dira/item");
var dirBItem = instance.GetFileInfo("/dirB/item");
// Assert
Assert.False(mismatchedCaseItem.Exists);
Assert.False(dirBItem.Exists);
Assert.False(dirBItem.IsDirectory);
Assert.Equal("item", dirBItem.Name);
Assert.Equal("/dirB/item", dirBItem.PhysicalPath);
}
[Fact]
public void ReturnsDirectoryContentsForExistingDirectory()
{
// Arrange
var instance = new InMemoryFileProvider(new[]
{
TestItem("/dir/subdir/item1"),
TestItem("/dir/subdir/item2"),
TestItem("/dir/otherdir/item3")
});
// Act
var contents = instance.GetDirectoryContents("/dir/subdir");
// Assert
Assert.True(contents.Exists);
Assert.Collection(contents,
item => Assert.Equal("/dir/subdir/item1", item.PhysicalPath),
item => Assert.Equal("/dir/subdir/item2", item.PhysicalPath));
}
[Fact]
public void EmptyStringAndSlashAreBothInterpretedAsRootDir()
{
// Technically this test duplicates checking the behavior asserted
// previously (i.e., trailing slashes are ignored), but it's worth
// checking that nothing bad happens when the path is an empty string
// Arrange
var instance = new InMemoryFileProvider(new[] { TestItem("/item") });
// Act/Assert
Assert.Collection(instance.GetDirectoryContents(string.Empty),
item => Assert.Equal("/item", item.PhysicalPath));
Assert.Collection(instance.GetDirectoryContents("/"),
item => Assert.Equal("/item", item.PhysicalPath));
}
[Fact]
public void ReturnsDirectoryContentsIfGivenPathEndsWithSlash()
{
// Arrange
var instance = new InMemoryFileProvider(new[] { TestItem("/dir/subdir/item1") });
// Act
var contents = instance.GetDirectoryContents("/dir/subdir/");
// Assert
Assert.True(contents.Exists);
Assert.Collection(contents,
item => Assert.Equal("/dir/subdir/item1", item.PhysicalPath));
}
[Fact]
public void ReturnsDirectoryContentsForNonExistingDirectory()
{
// Arrange
var instance = new InMemoryFileProvider(new[] { TestItem("/dir/subdir/item1") });
// Act
var contents = instance.GetDirectoryContents("/dir/otherdir");
// Assert
Assert.False(contents.Exists);
Assert.Throws<InvalidOperationException>(() => contents.GetEnumerator());
}
[Fact]
public void IncludesSubdirectoriesInDirectoryContents()
{
// Arrange
var instance = new InMemoryFileProvider(new[] {
TestItem("/dir/sub/item1"),
TestItem("/dir/sub/item2"),
TestItem("/dir/sub2/item"),
TestItem("/unrelated/item")
});
// Act
var contents = instance.GetDirectoryContents("/dir");
// Assert
Assert.True(contents.Exists);
Assert.Collection(contents,
item =>
{
// For this example, verify all properties. Don't need to do this for all examples.
Assert.True(item.Exists);
Assert.True(item.IsDirectory);
Assert.Equal(default(DateTimeOffset), item.LastModified);
Assert.Throws<InvalidOperationException>(() => item.Length);
Assert.Throws<InvalidOperationException>(() => item.CreateReadStream());
Assert.Equal("sub", item.Name);
Assert.Equal("/dir/sub", item.PhysicalPath);
},
item =>
{
Assert.Equal("/dir/sub2", item.PhysicalPath);
Assert.True(item.IsDirectory);
});
}
[Fact]
public void HasAllAncestorDirectoriesForDirectory()
{
// Arrange
var instance = new InMemoryFileProvider(new[] { TestItem("/a/b/c") });
// Act/Assert
Assert.Collection(instance.GetDirectoryContents("/"),
item =>
{
Assert.Equal("/a", item.PhysicalPath);
Assert.True(item.IsDirectory);
});
Assert.Collection(instance.GetDirectoryContents("/a"),
item =>
{
Assert.Equal("/a/b", item.PhysicalPath);
Assert.True(item.IsDirectory);
});
Assert.Collection(instance.GetDirectoryContents("/a/b"),
item =>
{
Assert.Equal("/a/b/c", item.PhysicalPath);
Assert.False(item.IsDirectory);
});
}
}
}

View File

@ -1,19 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.5.0" />
<PackageReference Include="xunit" Version="2.3.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.3.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Blazor.Common\Microsoft.AspNetCore.Blazor.Common.csproj" />
</ItemGroup>
</Project>

View File

@ -1,20 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.FileProviders.Abstractions" Version="2.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.5.0" />
<PackageReference Include="xunit" Version="2.3.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.3.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Blazor.Mono\Microsoft.AspNetCore.Blazor.Mono.csproj" />
</ItemGroup>
</Project>

View File

@ -1,48 +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.Linq;
using Xunit;
namespace Microsoft.AspNetCore.Blazor.Mono.Test
{
public class MonoStaticFileProviderTest
{
[Fact]
public void SuppliesJsFiles()
{
// The collection is small enough that we can assert the exact full list
Assert.Collection(MonoStaticFileProvider.JsFiles.GetDirectoryContents("/").OrderBy(i => i.Name),
item => Assert.Equal("/asmjs", item.PhysicalPath),
item => Assert.Equal("/wasm", item.PhysicalPath));
Assert.Collection(MonoStaticFileProvider.JsFiles.GetDirectoryContents("/asmjs").OrderBy(i => i.Name),
item => Assert.Equal("/asmjs/mono.asm.js", item.PhysicalPath),
item => Assert.Equal("/asmjs/mono.js", item.PhysicalPath),
item => Assert.Equal("/asmjs/mono.js.mem", item.PhysicalPath));
Assert.Collection(MonoStaticFileProvider.JsFiles.GetDirectoryContents("/wasm").OrderBy(i => i.Name),
item => Assert.Equal("/wasm/mono.js", item.PhysicalPath),
item => Assert.Equal("/wasm/mono.wasm", item.PhysicalPath));
}
[Fact]
public void SuppliesBclFiles()
{
Assert.Collection(MonoStaticFileProvider.BclFiles.GetDirectoryContents("/"),
item => Assert.Equal("/bcl", item.PhysicalPath));
// Not an exhaustive list. The full list is long.
var actualBclRootFiles = MonoStaticFileProvider.BclFiles.GetDirectoryContents("/bcl");
Assert.Contains(actualBclRootFiles, item => item.PhysicalPath == "/bcl/mscorlib.dll");
Assert.Contains(actualBclRootFiles, item => item.PhysicalPath == "/bcl/System.Core.dll");
Assert.Contains(actualBclRootFiles, item => item.PhysicalPath == "/bcl/System.dll");
// Not an exhaustive list. The full list is long.
var actualFacades = MonoStaticFileProvider.BclFiles.GetDirectoryContents("/bcl/Facades");
Assert.Contains(actualFacades, item => item.PhysicalPath == "/bcl/Facades/netstandard.dll");
Assert.Contains(actualFacades, item => item.PhysicalPath == "/bcl/Facades/System.Console.dll");
}
}
}

View File

@ -2,6 +2,7 @@
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<BlazorLinkOnBuild>true</BlazorLinkOnBuild>
<!-- Local alternative to <RunArguments>blazor serve</RunArguments> -->
<RunCommand>dotnet</RunCommand>