Skip to content

Add support for loading satellite assemblies #20033

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Mar 26, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/Components/Web.JS/dist/Release/blazor.webassembly.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/Components/Web.JS/src/Platform/BootConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export interface ResourceGroups {
readonly assembly: ResourceList;
readonly pdb?: ResourceList;
readonly runtime: ResourceList;
readonly satelliteResources?: { [cultureName: string] : ResourceList };
}

export type ResourceList = { [name: string]: string };

31 changes: 31 additions & 0 deletions src/Components/Web.JS/src/Platform/Mono/MonoPlatform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,37 @@ function createEmscriptenModuleInstance(resourceLoader: WebAssemblyResourceLoade
// of the extensions in the URLs. This allows loading assemblies with arbitrary filenames.
assembliesBeingLoaded.forEach(r => addResourceAsAssembly(r, changeExtension(r.name, '.dll')));
pdbsBeingLoaded.forEach(r => addResourceAsAssembly(r, r.name));

// Wire-up callbacks for satellite assemblies. Blazor will call these as part of the application
// startup sequence to load satellite assemblies for the application's culture.
window['Blazor']._internal.getSatelliteAssemblies = (culturesToLoadDotNetArray: System_Array<System_String>) : System_Object => {
const culturesToLoad = BINDING.mono_array_to_js_array<System_String, string>(culturesToLoadDotNetArray);
const satelliteResources = resourceLoader.bootConfig.resources.satelliteResources;

if (satelliteResources) {
const resourcePromises = Promise.all(culturesToLoad
.filter(culture => satelliteResources.hasOwnProperty(culture))
.map(culture => resourceLoader.loadResources(satelliteResources[culture], fileName => `_framework/_bin/${fileName}`))
.reduce((previous, next) => previous.concat(next), new Array<LoadingResource>())
.map(async resource => (await resource.response).arrayBuffer()));

return BINDING.js_to_mono_obj(
resourcePromises.then(resourcesToLoad => {
if (resourcesToLoad.length) {
window['Blazor']._internal.readSatelliteAssemblies = () => {
const array = BINDING.mono_obj_array_new(resourcesToLoad.length);
for (var i = 0; i < resourcesToLoad.length; i++) {
BINDING.mono_obj_array_set(array, i, BINDING.js_typed_array_to_array(new Uint8Array(resourcesToLoad[i])));
}
return array;
};
}

return resourcesToLoad.length;
}));
}
return BINDING.js_to_mono_obj(Promise.resolve(0));
}
});

module.postRun.push(() => {
Expand Down
9 changes: 6 additions & 3 deletions src/Components/Web.JS/src/Platform/Mono/MonoTypes.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Pointer, System_String } from '../Platform';
import { Pointer, System_String, System_Array, System_Object } from '../Platform';

// Mono uses this global to hang various debugging-related items on

Expand All @@ -10,9 +10,12 @@ declare interface MONO {

// Mono uses this global to hold low-level interop APIs
declare interface BINDING {
mono_obj_array_new(length: number): System_Array<System_Object>;
mono_obj_array_set(array: System_Array<System_Object>, index: Number, value: System_Object): void;
js_string_to_mono_string(jsString: string): System_String;
js_typed_array_to_array(array: Uint8Array): Pointer;
js_typed_array_to_array<T>(array: Array<T>): Pointer;
js_typed_array_to_array(array: Uint8Array): System_Object;
js_to_mono_obj(jsObject: any) : System_Object;
mono_array_to_js_array<TInput, TOutput>(array: System_Array<TInput>) : Array<TOutput>;
conv_string(dotnetString: System_String | null): string | null;
bind_static_method(fqn: string, signature?: string): Function;
}
Expand Down
4 changes: 2 additions & 2 deletions src/Components/Web.JS/src/Platform/WebAssemblyConfigLoader.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { BootConfigResult } from './BootConfig';
import { System_String, Pointer } from './Platform';
import { System_String, System_Object } from './Platform';

export class WebAssemblyConfigLoader {
static async initAsync(bootConfigResult: BootConfigResult): Promise<void> {
Expand All @@ -9,7 +9,7 @@ export class WebAssemblyConfigLoader {
.filter(name => name === 'appsettings.json' || name === `appsettings.${bootConfigResult.applicationEnvironment}.json`)
.map(async name => ({ name, content: await getConfigBytes(name) })));

window['Blazor']._internal.getConfig = (dotNetFileName: System_String) : Pointer | undefined => {
window['Blazor']._internal.getConfig = (dotNetFileName: System_String) : System_Object | undefined => {
const fileName = BINDING.conv_string(dotNetFileName);
const resolvedFile = configFiles.find(f => f.name === fileName);
return resolvedFile ? BINDING.js_typed_array_to_array(resolvedFile.content) : undefined;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
using System.Text;
using Microsoft.Build.Framework;
Expand Down Expand Up @@ -61,28 +62,56 @@ internal void WriteBootJson(Stream output, string entryAssemblyName)
cacheBootResources = CacheBootResources,
debugBuild = DebugBuild,
linkerEnabled = LinkerEnabled,
resources = new Dictionary<ResourceType, ResourceHashesByNameDictionary>(),
resources = new ResourcesData(),
config = new List<string>(),
};

// Build a two-level dictionary of the form:
// - BootResourceType (e.g., "assembly")
// - assembly:
// - UriPath (e.g., "System.Text.Json.dll")
// - ContentHash (e.g., "4548fa2e9cf52986")
// - runtime:
// - UriPath (e.g., "dotnet.js")
// - ContentHash (e.g., "3448f339acf512448")
if (Resources != null)
{
var resourceData = result.resources;
foreach (var resource in Resources)
{
var resourceTypeMetadata = resource.GetMetadata("BootManifestResourceType");
if (!Enum.TryParse<ResourceType>(resourceTypeMetadata, out var resourceType))
ResourceHashesByNameDictionary resourceList;
switch (resourceTypeMetadata)
{
throw new NotSupportedException($"Unsupported BootManifestResourceType metadata value: {resourceTypeMetadata}");
}

if (!result.resources.TryGetValue(resourceType, out var resourceList))
{
resourceList = new ResourceHashesByNameDictionary();
result.resources.Add(resourceType, resourceList);
case "runtime":
resourceList = resourceData.runtime;
break;
case "assembly":
resourceList = resourceData.assembly;
break;
case "pdb":
resourceData.pdb = new ResourceHashesByNameDictionary();
resourceList = resourceData.pdb;
break;
case "satellite":
if (resourceData.satelliteResources is null)
{
resourceData.satelliteResources = new Dictionary<string, ResourceHashesByNameDictionary>(StringComparer.OrdinalIgnoreCase);
}
var resourceCulture = resource.GetMetadata("Culture");
if (resourceCulture is null)
{
Log.LogWarning("Satellite resource {0} does not specify required metadata 'Culture'.", resource);
continue;
}

if (!resourceData.satelliteResources.TryGetValue(resourceCulture, out resourceList))
{
resourceList = new ResourceHashesByNameDictionary();
resourceData.satelliteResources.Add(resourceCulture, resourceList);
}
break;
default:
throw new NotSupportedException($"Unsupported BootManifestResourceType metadata value: {resourceTypeMetadata}");
}

var resourceName = GetResourceName(resource);
Expand Down Expand Up @@ -142,7 +171,7 @@ public class BootJsonData
/// and values are SHA-256 hashes formatted in prefixed base-64 style (e.g., 'sha256-abcdefg...')
/// as used for subresource integrity checking.
/// </summary>
public Dictionary<ResourceType, ResourceHashesByNameDictionary> resources { get; set; }
public ResourcesData resources { get; set; } = new ResourcesData();

/// <summary>
/// Gets a value that determines whether to enable caching of the <see cref="resources"/>
Expand All @@ -166,11 +195,29 @@ public class BootJsonData
public List<string> config { get; set; }
}

public enum ResourceType
public class ResourcesData
{
assembly,
pdb,
runtime,
/// <summary>
/// .NET Wasm runtime resources (dotnet.wasm, dotnet.js) etc.
/// </summary>
public ResourceHashesByNameDictionary runtime { get; set; } = new ResourceHashesByNameDictionary();

/// <summary>
/// "assembly" (.dll) resources
/// </summary>
public ResourceHashesByNameDictionary assembly { get; set; } = new ResourceHashesByNameDictionary();

/// <summary>
/// "debug" (.pdb) resources
/// </summary>
[DataMember(EmitDefaultValue = false)]
public ResourceHashesByNameDictionary pdb { get; set; }

/// <summary>
/// localization (.satellite resx) resources
/// </summary>
[DataMember(EmitDefaultValue = false)]
public Dictionary<string, ResourceHashesByNameDictionary> satelliteResources { get; set; }
}
#pragma warning restore IDE1006 // Naming Styles
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@
<AdditionalMonoLinkerOptions>--disable-opt unreachablebodies --verbose --strip-security true --exclude-feature com -v false -c link -u link -b true</AdditionalMonoLinkerOptions>

<_BlazorJsPath Condition="'$(_BlazorJsPath)' == ''">$(MSBuildThisFileDirectory)..\tools\blazor\blazor.webassembly.js</_BlazorJsPath>
<_BaseBlazorDistPath>dist\</_BaseBlazorDistPath>
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had to account for these in path manipulation. Removed since this isn't used any more.

<_BaseBlazorRuntimeOutputPath>$(_BaseBlazorDistPath)_framework\</_BaseBlazorRuntimeOutputPath>
<_BaseBlazorRuntimeOutputPath>_framework\</_BaseBlazorRuntimeOutputPath>
<_BlazorRuntimeBinOutputPath>$(_BaseBlazorRuntimeOutputPath)_bin\</_BlazorRuntimeBinOutputPath>
<_BlazorRuntimeWasmOutputPath>$(_BaseBlazorRuntimeOutputPath)wasm\</_BlazorRuntimeWasmOutputPath>
<_BlazorBuiltInBclLinkerDescriptor>$(MSBuildThisFileDirectory)BuiltInBclLinkerDescriptor.xml</_BlazorBuiltInBclLinkerDescriptor>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,14 @@
-->
<ItemGroup>
<!-- Assemblies from packages -->
<_BlazorManagedRuntimeAssemby Include="@(RuntimeCopyLocalItems)" />
<_BlazorManagedRuntimeAssembly Include="@(RuntimeCopyLocalItems)" />

<!-- Assemblies from other references -->
<_BlazorUserRuntimeAssembly Include="@(ReferencePath->WithMetadataValue('CopyLocal', 'true'))" />
<_BlazorUserRuntimeAssembly Include="@(ReferenceDependencyPaths->WithMetadataValue('CopyLocal', 'true'))" />

<_BlazorManagedRuntimeAssemby Include="@(_BlazorUserRuntimeAssembly)" />
<_BlazorManagedRuntimeAssemby Include="@(IntermediateAssembly)" />
<_BlazorManagedRuntimeAssembly Include="@(_BlazorUserRuntimeAssembly)" />
<_BlazorManagedRuntimeAssembly Include="@(IntermediateAssembly)" />
</ItemGroup>

<MakeDir Directories="$(_BlazorIntermediateOutputPath)" />
Expand All @@ -70,73 +70,68 @@
satellite assemblies, this should include all assemblies needed to run the application.
-->
<ItemGroup>
<_BlazorJSFile Include="$(_BlazorJSPath)" />
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Slight reshuffling to make it easier to view the binary logs

<_BlazorJSFile Include="$(_BlazorJSMapPath)" Condition="Exists('$(_BlazorJSMapPath)')" />
<_DotNetWasmRuntimeFile Include="$(ComponentsWebAssemblyRuntimePath)*" />

<!--
ReferenceCopyLocalPaths includes all files that are part of the build out with CopyLocalLockFileAssemblies on.
Remove assemblies that are inputs to calculating the assembly closure. Instead use the resolved outputs, since it is the minimal set.

ReferenceCopyLocalPaths also includes satellite assemblies from referenced projects but are inexpicably missing
any metadata that might allow them to be differentiated. We'll explicitly add those
to _BlazorOutputWithTargetPath so that satellite assemblies from packages, the current project and referenced project
are all treated the same.
-->
<_BlazorCopyLocalPaths Include="@(ReferenceCopyLocalPaths)" Condition="'%(Extension)' == '.dll'" />
<_BlazorCopyLocalPaths Remove="@(_BlazorManagedRuntimeAssemby)" />
<_BlazorCopyLocalPaths Include="@(ReferenceCopyLocalPaths)"
Exclude="@(_BlazorManagedRuntimeAssembly);@(ReferenceSatellitePaths)"
Condition="'%(Extension)' == '.dll'" />

<_BlazorCopyLocalPaths Include="@(IntermediateSatelliteAssembliesWithTargetPath)">
<DestinationSubDirectory>%(IntermediateSatelliteAssembliesWithTargetPath.Culture)\</DestinationSubDirectory>
</_BlazorCopyLocalPaths>

<_BlazorOutputWithTargetPath Include="@(_BlazorCopyLocalPaths)">
<!-- This group is for satellite assemblies. We set the resource name to include a path, e.g. "fr\\SomeAssembly.resources.dll" -->
<BootManifestResourceType Condition="'%(Extension)' == '.dll'">assembly</BootManifestResourceType>
<BootManifestResourceType Condition="'%(Extension)' == '.pdb'">pdb</BootManifestResourceType>
<BootManifestResourceType Condition="'%(_BlazorCopyLocalPaths.Culture)' == '' AND '%(_BlazorCopyLocalPaths.Extension)' == '.dll'">assembly</BootManifestResourceType>
<BootManifestResourceType Condition="'%(_BlazorCopyLocalPaths.Culture)' != '' AND '%(_BlazorCopyLocalPaths.Extension)' == '.dll'">satellite</BootManifestResourceType>
<BootManifestResourceName>%(_BlazorCopyLocalPaths.DestinationSubDirectory)%(FileName)%(Extension)</BootManifestResourceName>
<TargetOutputPath>$(_BlazorRuntimeBinOutputPath)%(_BlazorCopyLocalPaths.DestinationSubDirectory)%(FileName)%(Extension)</TargetOutputPath>
</_BlazorOutputWithTargetPath>

<_BlazorOutputWithTargetPath Include="@(_BlazorResolvedAssembly)">
<BootManifestResourceType Condition="'%(Extension)' == '.dll'">assembly</BootManifestResourceType>
<BootManifestResourceType Condition="'%(Extension)' == '.pdb'">pdb</BootManifestResourceType>
<BootManifestResourceName>%(FileName)%(Extension)</BootManifestResourceName>
<TargetOutputPath>$(_BlazorRuntimeBinOutputPath)%(FileName)%(Extension)</TargetOutputPath>
<_BlazorOutputWithTargetPath Include="@(ReferenceSatellitePaths)">
<Culture>$([System.String]::Copy('%(ReferenceSatellitePaths.DestinationSubDirectory)').Trim('\').Trim('/'))</Culture>
<BootManifestResourceType>satellite</BootManifestResourceType>
<BootManifestResourceName>%(ReferenceSatellitePaths.DestinationSubDirectory)%(FileName)%(Extension)</BootManifestResourceName>
<TargetOutputPath>$(_BlazorRuntimeBinOutputPath)%(ReferenceSatellitePaths.DestinationSubDirectory)%(FileName)%(Extension)</TargetOutputPath>
</_BlazorOutputWithTargetPath>
</ItemGroup>

<!--
We need to know at build time (not publish time) whether or not to include pdbs in the
blazor.boot.json file, so this is controlled by the BlazorEnableDebugging flag, whose
default value is determined by the build configuration.
-->
<ItemGroup Condition="'$(BlazorEnableDebugging)' != 'true'">
<_BlazorOutputWithTargetPath Remove="@(_BlazorOutputWithTargetPath)" Condition="'%(Extension)' == '.pdb'" />
</ItemGroup>

<!--
The following itemgroup attempts to extend the set to include satellite assemblies.
The mechanism behind this (or whether it's correct) is a bit unclear so
https://github.com/dotnet/aspnetcore/issues/18951 tracks the need for follow-up.
-->
<ItemGroup>
<!--
ReferenceCopyLocalPaths includes all files that are part of the build out with CopyLocalLockFileAssemblies on.
Remove assemblies that are inputs to calculating the assembly closure. Instead use the resolved outputs, since it is the minimal set.
-->
<_BlazorCopyLocalPaths Include="@(ReferenceCopyLocalPaths)" Condition="'%(Extension)' == '.dll'" />
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some of this code was duplicated in the file, possibly a bad merge.

<_BlazorCopyLocalPaths Remove="@(_BlazorManagedRuntimeAssemby)" Condition="'%(Extension)' == '.dll'" />

<_BlazorOutputWithTargetPath Include="@(_BlazorCopyLocalPaths)">
<_BlazorOutputWithTargetPath Include="@(_BlazorResolvedAssembly)">
<BootManifestResourceType Condition="'%(Extension)' == '.dll'">assembly</BootManifestResourceType>
<BootManifestResourceType Condition="'%(Extension)' == '.pdb'">pdb</BootManifestResourceType>
<BootManifestResourceType Condition="'%(_BlazorCopyLocalPaths.Extension)' == '.pdb'">pdb</BootManifestResourceType>
<BootManifestResourceName>%(_BlazorCopyLocalPaths.DestinationSubDirectory)%(FileName)%(Extension)</BootManifestResourceName>
<TargetOutputPath>$(_BlazorRuntimeBinOutputPath)%(_BlazorCopyLocalPaths.DestinationSubDirectory)%(FileName)%(Extension)</TargetOutputPath>
</_BlazorOutputWithTargetPath>
</ItemGroup>

<ItemGroup>
<_DotNetWasmRuntimeFile Include="$(ComponentsWebAssemblyRuntimePath)*" />
<_BlazorOutputWithTargetPath Include="@(_DotNetWasmRuntimeFile)">
<TargetOutputPath>$(_BlazorRuntimeWasmOutputPath)%(FileName)%(Extension)</TargetOutputPath>
<BootManifestResourceType>runtime</BootManifestResourceType>
<BootManifestResourceName>%(FileName)%(Extension)</BootManifestResourceName>
</_BlazorOutputWithTargetPath>

<_BlazorJSFile Include="$(_BlazorJSPath)" />
<_BlazorJSFile Include="$(_BlazorJSMapPath)" Condition="Exists('$(_BlazorJSMapPath)')" />
<_BlazorOutputWithTargetPath Include="@(_BlazorJSFile)">
<TargetOutputPath>$(_BaseBlazorRuntimeOutputPath)%(FileName)%(Extension)</TargetOutputPath>
</_BlazorOutputWithTargetPath>
</ItemGroup>

<!--
We need to know at build time (not publish time) whether or not to include pdbs in the
blazor.boot.json file, so this is controlled by the BlazorEnableDebugging flag, whose
default value is determined by the build configuration.
-->
<ItemGroup Condition="'$(BlazorEnableDebugging)' != 'true'">
<_BlazorOutputWithTargetPath Remove="@(_BlazorOutputWithTargetPath)" Condition="'%(Extension)' == '.pdb'" />
</ItemGroup>
</Target>

<!--
Expand Down Expand Up @@ -218,7 +213,7 @@
<Target
Name="_LinkBlazorApplication"
Inputs="$(ProjectAssetsFile);
@(_BlazorManagedRuntimeAssemby);
@(_BlazorManagedRuntimeAssembly);
@(BlazorLinkerDescriptor);
$(MSBuildAllProjects)"
Outputs="$(_BlazorLinkerOutputCache)">
Expand Down Expand Up @@ -277,7 +272,7 @@
Name="_ResolveBlazorRuntimeDependencies"
Inputs="$(ProjectAssetsFile);
@(IntermediateAssembly);
@(_BlazorManagedRuntimeAssemby)"
@(_BlazorManagedRuntimeAssembly)"
Outputs="$(_BlazorApplicationAssembliesCacheFile)">

<!--
Expand All @@ -287,7 +282,7 @@
-->
<ResolveBlazorRuntimeDependencies
EntryPoint="@(IntermediateAssembly)"
ApplicationDependencies="@(_BlazorManagedRuntimeAssemby)"
ApplicationDependencies="@(_BlazorManagedRuntimeAssembly)"
WebAssemblyBCLAssemblies="@(_WebAssemblyBCLAssembly)">

<Output TaskParameter="Dependencies" ItemName="_BlazorResolvedAssembly" />
Expand Down
Loading