Use System.Reflection.Metadata to generate BootConfig (#17156)
* Use System.Reflection.Metadata to generate BootConfig * Remove reference to Mono.Cecil * Remove support for auto embedded css \ js * Remove blazor.webassembly.js
This commit is contained in:
parent
92fce0da09
commit
5bdf75f3e1
|
|
@ -15,10 +15,6 @@ namespace Microsoft.AspNetCore.Blazor.Build.DevServer.Commands
|
||||||
"The path to a file that lists the paths to given referenced dll files",
|
"The path to a file that lists the paths to given referenced dll files",
|
||||||
CommandOptionType.SingleValue);
|
CommandOptionType.SingleValue);
|
||||||
|
|
||||||
var embeddedResourcesFile = command.Option("--embedded-resources",
|
|
||||||
"The path to a file that lists the paths of .NET assemblies that may contain embedded resources (typically, referenced assemblies in their pre-linked states)",
|
|
||||||
CommandOptionType.SingleValue);
|
|
||||||
|
|
||||||
var outputPath = command.Option("--output",
|
var outputPath = command.Option("--output",
|
||||||
"Path to the output file",
|
"Path to the output file",
|
||||||
CommandOptionType.SingleValue);
|
CommandOptionType.SingleValue);
|
||||||
|
|
@ -44,14 +40,9 @@ namespace Microsoft.AspNetCore.Blazor.Build.DevServer.Commands
|
||||||
? File.ReadAllLines(referencesFile.Value())
|
? File.ReadAllLines(referencesFile.Value())
|
||||||
: Array.Empty<string>();
|
: Array.Empty<string>();
|
||||||
|
|
||||||
var embeddedResourcesSources = embeddedResourcesFile.HasValue()
|
|
||||||
? File.ReadAllLines(embeddedResourcesFile.Value())
|
|
||||||
: Array.Empty<string>();
|
|
||||||
|
|
||||||
BootJsonWriter.WriteFile(
|
BootJsonWriter.WriteFile(
|
||||||
mainAssemblyPath.Value,
|
mainAssemblyPath.Value,
|
||||||
referencesSources,
|
referencesSources,
|
||||||
embeddedResourcesSources,
|
|
||||||
linkerEnabledFlag.HasValue(),
|
linkerEnabledFlag.HasValue(),
|
||||||
outputPath.Value());
|
outputPath.Value());
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,9 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Reflection;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using Microsoft.AspNetCore.Components;
|
using Microsoft.AspNetCore.Components;
|
||||||
using Mono.Cecil;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Blazor.Build
|
namespace Microsoft.AspNetCore.Blazor.Build
|
||||||
{
|
{
|
||||||
|
|
@ -16,79 +15,55 @@ namespace Microsoft.AspNetCore.Blazor.Build
|
||||||
public static void WriteFile(
|
public static void WriteFile(
|
||||||
string assemblyPath,
|
string assemblyPath,
|
||||||
string[] assemblyReferences,
|
string[] assemblyReferences,
|
||||||
string[] embeddedResourcesSources,
|
|
||||||
bool linkerEnabled,
|
bool linkerEnabled,
|
||||||
string outputPath)
|
string outputPath)
|
||||||
{
|
{
|
||||||
var embeddedContent = EmbeddedResourcesProcessor.ExtractEmbeddedResources(
|
|
||||||
embeddedResourcesSources, Path.GetDirectoryName(outputPath));
|
|
||||||
var bootJsonText = GetBootJsonContent(
|
var bootJsonText = GetBootJsonContent(
|
||||||
Path.GetFileName(assemblyPath),
|
AssemblyName.GetAssemblyName(assemblyPath).Name,
|
||||||
GetAssemblyEntryPoint(assemblyPath),
|
|
||||||
assemblyReferences,
|
assemblyReferences,
|
||||||
embeddedContent,
|
|
||||||
linkerEnabled);
|
linkerEnabled);
|
||||||
var normalizedOutputPath = Path.GetFullPath(outputPath);
|
var normalizedOutputPath = Path.GetFullPath(outputPath);
|
||||||
Console.WriteLine("Writing boot data to: " + normalizedOutputPath);
|
Console.WriteLine("Writing boot data to: " + normalizedOutputPath);
|
||||||
File.WriteAllText(normalizedOutputPath, bootJsonText);
|
File.WriteAllText(normalizedOutputPath, bootJsonText);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string GetBootJsonContent(string assemblyFileName, string entryPoint, string[] assemblyReferences, IEnumerable<EmbeddedResourceInfo> embeddedContent, bool linkerEnabled)
|
public static string GetBootJsonContent(string entryAssembly, string[] assemblyReferences, bool linkerEnabled)
|
||||||
{
|
{
|
||||||
var data = new BootJsonData(
|
var data = new BootJsonData(
|
||||||
assemblyFileName,
|
entryAssembly,
|
||||||
entryPoint,
|
|
||||||
assemblyReferences,
|
assemblyReferences,
|
||||||
embeddedContent,
|
|
||||||
linkerEnabled);
|
linkerEnabled);
|
||||||
return JsonSerializer.Serialize(data, JsonSerializerOptionsProvider.Options);
|
return JsonSerializer.Serialize(data, JsonSerializerOptionsProvider.Options);
|
||||||
}
|
}
|
||||||
|
|
||||||
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}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Defines the structure of a Blazor boot JSON file
|
/// Defines the structure of a Blazor boot JSON file
|
||||||
/// </summary>
|
/// </summary>
|
||||||
class BootJsonData
|
readonly struct BootJsonData
|
||||||
{
|
{
|
||||||
public string Main { get; }
|
/// <summary>
|
||||||
public string EntryPoint { get; }
|
/// Gets the name of the assembly with the application entry point
|
||||||
public IEnumerable<string> AssemblyReferences { get; }
|
/// </summary>
|
||||||
public IEnumerable<string> CssReferences { get; }
|
public string EntryAssembly { get; }
|
||||||
public IEnumerable<string> JsReferences { get; }
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the closure of assemblies to be loaded by Blazor WASM. This includes the application entry assembly.
|
||||||
|
/// </summary>
|
||||||
|
public IEnumerable<string> Assemblies { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value that determines if the linker is enabled.
|
||||||
|
/// </summary>
|
||||||
public bool LinkerEnabled { get; }
|
public bool LinkerEnabled { get; }
|
||||||
|
|
||||||
public BootJsonData(
|
public BootJsonData(
|
||||||
string entrypointAssemblyWithExtension,
|
string entryAssembly,
|
||||||
string entryPoint,
|
IEnumerable<string> assemblies,
|
||||||
IEnumerable<string> assemblyReferences,
|
|
||||||
IEnumerable<EmbeddedResourceInfo> embeddedContent,
|
|
||||||
bool linkerEnabled)
|
bool linkerEnabled)
|
||||||
{
|
{
|
||||||
Main = entrypointAssemblyWithExtension;
|
EntryAssembly = entryAssembly;
|
||||||
EntryPoint = entryPoint;
|
Assemblies = assemblies;
|
||||||
AssemblyReferences = assemblyReferences;
|
|
||||||
LinkerEnabled = linkerEnabled;
|
LinkerEnabled = linkerEnabled;
|
||||||
|
|
||||||
CssReferences = embeddedContent
|
|
||||||
.Where(c => c.Kind == EmbeddedResourceKind.Css)
|
|
||||||
.Select(c => c.RelativePath);
|
|
||||||
|
|
||||||
JsReferences = embeddedContent
|
|
||||||
.Where(c => c.Kind == EmbeddedResourceKind.JavaScript)
|
|
||||||
.Select(c => c.RelativePath);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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.
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Blazor.Build
|
|
||||||
{
|
|
||||||
internal class EmbeddedResourceInfo
|
|
||||||
{
|
|
||||||
public EmbeddedResourceKind Kind { get; }
|
|
||||||
public string RelativePath { get; }
|
|
||||||
|
|
||||||
public EmbeddedResourceInfo(EmbeddedResourceKind kind, string relativePath)
|
|
||||||
{
|
|
||||||
Kind = kind;
|
|
||||||
RelativePath = relativePath;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,12 +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.
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Blazor.Build
|
|
||||||
{
|
|
||||||
internal enum EmbeddedResourceKind
|
|
||||||
{
|
|
||||||
JavaScript,
|
|
||||||
Css,
|
|
||||||
Static
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,137 +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 Mono.Cecil;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Blazor.Build
|
|
||||||
{
|
|
||||||
internal class EmbeddedResourcesProcessor
|
|
||||||
{
|
|
||||||
const string ContentSubdirName = "_content";
|
|
||||||
|
|
||||||
private readonly static Dictionary<string, EmbeddedResourceKind> _knownResourceKindsByNamePrefix = new Dictionary<string, EmbeddedResourceKind>
|
|
||||||
{
|
|
||||||
{ "blazor:js:", EmbeddedResourceKind.JavaScript },
|
|
||||||
{ "blazor:css:", EmbeddedResourceKind.Css },
|
|
||||||
{ "blazor:file:", EmbeddedResourceKind.Static },
|
|
||||||
};
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Finds Blazor-specific embedded resources in the specified assemblies, writes them
|
|
||||||
/// to disk, and returns a description of those resources in dependency order.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="referencedAssemblyPaths">The paths to assemblies that may contain embedded resources.</param>
|
|
||||||
/// <param name="outputDir">The path to the directory where output is being written.</param>
|
|
||||||
/// <returns>A description of the embedded resources that were written to disk.</returns>
|
|
||||||
public static IReadOnlyList<EmbeddedResourceInfo> ExtractEmbeddedResources(
|
|
||||||
IEnumerable<string> referencedAssemblyPaths, string outputDir)
|
|
||||||
{
|
|
||||||
// Clean away any earlier state
|
|
||||||
var contentDir = Path.Combine(outputDir, ContentSubdirName);
|
|
||||||
if (Directory.Exists(contentDir))
|
|
||||||
{
|
|
||||||
Directory.Delete(contentDir, recursive: true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// First, get an ordered list of AssemblyDefinition instances
|
|
||||||
var referencedAssemblyDefinitions = referencedAssemblyPaths
|
|
||||||
.Where(path => !Path.GetFileName(path).StartsWith("System.", StringComparison.Ordinal)) // Skip System.* because they are never going to contain embedded resources that we want
|
|
||||||
.Select(path => AssemblyDefinition.ReadAssembly(path))
|
|
||||||
.ToList();
|
|
||||||
referencedAssemblyDefinitions.Sort(OrderWithReferenceSubjectFirst);
|
|
||||||
|
|
||||||
// Now process them in turn
|
|
||||||
return referencedAssemblyDefinitions
|
|
||||||
.SelectMany(def => ExtractEmbeddedResourcesFromSingleAssembly(def, outputDir))
|
|
||||||
.ToList()
|
|
||||||
.AsReadOnly();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static IEnumerable<EmbeddedResourceInfo> ExtractEmbeddedResourcesFromSingleAssembly(
|
|
||||||
AssemblyDefinition assemblyDefinition, string outputDirPath)
|
|
||||||
{
|
|
||||||
var assemblyName = assemblyDefinition.Name.Name;
|
|
||||||
foreach (var res in assemblyDefinition.MainModule.Resources)
|
|
||||||
{
|
|
||||||
if (TryExtractEmbeddedResource(assemblyName, res, outputDirPath, out var extractedResourceInfo))
|
|
||||||
{
|
|
||||||
yield return extractedResourceInfo;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool TryExtractEmbeddedResource(string assemblyName, Resource resource, string outputDirPath, out EmbeddedResourceInfo extractedResourceInfo)
|
|
||||||
{
|
|
||||||
if (resource is EmbeddedResource embeddedResource)
|
|
||||||
{
|
|
||||||
if (TryInterpretLogicalName(resource.Name, out var kind, out var name))
|
|
||||||
{
|
|
||||||
// Prefix the output path with the assembly name to ensure no clashes
|
|
||||||
// Also be invariant to the OS on which the package was built
|
|
||||||
name = Path.Combine(ContentSubdirName, assemblyName, EnsureHasPathSeparators(name, Path.DirectorySeparatorChar));
|
|
||||||
|
|
||||||
// Write the file content to disk, ensuring we don't try to write outside the output root
|
|
||||||
var outputPath = Path.GetFullPath(Path.Combine(outputDirPath, name));
|
|
||||||
if (!outputPath.StartsWith(outputDirPath))
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException($"Cannot write embedded resource from assembly '{assemblyName}' to '{outputPath}' because it is outside the expected directory {outputDirPath}");
|
|
||||||
}
|
|
||||||
WriteResourceFile(embeddedResource, outputPath);
|
|
||||||
|
|
||||||
// The URLs we write into the boot json file need to use web-style directory separators
|
|
||||||
extractedResourceInfo = new EmbeddedResourceInfo(kind, EnsureHasPathSeparators(name, '/'));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extractedResourceInfo = null;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void WriteResourceFile(EmbeddedResource resource, string outputPath)
|
|
||||||
{
|
|
||||||
Directory.CreateDirectory(Path.GetDirectoryName(outputPath));
|
|
||||||
using (var outputStream = File.OpenWrite(outputPath))
|
|
||||||
{
|
|
||||||
resource.GetResourceStream().CopyTo(outputStream);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string EnsureHasPathSeparators(string name, char desiredSeparatorChar) => name
|
|
||||||
.Replace('\\', desiredSeparatorChar)
|
|
||||||
.Replace('/', desiredSeparatorChar);
|
|
||||||
|
|
||||||
private static bool TryInterpretLogicalName(string logicalName, out EmbeddedResourceKind kind, out string resolvedName)
|
|
||||||
{
|
|
||||||
foreach (var kvp in _knownResourceKindsByNamePrefix)
|
|
||||||
{
|
|
||||||
if (logicalName.StartsWith(kvp.Key, StringComparison.Ordinal))
|
|
||||||
{
|
|
||||||
kind = kvp.Value;
|
|
||||||
resolvedName = logicalName.Substring(kvp.Key.Length);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
kind = default;
|
|
||||||
resolvedName = default;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// For each assembly B that references A, we want the resources from A to be loaded before
|
|
||||||
// the references for B (because B's resources might depend on A's resources)
|
|
||||||
private static int OrderWithReferenceSubjectFirst(AssemblyDefinition a, AssemblyDefinition b)
|
|
||||||
=> AssemblyHasReference(a, b) ? 1
|
|
||||||
: AssemblyHasReference(b, a) ? -1
|
|
||||||
: 0;
|
|
||||||
|
|
||||||
private static bool AssemblyHasReference(AssemblyDefinition from, AssemblyDefinition to)
|
|
||||||
=> from.MainModule.AssemblyReferences
|
|
||||||
.Select(reference => reference.Name)
|
|
||||||
.Contains(to.Name.Name, StringComparer.Ordinal);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -6,7 +6,9 @@ using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Mono.Cecil;
|
using System.Reflection;
|
||||||
|
using System.Reflection.Metadata;
|
||||||
|
using System.Reflection.PortableExecutable;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Blazor.Build
|
namespace Microsoft.AspNetCore.Blazor.Build
|
||||||
{
|
{
|
||||||
|
|
@ -27,19 +29,15 @@ namespace Microsoft.AspNetCore.Blazor.Build
|
||||||
string[] applicationDependencies,
|
string[] applicationDependencies,
|
||||||
string[] monoBclDirectories)
|
string[] monoBclDirectories)
|
||||||
{
|
{
|
||||||
var assembly = new AssemblyEntry(entryPoint, AssemblyDefinition.ReadAssembly(entryPoint));
|
var entryAssembly = new AssemblyEntry(entryPoint, GetAssemblyName(entryPoint));
|
||||||
|
|
||||||
var dependencies = applicationDependencies
|
var dependencies = CreateAssemblyLookup(applicationDependencies);
|
||||||
.Select(a => new AssemblyEntry(a, AssemblyDefinition.ReadAssembly(a)))
|
|
||||||
.ToArray();
|
|
||||||
|
|
||||||
var bcl = monoBclDirectories
|
var bcl = CreateAssemblyLookup(monoBclDirectories
|
||||||
.SelectMany(d => Directory.EnumerateFiles(d, "*.dll").Select(f => Path.Combine(d, f)))
|
.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(
|
var assemblyResolutionContext = new AssemblyResolutionContext(
|
||||||
assembly,
|
entryAssembly,
|
||||||
dependencies,
|
dependencies,
|
||||||
bcl);
|
bcl);
|
||||||
|
|
||||||
|
|
@ -47,6 +45,28 @@ namespace Microsoft.AspNetCore.Blazor.Build
|
||||||
|
|
||||||
var paths = assemblyResolutionContext.Results.Select(r => r.Path);
|
var paths = assemblyResolutionContext.Results.Select(r => r.Path);
|
||||||
return paths.Concat(FindPdbs(paths));
|
return paths.Concat(FindPdbs(paths));
|
||||||
|
|
||||||
|
static Dictionary<string, AssemblyEntry> CreateAssemblyLookup(IEnumerable<string> assemblyPaths)
|
||||||
|
{
|
||||||
|
var dictionary = new Dictionary<string, AssemblyEntry>(StringComparer.Ordinal);
|
||||||
|
foreach (var path in assemblyPaths)
|
||||||
|
{
|
||||||
|
var assemblyName = AssemblyName.GetAssemblyName(path).Name;
|
||||||
|
if (dictionary.TryGetValue(assemblyName, out var previous))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"Multiple assemblies found with the same assembly name '{assemblyName}':" +
|
||||||
|
Environment.NewLine + string.Join(Environment.NewLine, previous, path));
|
||||||
|
}
|
||||||
|
dictionary[assemblyName] = new AssemblyEntry(path, assemblyName);
|
||||||
|
}
|
||||||
|
|
||||||
|
return dictionary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetAssemblyName(string assemblyPath)
|
||||||
|
{
|
||||||
|
return AssemblyName.GetAssemblyName(assemblyPath).Name;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IEnumerable<string> FindPdbs(IEnumerable<string> dllPaths)
|
private static IEnumerable<string> FindPdbs(IEnumerable<string> dllPaths)
|
||||||
|
|
@ -59,46 +79,40 @@ namespace Microsoft.AspNetCore.Blazor.Build
|
||||||
public class AssemblyResolutionContext
|
public class AssemblyResolutionContext
|
||||||
{
|
{
|
||||||
public AssemblyResolutionContext(
|
public AssemblyResolutionContext(
|
||||||
AssemblyEntry assembly,
|
AssemblyEntry entryAssembly,
|
||||||
AssemblyEntry[] dependencies,
|
Dictionary<string, AssemblyEntry> dependencies,
|
||||||
AssemblyEntry[] bcl)
|
Dictionary<string, AssemblyEntry> bcl)
|
||||||
{
|
{
|
||||||
Assembly = assembly;
|
EntryAssembly = entryAssembly;
|
||||||
Dependencies = dependencies;
|
Dependencies = dependencies;
|
||||||
Bcl = bcl;
|
Bcl = bcl;
|
||||||
}
|
}
|
||||||
|
|
||||||
public AssemblyEntry Assembly { get; }
|
public AssemblyEntry EntryAssembly { get; }
|
||||||
public AssemblyEntry[] Dependencies { get; }
|
public Dictionary<string, AssemblyEntry> Dependencies { get; }
|
||||||
public AssemblyEntry[] Bcl { get; }
|
public Dictionary<string, AssemblyEntry> Bcl { get; }
|
||||||
|
|
||||||
public IList<AssemblyEntry> Results { get; } = new List<AssemblyEntry>();
|
public IList<AssemblyEntry> Results { get; } = new List<AssemblyEntry>();
|
||||||
|
|
||||||
internal void ResolveAssemblies()
|
internal void ResolveAssemblies()
|
||||||
{
|
{
|
||||||
var visitedAssemblies = new HashSet<string>();
|
var visitedAssemblies = new HashSet<string>();
|
||||||
var pendingAssemblies = new Stack<AssemblyNameReference>();
|
var pendingAssemblies = new Stack<string>();
|
||||||
pendingAssemblies.Push(Assembly.Definition.Name);
|
pendingAssemblies.Push(EntryAssembly.Name);
|
||||||
ResolveAssembliesCore();
|
ResolveAssembliesCore();
|
||||||
|
|
||||||
void ResolveAssembliesCore()
|
void ResolveAssembliesCore()
|
||||||
{
|
{
|
||||||
while (pendingAssemblies.TryPop(out var current))
|
while (pendingAssemblies.TryPop(out var current))
|
||||||
{
|
{
|
||||||
if (!visitedAssemblies.Contains(current.Name))
|
if (visitedAssemblies.Add(current))
|
||||||
{
|
{
|
||||||
visitedAssemblies.Add(current.Name);
|
// Not all references will be resolvable within the Mono BCL.
|
||||||
|
// Skipping unresolved assemblies here is equivalent to passing "--skip-unresolved true" to the Mono linker.
|
||||||
// Not all references will be resolvable within the Mono BCL, particularly
|
if (Resolve(current) is AssemblyEntry resolved)
|
||||||
// when building for server-side Blazor as you will be running on CoreCLR
|
|
||||||
// and therefore may depend on System.* BCL assemblies that aren't present
|
|
||||||
// in Mono WebAssembly. Skipping unresolved assemblies here is equivalent
|
|
||||||
// to passing "--skip-unresolved true" to the Mono linker.
|
|
||||||
var resolved = Resolve(current);
|
|
||||||
if (resolved != null)
|
|
||||||
{
|
{
|
||||||
Results.Add(resolved);
|
Results.Add(resolved);
|
||||||
var references = GetAssemblyReferences(resolved);
|
var references = GetAssemblyReferences(resolved.Path);
|
||||||
foreach (var reference in references)
|
foreach (var reference in references)
|
||||||
{
|
{
|
||||||
pendingAssemblies.Push(reference);
|
pendingAssemblies.Push(reference);
|
||||||
|
|
@ -108,58 +122,70 @@ namespace Microsoft.AspNetCore.Blazor.Build
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
IEnumerable<AssemblyNameReference> GetAssemblyReferences(AssemblyEntry current) =>
|
AssemblyEntry? Resolve(string assemblyName)
|
||||||
current.Definition.Modules.SelectMany(m => m.AssemblyReferences);
|
|
||||||
|
|
||||||
AssemblyEntry Resolve(AssemblyNameReference current)
|
|
||||||
{
|
{
|
||||||
if (Assembly.Definition.Name.Name == current.Name)
|
if (EntryAssembly.Name == assemblyName)
|
||||||
{
|
{
|
||||||
return Assembly;
|
return EntryAssembly;
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
// 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.
|
// assembly if there is a candidate assembly and an equivalent mono assembly.
|
||||||
if (bclAssemblyCandidate != null)
|
if (Bcl.TryGetValue(assemblyName, out var assembly) ||
|
||||||
|
Dependencies.TryGetValue(assemblyName, out assembly))
|
||||||
{
|
{
|
||||||
return bclAssemblyCandidate;
|
return assembly;
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static IReadOnlyList<string> GetAssemblyReferences(string assemblyPath)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var peReader = new PEReader(File.OpenRead(assemblyPath));
|
||||||
|
if (!peReader.HasMetadata)
|
||||||
|
{
|
||||||
|
return Array.Empty<string>(); // not a managed assembly
|
||||||
|
}
|
||||||
|
|
||||||
|
var metadataReader = peReader.GetMetadataReader();
|
||||||
|
|
||||||
|
var references = new List<string>();
|
||||||
|
foreach (var handle in metadataReader.AssemblyReferences)
|
||||||
|
{
|
||||||
|
var reference = metadataReader.GetAssemblyReference(handle);
|
||||||
|
var referenceName = metadataReader.GetString(reference.Name);
|
||||||
|
|
||||||
|
references.Add(referenceName);
|
||||||
|
}
|
||||||
|
|
||||||
|
return references;
|
||||||
|
}
|
||||||
|
catch (BadImageFormatException)
|
||||||
|
{
|
||||||
|
// not a PE file, or invalid metadata
|
||||||
|
}
|
||||||
|
|
||||||
|
return Array.Empty<string>(); // not a managed assembly
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[DebuggerDisplay("{ToString(),nq}")]
|
[DebuggerDisplay("{ToString(),nq}")]
|
||||||
public class AssemblyEntry
|
public readonly struct AssemblyEntry
|
||||||
{
|
{
|
||||||
public AssemblyEntry(string path, AssemblyDefinition definition)
|
public AssemblyEntry(string path, string name)
|
||||||
{
|
{
|
||||||
Path = path;
|
Path = path;
|
||||||
Definition = definition;
|
Name = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Path { get; set; }
|
public string Path { get; }
|
||||||
public AssemblyDefinition Definition { get; set; }
|
public string Name { get; }
|
||||||
|
|
||||||
public override string ToString() => Definition.FullName;
|
public override string ToString() => Name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,12 +27,8 @@
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Condition="'$(BuildNodeJS)' != 'false' and '$(BuildingInsideVisualStudio)' != 'true'" Include="$(RepoRoot)src\Components\Web.JS\Microsoft.AspNetCore.Components.Web.JS.npmproj" ReferenceOutputAssembly="false" />
|
<ProjectReference Condition="'$(BuildNodeJS)' != 'false' and '$(BuildingInsideVisualStudio)' != 'true'" Include="$(RepoRoot)src\Components\Web.JS\Microsoft.AspNetCore.Components.Web.JS.npmproj" ReferenceOutputAssembly="false" />
|
||||||
<Reference Include="Microsoft.AspNetCore.Components" />
|
|
||||||
<Reference Include="Microsoft.Extensions.CommandLineUtils.Sources" />
|
<Reference Include="Microsoft.Extensions.CommandLineUtils.Sources" />
|
||||||
<Reference Include="Microsoft.Extensions.FileProviders.Composite" />
|
<Reference Include="System.Reflection.Metadata" />
|
||||||
<Reference Include="Microsoft.Extensions.FileProviders.Physical" />
|
|
||||||
<Reference Include="Mono.Cecil" />
|
|
||||||
<Reference Include="System.CodeDom" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
||||||
|
|
@ -623,14 +623,12 @@
|
||||||
Inputs="$(BlazorBuildBootJsonInputsCache);@(_BlazorDependencyInput)"
|
Inputs="$(BlazorBuildBootJsonInputsCache);@(_BlazorDependencyInput)"
|
||||||
Outputs="$(BlazorBootJsonIntermediateOutputPath)">
|
Outputs="$(BlazorBootJsonIntermediateOutputPath)">
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<_UnlinkedAppReferencesPaths Include="@(_BlazorDependencyInput)" />
|
<_AppReferences Include="@(BlazorItemOutput->WithMetadataValue('Type','Assembly')->'%(FileName)%(Extension)')" />
|
||||||
<_AppReferences Include="@(BlazorItemOutput->WithMetadataValue('Type','Assembly')->WithMetadataValue('PrimaryOutput','')->'%(FileName)%(Extension)')" />
|
|
||||||
<_AppReferences Include="@(BlazorItemOutput->WithMetadataValue('Type','Pdb')->'%(FileName)%(Extension)')" Condition="'$(BlazorEnableDebugging)' == 'true'" />
|
<_AppReferences Include="@(BlazorItemOutput->WithMetadataValue('Type','Pdb')->'%(FileName)%(Extension)')" Condition="'$(BlazorEnableDebugging)' == 'true'" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<_LinkerEnabledFlag Condition="'$(_BlazorShouldLinkApplicationAssemblies)' != ''">--linker-enabled</_LinkerEnabledFlag>
|
<_LinkerEnabledFlag Condition="'$(_BlazorShouldLinkApplicationAssemblies)' != ''">--linker-enabled</_LinkerEnabledFlag>
|
||||||
<_ReferencesArg Condition="'@(_AppReferences)' != ''">--references "$(BlazorBootJsonReferencesFilePath)"</_ReferencesArg>
|
<_ReferencesArg Condition="'@(_AppReferences)' != ''">--references "$(BlazorBootJsonReferencesFilePath)"</_ReferencesArg>
|
||||||
<_EmbeddedResourcesArg Condition="'@(_UnlinkedAppReferencesPaths)' != ''">--embedded-resources "$(BlazorEmbeddedResourcesConfigFilePath)"</_EmbeddedResourcesArg>
|
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<WriteLinesToFile
|
<WriteLinesToFile
|
||||||
|
|
@ -638,13 +636,7 @@
|
||||||
Lines="@(_AppReferences)"
|
Lines="@(_AppReferences)"
|
||||||
Overwrite="true" />
|
Overwrite="true" />
|
||||||
|
|
||||||
<WriteLinesToFile
|
<Exec Command="$(BlazorBuildExe) write-boot-json "@(IntermediateAssembly)" $(_ReferencesArg) $(_LinkerEnabledFlag) --output "$(BlazorBootJsonIntermediateOutputPath)"" />
|
||||||
Condition="'@(_UnlinkedAppReferencesPaths)' != ''"
|
|
||||||
File="$(BlazorEmbeddedResourcesConfigFilePath)"
|
|
||||||
Lines="@(_UnlinkedAppReferencesPaths)"
|
|
||||||
Overwrite="true" />
|
|
||||||
|
|
||||||
<Exec Command="$(BlazorBuildExe) write-boot-json "@(IntermediateAssembly)" $(_ReferencesArg) $(_EmbeddedResourcesArg) $(_LinkerEnabledFlag) --output "$(BlazorBootJsonIntermediateOutputPath)"" />
|
|
||||||
|
|
||||||
<ItemGroup Condition="Exists('$(BlazorBootJsonIntermediateOutputPath)')">
|
<ItemGroup Condition="Exists('$(BlazorBootJsonIntermediateOutputPath)')">
|
||||||
<_BlazorBootJson Include="$(BlazorBootJsonIntermediateOutputPath)" />
|
<_BlazorBootJson Include="$(BlazorBootJsonIntermediateOutputPath)" />
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,6 @@
|
||||||
|
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using System;
|
|
||||||
using System.Linq;
|
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Blazor.Build.Test
|
namespace Microsoft.AspNetCore.Blazor.Build.Test
|
||||||
|
|
@ -15,48 +13,16 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test
|
||||||
public void ProducesJsonReferencingAssemblyAndDependencies()
|
public void ProducesJsonReferencingAssemblyAndDependencies()
|
||||||
{
|
{
|
||||||
// Arrange/Act
|
// Arrange/Act
|
||||||
var assemblyReferences = new string[] { "System.Abc.dll", "MyApp.ClassLib.dll", };
|
var assemblyReferences = new string[] { "MyApp.EntryPoint.dll", "System.Abc.dll", "MyApp.ClassLib.dll", };
|
||||||
var content = BootJsonWriter.GetBootJsonContent(
|
var content = BootJsonWriter.GetBootJsonContent(
|
||||||
"MyApp.Entrypoint.dll",
|
"MyApp.Entrypoint.dll",
|
||||||
"MyNamespace.MyType::MyMethod",
|
|
||||||
assemblyReferences,
|
assemblyReferences,
|
||||||
Enumerable.Empty<EmbeddedResourceInfo>(),
|
|
||||||
linkerEnabled: true);
|
linkerEnabled: true);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
var parsedContent = JsonConvert.DeserializeObject<JObject>(content);
|
var parsedContent = JsonConvert.DeserializeObject<JObject>(content);
|
||||||
Assert.Equal("MyApp.Entrypoint.dll", parsedContent["main"].Value<string>());
|
Assert.Equal("MyApp.Entrypoint.dll", parsedContent["entryAssembly"].Value<string>());
|
||||||
Assert.Equal("MyNamespace.MyType::MyMethod", parsedContent["entryPoint"].Value<string>());
|
Assert.Equal(assemblyReferences, parsedContent["assemblies"].Values<string>());
|
||||||
Assert.Equal(assemblyReferences, parsedContent["assemblyReferences"].Values<string>());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void IncludesReferencesToEmbeddedContent()
|
|
||||||
{
|
|
||||||
// Arrange/Act
|
|
||||||
var embeddedContent = new[]
|
|
||||||
{
|
|
||||||
new EmbeddedResourceInfo(EmbeddedResourceKind.Static, "my/static/file"),
|
|
||||||
new EmbeddedResourceInfo(EmbeddedResourceKind.Css, "css/first.css"),
|
|
||||||
new EmbeddedResourceInfo(EmbeddedResourceKind.JavaScript, "javascript/first.js"),
|
|
||||||
new EmbeddedResourceInfo(EmbeddedResourceKind.Css, "css/second.css"),
|
|
||||||
new EmbeddedResourceInfo(EmbeddedResourceKind.JavaScript, "javascript/second.js"),
|
|
||||||
};
|
|
||||||
var content = BootJsonWriter.GetBootJsonContent(
|
|
||||||
"MyApp.Entrypoint",
|
|
||||||
"MyNamespace.MyType::MyMethod",
|
|
||||||
assemblyReferences: new[] { "Something.dll" },
|
|
||||||
embeddedContent: embeddedContent,
|
|
||||||
linkerEnabled: true);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
var parsedContent = JsonConvert.DeserializeObject<JObject>(content);
|
|
||||||
Assert.Equal(
|
|
||||||
new[] { "css/first.css", "css/second.css" },
|
|
||||||
parsedContent["cssReferences"].Values<string>());
|
|
||||||
Assert.Equal(
|
|
||||||
new[] { "javascript/first.js", "javascript/second.js" },
|
|
||||||
parsedContent["jsReferences"].Values<string>());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,3 @@
|
||||||
node_modules/
|
node_modules/
|
||||||
dist/Debug/
|
dist/Debug/
|
||||||
|
dist/Release/blazor.webassembly.js
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -2,7 +2,6 @@ import '@dotnet/jsinterop';
|
||||||
import './GlobalExports';
|
import './GlobalExports';
|
||||||
import * as Environment from './Environment';
|
import * as Environment from './Environment';
|
||||||
import { monoPlatform } from './Platform/Mono/MonoPlatform';
|
import { monoPlatform } from './Platform/Mono/MonoPlatform';
|
||||||
import { getAssemblyNameFromUrl } from './Platform/Url';
|
|
||||||
import { renderBatch } from './Rendering/Renderer';
|
import { renderBatch } from './Rendering/Renderer';
|
||||||
import { SharedMemoryRenderBatch } from './Rendering/RenderBatch/SharedMemoryRenderBatch';
|
import { SharedMemoryRenderBatch } from './Rendering/RenderBatch/SharedMemoryRenderBatch';
|
||||||
import { Pointer } from './Platform/Platform';
|
import { Pointer } from './Platform/Platform';
|
||||||
|
|
@ -39,15 +38,13 @@ async function boot(options?: any): Promise<void> {
|
||||||
|
|
||||||
// Fetch the boot JSON file
|
// Fetch the boot JSON file
|
||||||
const bootConfig = await fetchBootConfigAsync();
|
const bootConfig = await fetchBootConfigAsync();
|
||||||
const embeddedResourcesPromise = loadEmbeddedResourcesAsync(bootConfig);
|
|
||||||
|
|
||||||
if (!bootConfig.linkerEnabled) {
|
if (!bootConfig.linkerEnabled) {
|
||||||
console.info('Blazor is running in dev mode without IL stripping. To make the bundle size significantly smaller, publish the application or see https://go.microsoft.com/fwlink/?linkid=870414');
|
console.info('Blazor is running in dev mode without IL stripping. To make the bundle size significantly smaller, publish the application or see https://go.microsoft.com/fwlink/?linkid=870414');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine the URLs of the assemblies we want to load, then begin fetching them all
|
// Determine the URLs of the assemblies we want to load, then begin fetching them all
|
||||||
const loadAssemblyUrls = [bootConfig.main]
|
const loadAssemblyUrls = bootConfig.assemblies
|
||||||
.concat(bootConfig.assemblyReferences)
|
|
||||||
.map(filename => `_framework/_bin/${filename}`);
|
.map(filename => `_framework/_bin/${filename}`);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
@ -56,12 +53,8 @@ async function boot(options?: any): Promise<void> {
|
||||||
throw new Error(`Failed to start platform. Reason: ${ex}`);
|
throw new Error(`Failed to start platform. Reason: ${ex}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Before we start running .NET code, be sure embedded content resources are all loaded
|
|
||||||
await embeddedResourcesPromise;
|
|
||||||
|
|
||||||
// Start up the application
|
// Start up the application
|
||||||
const mainAssemblyName = getAssemblyNameFromUrl(bootConfig.main);
|
platform.callEntryPoint(bootConfig.entryAssembly);
|
||||||
platform.callEntryPoint(mainAssemblyName);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchBootConfigAsync() {
|
async function fetchBootConfigAsync() {
|
||||||
|
|
@ -71,36 +64,10 @@ async function fetchBootConfigAsync() {
|
||||||
return bootConfigResponse.json() as Promise<BootJsonData>;
|
return bootConfigResponse.json() as Promise<BootJsonData>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadEmbeddedResourcesAsync(bootConfig: BootJsonData): Promise<any> {
|
|
||||||
const cssLoadingPromises = bootConfig.cssReferences.map(cssReference => {
|
|
||||||
const linkElement = document.createElement('link');
|
|
||||||
linkElement.rel = 'stylesheet';
|
|
||||||
linkElement.href = cssReference;
|
|
||||||
return loadResourceFromElement(linkElement);
|
|
||||||
});
|
|
||||||
const jsLoadingPromises = bootConfig.jsReferences.map(jsReference => {
|
|
||||||
const scriptElement = document.createElement('script');
|
|
||||||
scriptElement.src = jsReference;
|
|
||||||
return loadResourceFromElement(scriptElement);
|
|
||||||
});
|
|
||||||
return Promise.all(cssLoadingPromises.concat(jsLoadingPromises));
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadResourceFromElement(element: HTMLElement) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
element.onload = resolve;
|
|
||||||
element.onerror = reject;
|
|
||||||
document.head!.appendChild(element);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Keep in sync with BootJsonData in Microsoft.AspNetCore.Blazor.Build
|
// Keep in sync with BootJsonData in Microsoft.AspNetCore.Blazor.Build
|
||||||
interface BootJsonData {
|
interface BootJsonData {
|
||||||
main: string;
|
entryAssembly: string;
|
||||||
entryPoint: string;
|
assemblies: string[];
|
||||||
assemblyReferences: string[];
|
|
||||||
cssReferences: string[];
|
|
||||||
jsReferences: string[];
|
|
||||||
linkerEnabled: boolean;
|
linkerEnabled: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue