diff --git a/src/libraries/sendtohelix-browser.targets b/src/libraries/sendtohelix-browser.targets
index 5e6d3bc50e63a..11dcb6fa04d05 100644
--- a/src/libraries/sendtohelix-browser.targets
+++ b/src/libraries/sendtohelix-browser.targets
@@ -139,6 +139,7 @@
$(RepositoryEngineeringDir)testing\scenarios\BuildWasmAppsJobsList.txt
<_XUnitTraitArg Condition="'$(TestUsingWorkloads)' == 'true'">-notrait category=no-workload
<_XUnitTraitArg Condition="'$(TestUsingWorkloads)' != 'true'">-trait category=no-workload
+ <_XUnitTraitArg Condition="'$(TestUsingFingerprinting)' == 'false'">$(_XUnitTraitArg) -trait category=no-fingerprinting
diff --git a/src/libraries/sendtohelix-wasm.targets b/src/libraries/sendtohelix-wasm.targets
index 3fce57cf9c4e1..23fb50e216f09 100644
--- a/src/libraries/sendtohelix-wasm.targets
+++ b/src/libraries/sendtohelix-wasm.targets
@@ -13,6 +13,7 @@
Workloads-
NoWorkload-
$(WorkItemPrefix)NoWebcil-
+ $(WorkItemPrefix)NoFingerprint-
$(WorkItemPrefix)ST-
$(WorkItemPrefix)MT-
@@ -49,7 +50,7 @@
-
+
$(_BuildWasmAppsPayloadArchive)
set "HELIX_XUNIT_ARGS=-class %(Identity)"
export "HELIX_XUNIT_ARGS=-class %(Identity)"
@@ -57,7 +58,7 @@
$(_workItemTimeout)
-
+
$(_BuildWasmAppsPayloadArchive)
$(HelixCommand)
$(_workItemTimeout)
diff --git a/src/libraries/sendtohelix.proj b/src/libraries/sendtohelix.proj
index 17aa91b1597bb..999d8ed9d61c7 100644
--- a/src/libraries/sendtohelix.proj
+++ b/src/libraries/sendtohelix.proj
@@ -87,17 +87,24 @@
<_TestUsingWorkloadsValues Include="true;false" />
<_TestUsingWebcilValues Include="true;false" Condition="'$(TargetOS)' == 'browser'" />
+ <_TestUsingFingerprintingValues Include="true;false" Condition="'$(TargetOS)' == 'browser'" />
<_TestUsingCrossProductValuesTemp Include="@(_TestUsingWorkloadsValues)">
%(_TestUsingWorkloadsValues.Identity)
- <_TestUsingCrossProductValues Include="@(_TestUsingCrossProductValuesTemp)">
+ <_TestUsingCrossProductValuesTemp2 Include="@(_TestUsingCrossProductValuesTemp)">
%(_TestUsingWebcilValues.Identity)
+
+ <_TestUsingCrossProductValues Include="@(_TestUsingCrossProductValuesTemp2)">
+ %(_TestUsingFingerprintingValues.Identity)
+
+ <_TestUsingCrossProductValues Remove="@(_TestUsingCrossProductValues)" Condition="'%(_TestUsingCrossProductValues.Workloads)' == 'false' and '%(_TestUsingCrossProductValues.Fingerprinting)' == 'false'" />
+
<_BuildWasmAppsProjectsToBuild Include="$(PerScenarioProjectFile)">
- $(_PropertiesToPass);Scenario=BuildWasmApps;TestArchiveRuntimeFile=$(TestArchiveRuntimeFile);TestUsingWorkloads=%(_TestUsingCrossProductValues.Workloads);TestUsingWebcil=%(_TestUsingCrossProductValues.Webcil)
+ $(_PropertiesToPass);Scenario=BuildWasmApps;TestArchiveRuntimeFile=$(TestArchiveRuntimeFile);TestUsingWorkloads=%(_TestUsingCrossProductValues.Workloads);TestUsingWebcil=%(_TestUsingCrossProductValues.Webcil);TestUsingFingerprinting=%(_TestUsingCrossProductValues.Fingerprinting)
%(_BuildWasmAppsProjectsToBuild.AdditionalProperties);NeedsToBuildWasmAppsOnHelix=$(NeedsToBuildWasmAppsOnHelix)
diff --git a/src/libraries/sendtohelixhelp.proj b/src/libraries/sendtohelixhelp.proj
index 81a3ac4d71097..1873cbb79e03f 100644
--- a/src/libraries/sendtohelixhelp.proj
+++ b/src/libraries/sendtohelixhelp.proj
@@ -157,6 +157,7 @@
+
@@ -346,7 +347,7 @@
+ Text="Scenario: $(Scenario), TestUsingWorkloads: $(TestUsingWorkloads), TestUsingWebcil: $(TestUsingWebcil), TestUsingFingerprinting: $(TestUsingFingerprinting)" />
diff --git a/src/mono/browser/.gitignore b/src/mono/browser/.gitignore
index 6177029d9f9e1..a7c533cfd01b3 100644
--- a/src/mono/browser/.gitignore
+++ b/src/mono/browser/.gitignore
@@ -2,4 +2,4 @@
.stamp-wasm-install-and-select*
emsdk
-runtime/dotnet.d.ts.sha256
+runtime/*.d.ts.sha256
diff --git a/src/mono/browser/debugger/BrowserDebugProxy/DebugStore.cs b/src/mono/browser/debugger/BrowserDebugProxy/DebugStore.cs
index dfd0f368561bd..c535f47fc7d61 100644
--- a/src/mono/browser/debugger/BrowserDebugProxy/DebugStore.cs
+++ b/src/mono/browser/debugger/BrowserDebugProxy/DebugStore.cs
@@ -1683,6 +1683,9 @@ public async IAsyncEnumerable Load(SessionId id, string[] loaded_fil
var asm_files = new List();
List steps = new List();
+ // Use System.Private.CoreLib to determine if we have a fingerprinted assemblies or not.
+ bool isFingerprinted = Path.GetFileNameWithoutExtension(loaded_files.FirstOrDefault(f => f.Contains("System.Private.CoreLib"))) != "System.Private.CoreLib";
+
if (!useDebuggerProtocol)
{
var pdb_files = new List();
@@ -1698,8 +1701,17 @@ public async IAsyncEnumerable Load(SessionId id, string[] loaded_fil
{
try
{
- string candidate_pdb = Path.ChangeExtension(url, "pdb");
- string pdb = pdb_files.FirstOrDefault(n => n == candidate_pdb);
+ string pdb;
+ if (isFingerprinted)
+ {
+ string noFingerprintPdbFileName = string.Concat(Path.GetFileNameWithoutExtension(Path.GetFileNameWithoutExtension(url)), ".pdb");
+ pdb = pdb_files.FirstOrDefault(n => string.Concat(Path.GetFileNameWithoutExtension(Path.GetFileNameWithoutExtension(n)), Path.GetExtension(n)) == noFingerprintPdbFileName);
+ }
+ else
+ {
+ string candidate_pdb = Path.ChangeExtension(url, "pdb");
+ pdb = pdb_files.FirstOrDefault(n => n == candidate_pdb);
+ }
steps.Add(
new DebugItem
@@ -1722,12 +1734,14 @@ public async IAsyncEnumerable Load(SessionId id, string[] loaded_fil
continue;
try
{
- string unescapedFileName = Uri.UnescapeDataString(file_name);
+ string unescapedFileName = Path.GetFileName(Uri.UnescapeDataString(file_name));
+ if (isFingerprinted)
+ unescapedFileName = string.Concat(Path.GetFileNameWithoutExtension(Path.GetFileNameWithoutExtension(unescapedFileName)), Path.GetExtension(unescapedFileName));
steps.Add(
new DebugItem
{
Url = file_name,
- DataTask = context.SdbAgent.GetDataFromAssemblyAndPdbAsync(Path.GetFileName(unescapedFileName), false, token)
+ DataTask = context.SdbAgent.GetDataFromAssemblyAndPdbAsync(unescapedFileName, false, token)
});
}
catch (Exception e)
diff --git a/src/mono/browser/runtime/diagnostics-mock.d.ts b/src/mono/browser/runtime/diagnostics-mock.d.ts
index 59686e8797399..d1f24c99761d4 100644
--- a/src/mono/browser/runtime/diagnostics-mock.d.ts
+++ b/src/mono/browser/runtime/diagnostics-mock.d.ts
@@ -71,4 +71,4 @@ interface MockEnvironment {
expectAdvertise: FilterPredicate;
}
-export { MockEnvironment, MockScriptConnection, PromiseAndController };
+export type { MockEnvironment, MockScriptConnection, PromiseAndController };
diff --git a/src/mono/browser/runtime/diagnostics-mock.d.ts.sha256 b/src/mono/browser/runtime/diagnostics-mock.d.ts.sha256
deleted file mode 100644
index 0cfcc26b86906..0000000000000
--- a/src/mono/browser/runtime/diagnostics-mock.d.ts.sha256
+++ /dev/null
@@ -1 +0,0 @@
-6d0ff454946223f77abe8e6c1e377489c33b2914da86120f6b2952b739ebec20
\ No newline at end of file
diff --git a/src/mono/browser/runtime/dotnet.d.ts b/src/mono/browser/runtime/dotnet.d.ts
index 94e31ff6a3e88..1357bc4a0b613 100644
--- a/src/mono/browser/runtime/dotnet.d.ts
+++ b/src/mono/browser/runtime/dotnet.d.ts
@@ -247,6 +247,9 @@ type ResourceExtensions = {
};
interface ResourceGroups {
hash?: string;
+ fingerprinting?: {
+ [name: string]: string;
+ };
coreAssembly?: ResourceList;
assembly?: ResourceList;
lazyAssembly?: ResourceList;
@@ -692,4 +695,4 @@ declare global {
}
declare const createDotnetRuntime: CreateDotnetRuntimeType;
-export { AssetBehaviors, AssetEntry, CreateDotnetRuntimeType, DotnetHostBuilder, DotnetModuleConfig, EmscriptenModule, GlobalizationMode, IMemoryView, ModuleAPI, MonoConfig, RuntimeAPI, createDotnetRuntime as default, dotnet, exit };
+export { type AssetBehaviors, type AssetEntry, type CreateDotnetRuntimeType, type DotnetHostBuilder, type DotnetModuleConfig, type EmscriptenModule, GlobalizationMode, type IMemoryView, type ModuleAPI, type MonoConfig, type RuntimeAPI, createDotnetRuntime as default, dotnet, exit };
diff --git a/src/mono/browser/runtime/lazyLoading.ts b/src/mono/browser/runtime/lazyLoading.ts
index f4da4521010a9..3d075b3005a8e 100644
--- a/src/mono/browser/runtime/lazyLoading.ts
+++ b/src/mono/browser/runtime/lazyLoading.ts
@@ -7,11 +7,23 @@ import { AssetEntry } from "./types";
export async function loadLazyAssembly (assemblyNameToLoad: string): Promise {
const resources = loaderHelpers.config.resources!;
+ const originalAssemblyName = assemblyNameToLoad;
const lazyAssemblies = resources.lazyAssembly;
if (!lazyAssemblies) {
throw new Error("No assemblies have been marked as lazy-loadable. Use the 'BlazorWebAssemblyLazyLoad' item group in your project file to enable lazy loading an assembly.");
}
+ if (loaderHelpers.config.resources!.fingerprinting) {
+ const map = loaderHelpers.config.resources!.fingerprinting;
+ for (const fingerprintedName in map) {
+ const nonFingerprintedName = map[fingerprintedName];
+ if (nonFingerprintedName == assemblyNameToLoad) {
+ assemblyNameToLoad = fingerprintedName;
+ break;
+ }
+ }
+ }
+
if (!lazyAssemblies[assemblyNameToLoad]) {
throw new Error(`${assemblyNameToLoad} must be marked with 'BlazorWebAssemblyLazyLoad' item group in your project file to allow lazy-loading.`);
}
@@ -26,8 +38,22 @@ export async function loadLazyAssembly (assemblyNameToLoad: string): Promise {
+ if (resources.fingerprinting && (asset.behavior == "assembly" || asset.behavior == "pdb" || asset.behavior == "resource")) {
+ asset.virtualPath = getNonFingerprintedAssetName(asset.name);
+ }
if (isCore) {
asset.isCore = true;
coreAssetsToLoad.push(asset);
@@ -418,7 +421,7 @@ export function prepareAssets () {
behavior: "icu",
loadRemote: true
});
- } else if (name === "segmentation-rules.json") {
+ } else if (name.startsWith("segmentation-rules") && name.endsWith(".json")) {
assetsToLoad.push({
name,
hash: resources.icu[name],
@@ -460,6 +463,15 @@ export function prepareAssets () {
config.assets = [...coreAssetsToLoad, ...assetsToLoad, ...modulesAssets];
}
+export function getNonFingerprintedAssetName (assetName: string) {
+ const fingerprinting = loaderHelpers.config.resources?.fingerprinting;
+ if (fingerprinting && fingerprinting[assetName]) {
+ return fingerprinting[assetName];
+ }
+
+ return assetName;
+}
+
export function prepareAssetsWorker () {
const config = loaderHelpers.config;
mono_assert(config.assets, "config.assets must be defined");
diff --git a/src/mono/browser/runtime/loader/icu.ts b/src/mono/browser/runtime/loader/icu.ts
index b9bf54eb31f10..d5ad0dd3b8bbd 100644
--- a/src/mono/browser/runtime/loader/icu.ts
+++ b/src/mono/browser/runtime/loader/icu.ts
@@ -5,6 +5,7 @@ import { mono_log_error } from "./logging";
import { GlobalizationMode, MonoConfig } from "../types";
import { ENVIRONMENT_IS_WEB, loaderHelpers } from "./globals";
import { mono_log_info, mono_log_debug } from "./logging";
+import { getNonFingerprintedAssetName } from "./assets";
export function init_globalization () {
loaderHelpers.preferredIcuAsset = getIcuResourceName(loaderHelpers.config);
@@ -51,6 +52,17 @@ export function getIcuResourceName (config: MonoConfig): string | null {
const culture = config.applicationCulture || (ENVIRONMENT_IS_WEB ? (globalThis.navigator && globalThis.navigator.languages && globalThis.navigator.languages[0]) : Intl.DateTimeFormat().resolvedOptions().locale);
const icuFiles = Object.keys(config.resources.icu);
+ const fileMapping: {
+ [k: string]: string
+ } = {};
+ for (let index = 0; index < icuFiles.length; index++) {
+ const icuFile = icuFiles[index];
+ if (config.resources.fingerprinting) {
+ fileMapping[getNonFingerprintedAssetName(icuFile)] = icuFile;
+ } else {
+ fileMapping[icuFile] = icuFile;
+ }
+ }
let icuFile = null;
if (config.globalizationMode === GlobalizationMode.Custom) {
@@ -65,8 +77,8 @@ export function getIcuResourceName (config: MonoConfig): string | null {
icuFile = getShardedIcuResourceName(culture);
}
- if (icuFile && icuFiles.includes(icuFile)) {
- return icuFile;
+ if (icuFile && fileMapping[icuFile]) {
+ return fileMapping[icuFile];
}
}
diff --git a/src/mono/browser/runtime/types/index.ts b/src/mono/browser/runtime/types/index.ts
index 5e8e8c9d18bb7..c3658624dff19 100644
--- a/src/mono/browser/runtime/types/index.ts
+++ b/src/mono/browser/runtime/types/index.ts
@@ -203,6 +203,7 @@ export type ResourceExtensions = { [extensionName: string]: ResourceList };
export interface ResourceGroups {
hash?: string;
+ fingerprinting?: { [name: string]: string },
coreAssembly?: ResourceList; // nullable only temporarily
assembly?: ResourceList; // nullable only temporarily
lazyAssembly?: ResourceList; // nullable only temporarily
diff --git a/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.props b/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.props
index 45d892d2fc467..5adc448e11f4b 100644
--- a/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.props
+++ b/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.props
@@ -35,6 +35,7 @@ Copyright (c) .NET Foundation. All rights reserved.
ComputeFilesToPublish;GetCurrentProjectPublishStaticWebAssetItems
$(StaticWebAssetsAdditionalPublishProperties);BuildProjectReferences=false;ResolveAssemblyReferencesFindRelatedSatellites=true
$(StaticWebAssetsAdditionalPublishPropertiesToRemove);NoBuild;RuntimeIdentifier
+ true
diff --git a/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.targets b/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.targets
index 02688abdf80c1..22ebca926a5ce 100644
--- a/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.targets
+++ b/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.targets
@@ -65,7 +65,6 @@ Copyright (c) .NET Foundation. All rights reserved.
true
true
- false
false
@@ -178,6 +177,7 @@ Copyright (c) .NET Foundation. All rights reserved.
<_TargetingNET80OrLater>false
<_TargetingNET80OrLater Condition="'$(TargetFrameworkIdentifier)' == '.NETCoreApp' and $([MSBuild]::VersionGreaterThanOrEquals('$(TargetFrameworkVersion)', '8.0'))">true
+ <_TargetingNET90OrLater Condition="'$(TargetFrameworkIdentifier)' == '.NETCoreApp' and $([MSBuild]::VersionGreaterThanOrEquals('$(TargetFrameworkVersion)', '9.0'))">true
<_BlazorEnableTimeZoneSupport>$(BlazorEnableTimeZoneSupport)
<_BlazorEnableTimeZoneSupport Condition="'$(_BlazorEnableTimeZoneSupport)' == ''">true
@@ -194,6 +194,11 @@ Copyright (c) .NET Foundation. All rights reserved.
<_BlazorWebAssemblyStartupMemoryCache>$(BlazorWebAssemblyStartupMemoryCache)
<_BlazorWebAssemblyJiterpreter>$(BlazorWebAssemblyJiterpreter)
<_BlazorWebAssemblyRuntimeOptions>$(BlazorWebAssemblyRuntimeOptions)
+ <_WasmFingerprintAssets>$(WasmFingerprintAssets)
+ <_WasmFingerprintAssets Condition="'$(_WasmFingerprintAssets)' == '' and '$(_TargetingNET90OrLater)' == 'true'">true
+ <_WasmFingerprintAssets Condition="'$(_WasmFingerprintAssets)' == ''">false
+ <_WasmFingerprintDotnetJs>$(WasmFingerprintDotnetJs)
+ <_WasmFingerprintDotnetJs Condition="'$(_WasmFingerprintDotnetJs)' == ''">false
$(OutputPath)$(PublishDirName)\
@@ -238,12 +243,12 @@ Copyright (c) .NET Foundation. All rights reserved.
InvariantGlobalization="$(_WasmInvariantGlobalization)"
HybridGlobalization="$(_IsHybridGlobalization)"
LoadFullICUData="$(_BlazorWebAssemblyLoadAllGlobalizationData)"
- DotNetJsVersion="$(_WasmRuntimePackVersion)"
CopySymbols="$(_WasmCopyOutputSymbolsToOutputDirectory)"
OutputPath="$(OutputPath)"
- FingerprintDotNetJs="$(WasmFingerprintDotnetJs)"
EnableThreads="$(_WasmEnableThreads)"
EmitSourceMap="$(_WasmEmitSourceMapBuild)"
+ FingerprintAssets="$(_WasmFingerprintAssets)"
+ FingerprintDotnetJs="$(_WasmFingerprintDotnetJs)"
>
@@ -260,6 +265,13 @@ Copyright (c) .NET Foundation. All rights reserved.
+
+ <_WasmFingerprintPatterns Include="WasmFiles" Pattern="*.wasm" Expression="#[.{fingerprint}]!" />
+ <_WasmFingerprintPatterns Include="DllFiles" Pattern="*.dll" Expression="#[.{fingerprint}]!" />
+ <_WasmFingerprintPatterns Include="DatFiles" Pattern="*.dat" Expression="#[.{fingerprint}]!" />
+ <_WasmFingerprintPatterns Include="Pdb" Pattern="*.pdb" Expression="#[.{fingerprint}]!" />
+
+
@@ -322,25 +336,34 @@ Copyright (c) .NET Foundation. All rights reserved.
Condition="'%(StaticWebAsset.AssetTraitName)' == 'JSModule' and '%(StaticWebAsset.AssetTraitValue)' == 'JSLibraryModule' and '%(AssetKind)' != 'Publish'" />
-
-
-
-
-
-
-
+
+
-
-
-
+
+
+
+
+
+
+
+ IsMultiThreaded="$(WasmEnableThreads)"
+ FingerprintAssets="$(_WasmFingerprintAssets)" />
@@ -401,14 +425,6 @@ Copyright (c) .NET Foundation. All rights reserved.
-
-
-
-
@@ -468,13 +484,13 @@ Copyright (c) .NET Foundation. All rights reserved.
LoadFullICUData="$(_BlazorWebAssemblyLoadAllGlobalizationData)"
CopySymbols="$(CopyOutputSymbolsToPublishDirectory)"
ExistingAssets="@(_WasmPublishPrefilteredAssets)"
- DotNetJsVersion="$(_WasmRuntimePackVersion)"
- FingerprintDotNetJs="$(WasmFingerprintDotnetJs)"
EnableThreads="$(_WasmEnableThreads)"
EmitSourceMap="$(_WasmEmitSourceMapPublish)"
IsWebCilEnabled="$(_WasmEnableWebcil)"
+ FingerprintAssets="$(_WasmFingerprintAssets)"
>
+
@@ -497,7 +513,7 @@ Copyright (c) .NET Foundation. All rights reserved.
RemoveMetadata="Integrity;Fingerprint" />
-
+
@@ -617,26 +633,28 @@ Copyright (c) .NET Foundation. All rights reserved.
<_WasmJsModuleCandidatesForPublish
Include="@(StaticWebAsset)"
Condition="'%(StaticWebAsset.AssetTraitName)' == 'JSModule' and '%(StaticWebAsset.AssetTraitValue)' == 'JSLibraryModule' and '%(AssetKind)' != 'Build'" />
-
-
- <_WasmPublishAsset Remove="@(_BlazorExtensionsCandidatesForPublish)" />
-
-
-
+
+
-
-
-
+
+
+
+ IsMultiThreaded="$(WasmEnableThreads)"
+ FingerprintAssets="$(_WasmFingerprintAssets)" />
diff --git a/src/mono/wasm/Wasm.Build.Tests/Blazor/BuildPublishTests.cs b/src/mono/wasm/Wasm.Build.Tests/Blazor/BuildPublishTests.cs
index 10f1ff6f47848..5877b0f6a06dd 100644
--- a/src/mono/wasm/Wasm.Build.Tests/Blazor/BuildPublishTests.cs
+++ b/src/mono/wasm/Wasm.Build.Tests/Blazor/BuildPublishTests.cs
@@ -166,8 +166,8 @@ void AssertResourcesDlls(string basePath)
{
foreach (string culture in cultures)
{
- string resourceAssemblyPath = Path.Combine(basePath, culture, $"{id}.resources{ProjectProviderBase.WasmAssemblyExtension}");
- Assert.True(File.Exists(resourceAssemblyPath), $"Expects to have a resource assembly at {resourceAssemblyPath}");
+ string? resourceAssemblyPath = Directory.EnumerateFiles(Path.Combine(basePath, culture), $"*{ProjectProviderBase.WasmAssemblyExtension}").SingleOrDefault(f => Path.GetFileNameWithoutExtension(f).StartsWith($"{id}.resources"));
+ Assert.True(resourceAssemblyPath != null && File.Exists(resourceAssemblyPath), $"Expects to have a resource assembly at {resourceAssemblyPath}");
}
}
}
diff --git a/src/mono/wasm/Wasm.Build.Tests/Blazor/MiscTests3.cs b/src/mono/wasm/Wasm.Build.Tests/Blazor/MiscTests3.cs
index 31c9f0ac05f02..4d82356b10379 100644
--- a/src/mono/wasm/Wasm.Build.Tests/Blazor/MiscTests3.cs
+++ b/src/mono/wasm/Wasm.Build.Tests/Blazor/MiscTests3.cs
@@ -114,10 +114,10 @@ public void BugRegression_60479_WithRazorClassLib()
.ExecuteWithCapturedOutput("new razorclasslib")
.EnsureSuccessful();
- string razorClassLibraryFileName = $"RazorClassLibrary{ProjectProviderBase.WasmAssemblyExtension}";
+ string razorClassLibraryFileNameWithoutExtension = "RazorClassLibrary";
AddItemsPropertiesToProject(wasmProjectFile, extraItems: @$"
-
+
");
_projectDir = wasmProjectDir;
@@ -140,7 +140,7 @@ public void BugRegression_60479_WithRazorClassLib()
throw new XunitException($"Could not find resources.lazyAssembly object in {bootJson}");
}
- Assert.Contains(razorClassLibraryFileName, lazyVal.EnumerateObject().Select(jp => jp.Name));
+ Assert.True(lazyVal.EnumerateObject().Select(jp => jp.Name).FirstOrDefault(f => f.StartsWith(razorClassLibraryFileNameWithoutExtension)) != null);
}
private void BlazorAddRazorButton(string buttonText, string customCode, string methodName = "test", string razorPage = "Pages/Counter.razor")
diff --git a/src/mono/wasm/Wasm.Build.Tests/Common/BuildEnvironment.cs b/src/mono/wasm/Wasm.Build.Tests/Common/BuildEnvironment.cs
index 6142128dd500d..0b71e4d663015 100644
--- a/src/mono/wasm/Wasm.Build.Tests/Common/BuildEnvironment.cs
+++ b/src/mono/wasm/Wasm.Build.Tests/Common/BuildEnvironment.cs
@@ -134,6 +134,12 @@ public BuildEnvironment()
EnvVars["WasmEnableWebCil"] = "false";
}
+ if (!EnvironmentVariables.UseFingerprinting)
+ {
+ // Default is 'true'
+ EnvVars["WasmFingerprintAssets"] = "false";
+ }
+
DotNet = Path.Combine(sdkForWorkloadPath!, "dotnet");
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
DotNet += ".exe";
diff --git a/src/mono/wasm/Wasm.Build.Tests/Common/DotNetFileName.cs b/src/mono/wasm/Wasm.Build.Tests/Common/DotNetFileName.cs
index 42fd8873e78f5..0c1739d55833d 100644
--- a/src/mono/wasm/Wasm.Build.Tests/Common/DotNetFileName.cs
+++ b/src/mono/wasm/Wasm.Build.Tests/Common/DotNetFileName.cs
@@ -8,7 +8,6 @@ namespace Wasm.Build.Tests;
public sealed record DotNetFileName
(
string ExpectedFilename,
- string? Version,
string? Hash,
string ActualPath
);
diff --git a/src/mono/wasm/Wasm.Build.Tests/Common/EnvironmentVariables.cs b/src/mono/wasm/Wasm.Build.Tests/Common/EnvironmentVariables.cs
index e2cf9b3dbf8b4..d928fc1e1b406 100644
--- a/src/mono/wasm/Wasm.Build.Tests/Common/EnvironmentVariables.cs
+++ b/src/mono/wasm/Wasm.Build.Tests/Common/EnvironmentVariables.cs
@@ -22,6 +22,7 @@ internal static class EnvironmentVariables
internal static readonly bool IsRunningOnCI = Environment.GetEnvironmentVariable("IS_RUNNING_ON_CI") is "true";
internal static readonly bool ShowBuildOutput = IsRunningOnCI || Environment.GetEnvironmentVariable("SHOW_BUILD_OUTPUT") is not null;
internal static readonly bool UseWebcil = Environment.GetEnvironmentVariable("USE_WEBCIL_FOR_TESTS") is "true";
+ internal static readonly bool UseFingerprinting = Environment.GetEnvironmentVariable("USE_FINGERPRINTING_FOR_TESTS") is "true";
internal static readonly string? SdkDirName = Environment.GetEnvironmentVariable("SDK_DIR_NAME");
internal static readonly string? WasiSdkPath = Environment.GetEnvironmentVariable("WASI_SDK_PATH");
internal static readonly bool WorkloadsTestPreviousVersions = Environment.GetEnvironmentVariable("WORKLOADS_TEST_PREVIOUS_VERSIONS") is "true";
diff --git a/src/mono/wasm/Wasm.Build.Tests/ProjectProviderBase.cs b/src/mono/wasm/Wasm.Build.Tests/ProjectProviderBase.cs
index 6be096cb67ea5..69b6d07ff2d6c 100644
--- a/src/mono/wasm/Wasm.Build.Tests/ProjectProviderBase.cs
+++ b/src/mono/wasm/Wasm.Build.Tests/ProjectProviderBase.cs
@@ -10,6 +10,7 @@
using System.Linq;
using System.Runtime.Serialization.Json;
using System.Text;
+using System.Text.Json;
using System.Text.RegularExpressions;
using Microsoft.NET.Sdk.WebAssembly;
using Xunit;
@@ -22,7 +23,7 @@ namespace Wasm.Build.Tests;
public abstract class ProjectProviderBase(ITestOutputHelper _testOutput, string? _projectDir)
{
public static string WasmAssemblyExtension = BuildTestBase.s_buildEnv.UseWebcil ? ".wasm" : ".dll";
- protected const string s_dotnetVersionHashRegex = @"\.(?[0-9]+\.[0-9]+\.[a-zA-Z0-9\.-]+)\.(?[a-zA-Z0-9]+)\.";
+ protected const string s_dotnetVersionHashRegex = @"\.(?[a-zA-Z0-9]+)\.";
private const string s_runtimePackPathPattern = "\\*\\* MicrosoftNetCoreAppRuntimePackDir : '([^ ']*)'";
private static Regex s_runtimePackPathRegex = new Regex(s_runtimePackPathPattern);
@@ -37,6 +38,10 @@ public abstract class ProjectProviderBase(ITestOutputHelper _testOutput, string?
protected BuildEnvironment _buildEnv = BuildTestBase.s_buildEnv;
public string BundleDirName { get; set; } = "wwwroot";
+ public bool IsFingerprintingSupported { get; protected set; }
+
+ public bool IsFingerprintingEnabled => IsFingerprintingSupported && EnvironmentVariables.UseFingerprinting;
+
// Returns the actual files on disk
public IReadOnlyDictionary AssertBasicBundle(AssertBundleOptionsBase assertOptions)
{
@@ -45,17 +50,17 @@ public IReadOnlyDictionary AssertBasicBundle(AssertBundl
TestUtils.AssertFilesExist(assertOptions.BinFrameworkDir,
new[] { "System.Private.CoreLib.dll" },
- expectToExist: !BuildTestBase.UseWebcil);
+ expectToExist: IsFingerprintingEnabled ? false : !BuildTestBase.UseWebcil);
TestUtils.AssertFilesExist(assertOptions.BinFrameworkDir,
new[] { "System.Private.CoreLib.wasm" },
- expectToExist: BuildTestBase.UseWebcil);
+ expectToExist: IsFingerprintingEnabled ? false : BuildTestBase.UseWebcil);
- AssertBootJson(assertOptions);
+ var bootJson = AssertBootJson(assertOptions);
// icu
if (assertOptions.AssertIcuAssets)
{
- AssertIcuAssets(assertOptions);
+ AssertIcuAssets(assertOptions, bootJson);
}
else
{
@@ -124,8 +129,7 @@ public IReadOnlyDictionary FindAndAssertDotnetFiles(
return true;
actual[expectedFilename] = new(ExpectedFilename: expectedFilename,
- Version: match.Groups[1].Value,
- Hash: match.Groups[2].Value,
+ Hash: match.Groups[1].Value,
ActualPath: actualFile);
}
else
@@ -134,7 +138,6 @@ public IReadOnlyDictionary FindAndAssertDotnetFiles(
return true;
actual[expectedFilename] = new(ExpectedFilename: expectedFilename,
- Version: null,
Hash: null,
ActualPath: actualFile);
}
@@ -180,15 +183,11 @@ private void AssertDotNetFilesSet(
expectFingerprintOnDotnetJs: expectFingerprintOnDotnetJs,
expectFingerprintForThisFile: expectFingerprint))
{
- if (string.IsNullOrEmpty(actual[expectedFilename].Version))
- throw new XunitException($"Expected version in filename: {actual[expectedFilename].ActualPath}");
if (string.IsNullOrEmpty(actual[expectedFilename].Hash))
throw new XunitException($"Expected hash in filename: {actual[expectedFilename].ActualPath}");
}
else
{
- if (!string.IsNullOrEmpty(actual[expectedFilename].Version))
- throw new XunitException($"Expected no version in filename: {actual[expectedFilename].ActualPath}");
if (!string.IsNullOrEmpty(actual[expectedFilename].Hash))
throw new XunitException($"Expected no hash in filename: {actual[expectedFilename].ActualPath}");
}
@@ -322,8 +321,8 @@ public static string FindSubDirIgnoringCase(string parentDir, string dirName)
return dict;
}
- public static bool ShouldCheckFingerprint(string expectedFilename, bool expectFingerprintOnDotnetJs, bool expectFingerprintForThisFile) =>
- (expectedFilename == "dotnet.js" && expectFingerprintOnDotnetJs) || expectFingerprintForThisFile;
+ public bool ShouldCheckFingerprint(string expectedFilename, bool expectFingerprintOnDotnetJs, bool expectFingerprintForThisFile)
+ => IsFingerprintingEnabled && ((expectedFilename == "dotnet.js" && expectFingerprintOnDotnetJs) || expectFingerprintForThisFile);
public static void AssertRuntimePackPath(string buildOutput, string targetFramework, RuntimeVariant runtimeType = RuntimeVariant.SingleThreaded)
@@ -351,7 +350,7 @@ public static void AssertDotNetJsSymbols(AssertBundleOptionsBase assertOptions)
}
}
- public void AssertIcuAssets(AssertBundleOptionsBase assertOptions)
+ public void AssertIcuAssets(AssertBundleOptionsBase assertOptions, BootJsonData bootJson)
{
List expected = new();
switch (assertOptions.GlobalizationMode)
@@ -370,7 +369,7 @@ public void AssertIcuAssets(AssertBundleOptionsBase assertOptions)
throw new ArgumentException("WasmBuildTest is invalid, value for predefinedIcudt is required when GlobalizationMode=PredefinedIcu.");
// predefined ICU name can be identical with the icu files from runtime pack
- expected.Add(assertOptions.PredefinedIcudt);
+ expected.Add(Path.GetFileName(assertOptions.PredefinedIcudt));
break;
case GlobalizationMode.Sharded:
// icu shard chosen based on the locale
@@ -384,7 +383,23 @@ public void AssertIcuAssets(AssertBundleOptionsBase assertOptions)
IEnumerable actual = Directory.EnumerateFiles(assertOptions.BinFrameworkDir, "icudt*dat");
if (assertOptions.GlobalizationMode == GlobalizationMode.Hybrid)
- actual = actual.Union(Directory.EnumerateFiles(assertOptions.BinFrameworkDir, "segmentation-rules.json"));
+ actual = actual.Union(Directory.EnumerateFiles(assertOptions.BinFrameworkDir, "segmentation-rules*json"));
+
+ if (IsFingerprintingEnabled)
+ {
+ var expectedFingerprinted = new List(expected.Count);
+ foreach (var expectedItem in expected)
+ {
+ var expectedFingerprintedItem = bootJson.resources.fingerprinting.Where(kv => kv.Value == expectedItem).SingleOrDefault().Key;
+ if (string.IsNullOrEmpty(expectedFingerprintedItem))
+ throw new XunitException($"Could not find ICU asset {expectedItem} in fingerprinting in boot config");
+
+ expectedFingerprinted.Add(expectedFingerprintedItem);
+ }
+
+ expected = expectedFingerprinted;
+ }
+
AssertFileNames(expected, actual);
if (assertOptions.GlobalizationMode is GlobalizationMode.PredefinedIcu)
{
@@ -396,7 +411,7 @@ public void AssertIcuAssets(AssertBundleOptionsBase assertOptions)
}
}
- public void AssertBootJson(AssertBundleOptionsBase options)
+ public BootJsonData AssertBootJson(AssertBundleOptionsBase options)
{
EnsureProjectDirIsSet();
// string binFrameworkDir = FindBinFrameworkDir(options.Config, options.IsPublish, options.TargetFramework);
@@ -406,23 +421,29 @@ public void AssertBootJson(AssertBundleOptionsBase options)
BootJsonData bootJson = ParseBootData(bootJsonPath);
string spcExpectedFilename = $"System.Private.CoreLib{WasmAssemblyExtension}";
+
+ if (IsFingerprintingEnabled)
+ {
+ spcExpectedFilename = bootJson.resources.fingerprinting.Where(kv => kv.Value == spcExpectedFilename).SingleOrDefault().Key;
+ if (string.IsNullOrEmpty(spcExpectedFilename))
+ throw new XunitException($"Could not find an assembly System.Private.CoreLib in fingerprinting in {bootJsonPath}");
+ }
+
string? spcActualFilename = bootJson.resources.coreAssembly.Keys
- .Where(a => Path.GetFileNameWithoutExtension(a) == "System.Private.CoreLib")
+ .Where(a => a == spcExpectedFilename)
.SingleOrDefault();
if (spcActualFilename is null)
throw new XunitException($"Could not find an assembly named System.Private.CoreLib.* in {bootJsonPath}");
- if (spcExpectedFilename != spcActualFilename)
- throw new XunitException($"Expected to find {spcExpectedFilename} but found {spcActualFilename} in {bootJsonPath}");
var bootJsonEntries = bootJson.resources.jsModuleNative.Keys
+ .Union(bootJson.resources.wasmNative.Keys)
.Union(bootJson.resources.jsModuleRuntime.Keys)
.Union(bootJson.resources.jsModuleWorker?.Keys ?? Enumerable.Empty())
.Union(bootJson.resources.jsModuleGlobalization?.Keys ?? Enumerable.Empty())
.Union(bootJson.resources.wasmSymbols?.Keys ?? Enumerable.Empty())
- .Union(bootJson.resources.wasmNative.Keys)
.ToArray();
- var expectedEntries = new SortedDictionary>();
+ var expectedEntries = new SortedDictionary>();
IReadOnlySet expected = GetDotNetFilesExpectedSet(options);
var knownSet = GetAllKnownDotnetFilesToFingerprintMap(options);
@@ -442,28 +463,34 @@ public void AssertBootJson(AssertBundleOptionsBase options)
expectFingerprintOnDotnetJs: options.ExpectFingerprintOnDotnetJs,
expectFingerprintForThisFile: expectFingerprint))
{
- Assert.Matches($"{prefix}{s_dotnetVersionHashRegex}{extension}", item);
+ return Regex.Match(item, $"{prefix}{s_dotnetVersionHashRegex}{extension}").Success;
}
else
{
- Assert.Equal(expectedFilename, item);
+ return expectedFilename == item;
}
-
- string absolutePath = Path.Combine(binFrameworkDir, item);
- Assert.True(File.Exists(absolutePath), $"Expected to find '{absolutePath}'");
};
}
// FIXME: maybe use custom code so the details can show up in the log
- bootJsonEntries = bootJsonEntries.Order().ToArray();
+ bootJsonEntries = bootJsonEntries.ToArray();
if (bootJsonEntries.Length != expectedEntries.Count)
{
throw new XunitException($"In {bootJsonPath}{Environment.NewLine}" +
$" Expected: {string.Join(", ", expectedEntries.Keys.ToArray())}{Environment.NewLine}" +
$" Actual : {string.Join(", ", bootJsonEntries)}");
+ }
+ var expectedEntriesToCheck = expectedEntries.Values.ToList();
+ foreach (var bootJsonEntry in bootJsonEntries)
+ {
+ var matcher = expectedEntriesToCheck.FirstOrDefault(c => c(bootJsonEntry));
+ if (matcher == null)
+ throw new XunitException($"Unexpected entry in boot json '{bootJsonEntry}'. Expected files {String.Join(", ", expectedEntries.Keys)}");
+ expectedEntriesToCheck.Remove(matcher);
}
- Assert.Collection(bootJsonEntries.Order(), expectedEntries.Values.ToArray());
+
+ return bootJson;
}
public static BootJsonData ParseBootData(string bootJsonPath)
diff --git a/src/mono/wasm/Wasm.Build.Tests/Templates/WasmTemplateTests.cs b/src/mono/wasm/Wasm.Build.Tests/Templates/WasmTemplateTests.cs
index 3142bed49a35f..b32b95debd879 100644
--- a/src/mono/wasm/Wasm.Build.Tests/Templates/WasmTemplateTests.cs
+++ b/src/mono/wasm/Wasm.Build.Tests/Templates/WasmTemplateTests.cs
@@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.IO;
+using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xunit;
@@ -107,7 +108,7 @@ private void UpdateMainJsEnvironmentVariables(params (string key, string value)[
File.WriteAllText(mainJsPath, mainJsContent);
}
- [Theory]
+ [Theory, TestCategory("no-fingerprinting")]
[InlineData("Debug")]
[InlineData("Release")]
public void BrowserBuildThenPublish(string config)
@@ -294,7 +295,7 @@ void AddTestData(bool forConsole, bool runOutsideProjectDirectory)
return data;
}
- [Theory]
+ [Theory, TestCategory("no-fingerprinting")]
[MemberData(nameof(TestDataForAppBundleDir))]
public async Task RunWithDifferentAppBundleLocations(bool forConsole, bool runOutsideProjectDirectory, string extraProperties)
=> await (forConsole
@@ -598,11 +599,12 @@ internal static void TestWasmStripILAfterAOTOutput(string objBuildDir, string fr
Assert.False(Directory.Exists(strippedAssemblyDir), $"Expected {strippedAssemblyDir} to not exist");
string assemblyToExamine = "System.Private.CoreLib.dll";
+ string assemblyToExamineWithoutExtension = Path.GetFileNameWithoutExtension(assemblyToExamine);
string originalAssembly = Path.Combine(objBuildDir, origAssemblyDir, assemblyToExamine);
string strippedAssembly = Path.Combine(objBuildDir, strippedAssemblyDir, assemblyToExamine);
- string bundledAssembly = Path.Combine(frameworkDir, Path.ChangeExtension(assemblyToExamine, ProjectProviderBase.WasmAssemblyExtension));
+ string? bundledAssembly = Directory.EnumerateFiles(frameworkDir, $"*{ProjectProviderBase.WasmAssemblyExtension}").FirstOrDefault(f => Path.GetFileNameWithoutExtension(f).StartsWith(assemblyToExamineWithoutExtension));
Assert.True(File.Exists(originalAssembly), $"Expected {nameof(originalAssembly)} {originalAssembly} to exist");
- Assert.True(File.Exists(bundledAssembly), $"Expected {nameof(bundledAssembly)} {bundledAssembly} to exist");
+ Assert.True(bundledAssembly != null && File.Exists(bundledAssembly), $"Expected {nameof(bundledAssembly)} {bundledAssembly} to exist");
if (expectILStripping)
Assert.True(File.Exists(strippedAssembly), $"Expected {nameof(strippedAssembly)} {strippedAssembly} to exist");
else
@@ -658,8 +660,8 @@ public void PublishPdb(bool copyOutputSymbolsToPublishDirectory)
void AssertFile(string suffix)
{
- var fileName = $"{id}{suffix}";
- Assert.True(copyOutputSymbolsToPublishDirectory == File.Exists(Path.Combine(publishFrameworkPath, fileName)), $"The {fileName} file {(copyOutputSymbolsToPublishDirectory ? "should" : "shouldn't")} exist in publish folder");
+ var fileName = Directory.EnumerateFiles(publishFrameworkPath, $"*{suffix}").FirstOrDefault(f => Path.GetFileNameWithoutExtension(f).StartsWith(id));
+ Assert.True(copyOutputSymbolsToPublishDirectory == (fileName != null && File.Exists(fileName)), $"The {fileName} file {(copyOutputSymbolsToPublishDirectory ? "should" : "shouldn't")} exist in publish folder");
}
}
}
diff --git a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/LazyLoadingTests.cs b/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/LazyLoadingTests.cs
index 038951e1822e6..6a167ec2de10a 100644
--- a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/LazyLoadingTests.cs
+++ b/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/LazyLoadingTests.cs
@@ -20,14 +20,16 @@ public LazyLoadingTests(ITestOutputHelper output, SharedBuildPerTestClassFixture
{
}
- [Fact]
+ [Fact, TestCategory("no-fingerprinting")]
public async Task LoadLazyAssemblyBeforeItIsNeeded()
{
CopyTestAsset("WasmBasicTestApp", "LazyLoadingTests", "App");
- PublishProject("Debug");
+ BuildProject("Debug");
+
+ var result = await RunSdkStyleAppForBuild(new(Configuration: "Debug", TestScenario: "LazyLoadingTest"));
- var result = await RunSdkStyleAppForPublish(new(Configuration: "Debug", TestScenario: "LazyLoadingTest"));
Assert.True(result.TestOutput.Any(m => m.Contains("FirstName")), "The lazy loading test didn't emit expected message with JSON");
+ Assert.True(result.ConsoleOutput.Any(m => m.Contains("Attempting to download") && m.Contains("_framework/Json.") && m.Contains(".pdb")), "The lazy loading test didn't load PDB");
}
[Fact]
diff --git a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/SatelliteLoadingTests.cs b/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/SatelliteLoadingTests.cs
index 517f34255f996..5e15cc53841ad 100644
--- a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/SatelliteLoadingTests.cs
+++ b/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/SatelliteLoadingTests.cs
@@ -23,7 +23,7 @@ public SatelliteLoadingTests(ITestOutputHelper output, SharedBuildPerTestClassFi
{
}
- [Fact]
+ [Fact, TestCategory("no-fingerprinting")]
public async Task LoadSatelliteAssembly()
{
CopyTestAsset("WasmBasicTestApp", "SatelliteLoadingTests", "App");
diff --git a/src/mono/wasm/Wasm.Build.Tests/Wasm.Build.Tests.csproj b/src/mono/wasm/Wasm.Build.Tests/Wasm.Build.Tests.csproj
index 62601dd54d85b..cd39a7102485d 100644
--- a/src/mono/wasm/Wasm.Build.Tests/Wasm.Build.Tests.csproj
+++ b/src/mono/wasm/Wasm.Build.Tests/Wasm.Build.Tests.csproj
@@ -94,6 +94,7 @@
<_XUnitTraitArg Condition="'$(TestUsingWorkloads)' == 'true'">-notrait category=no-workload
<_XUnitTraitArg Condition="'$(TestUsingWorkloads)' != 'true'">-trait category=no-workload
+ <_XUnitTraitArg Condition="'$(TestUsingFingerprinting)' == 'false'">-trait category=no-fingerprinting
@@ -112,6 +113,9 @@
+
+
+
diff --git a/src/mono/wasm/Wasm.Build.Tests/WasmSdkBasedProjectProvider.cs b/src/mono/wasm/Wasm.Build.Tests/WasmSdkBasedProjectProvider.cs
index 77ccc61fe5ac4..bd35644b0e8d0 100644
--- a/src/mono/wasm/Wasm.Build.Tests/WasmSdkBasedProjectProvider.cs
+++ b/src/mono/wasm/Wasm.Build.Tests/WasmSdkBasedProjectProvider.cs
@@ -17,7 +17,9 @@ public class WasmSdkBasedProjectProvider : ProjectProviderBase
{
public WasmSdkBasedProjectProvider(ITestOutputHelper _testOutput, string? _projectDir = null)
: base(_testOutput, _projectDir)
- {}
+ {
+ IsFingerprintingSupported = true;
+ }
protected override IReadOnlyDictionary GetAllKnownDotnetFilesToFingerprintMap(AssertBundleOptionsBase assertOptions)
=> new SortedDictionary()
@@ -27,7 +29,7 @@ protected override IReadOnlyDictionary GetAllKnownDotnetFilesToFin
{ "dotnet.native.js", true },
{ "dotnet.native.js.symbols", false },
{ "dotnet.globalization.js", true },
- { "dotnet.native.wasm", false },
+ { "dotnet.native.wasm", true },
{ "dotnet.native.worker.mjs", true },
{ "dotnet.runtime.js", true },
{ "dotnet.runtime.js.map", false },
diff --git a/src/mono/wasm/Wasm.Build.Tests/data/RunScriptTemplate.cmd b/src/mono/wasm/Wasm.Build.Tests/data/RunScriptTemplate.cmd
index e088aa4861df4..fc66d9e745a06 100644
--- a/src/mono/wasm/Wasm.Build.Tests/data/RunScriptTemplate.cmd
+++ b/src/mono/wasm/Wasm.Build.Tests/data/RunScriptTemplate.cmd
@@ -56,6 +56,11 @@ if [%TEST_USING_WEBCIL%] == [false] (
) else (
set USE_WEBCIL_FOR_TESTS=true
)
+if [%TEST_USING_FINGERPRINTING%] == [false] (
+ set USE_FINGERPRINTING_FOR_TESTS=false
+) else (
+ set USE_FINGERPRINTING_FOR_TESTS=true
+)
if [%HELIX_CORRELATION_PAYLOAD%] NEQ [] (
robocopy /mt /np /nfl /NDL /nc /e %BASE_DIR%\%SDK_DIR_NAME% %EXECUTION_DIR%\%SDK_DIR_NAME%
diff --git a/src/mono/wasm/Wasm.Build.Tests/data/RunScriptTemplate.sh b/src/mono/wasm/Wasm.Build.Tests/data/RunScriptTemplate.sh
index 2c23e8a4185f6..bab35b29d534a 100644
--- a/src/mono/wasm/Wasm.Build.Tests/data/RunScriptTemplate.sh
+++ b/src/mono/wasm/Wasm.Build.Tests/data/RunScriptTemplate.sh
@@ -39,6 +39,12 @@ function set_env_vars()
export USE_WEBCIL_FOR_TESTS=true
fi
+ if [ "x$TEST_USING_FINGERPRINTING" = "xfalse" ]; then
+ export USE_FINGERPRINTING_FOR_TESTS=false
+ else
+ export USE_FINGERPRINTING_FOR_TESTS=true
+ fi
+
local _SDK_DIR=
if [[ -n "$HELIX_WORKITEM_UPLOAD_ROOT" ]]; then
cp -r $BASE_DIR/$SDK_DIR_NAME $EXECUTION_DIR
diff --git a/src/mono/wasm/sln/WasmBuild.sln b/src/mono/wasm/sln/WasmBuild.sln
index b84f2d7ba8c84..48bbfe555ab42 100755
--- a/src/mono/wasm/sln/WasmBuild.sln
+++ b/src/mono/wasm/sln/WasmBuild.sln
@@ -29,6 +29,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Wasi.Build.Tests", "..\..\w
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WasmTestRunner", "..\..\..\libraries\Common\tests\WasmTestRunner\WasmTestRunner.csproj", "{2BBE4AA8-5424-44AB-933C-66554B688872}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkloadBuildTasks", "..\..\..\tasks\WorkloadBuildTasks\WorkloadBuildTasks.csproj", "{680C730D-C257-4F65-9CD9-6B9A161844B0}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -87,6 +89,10 @@ Global
{2BBE4AA8-5424-44AB-933C-66554B688872}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2BBE4AA8-5424-44AB-933C-66554B688872}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2BBE4AA8-5424-44AB-933C-66554B688872}.Release|Any CPU.Build.0 = Release|Any CPU
+ {680C730D-C257-4F65-9CD9-6B9A161844B0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {680C730D-C257-4F65-9CD9-6B9A161844B0}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {680C730D-C257-4F65-9CD9-6B9A161844B0}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {680C730D-C257-4F65-9CD9-6B9A161844B0}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/src/mono/wasm/testassets/WasmBasicTestApp/App/wwwroot/main.js b/src/mono/wasm/testassets/WasmBasicTestApp/App/wwwroot/main.js
index 74bfbb3fe5fd2..85fd83270b273 100644
--- a/src/mono/wasm/testassets/WasmBasicTestApp/App/wwwroot/main.js
+++ b/src/mono/wasm/testassets/WasmBasicTestApp/App/wwwroot/main.js
@@ -31,6 +31,9 @@ switch (testCase) {
case "AppSettingsTest":
dotnet.withApplicationEnvironment(params.get("applicationEnvironment"));
break;
+ case "LazyLoadingTest":
+ dotnet.withDiagnosticTracing(true);
+ break;
case "DownloadResourceProgressTest":
if (params.get("failAssemblyDownload") === "true") {
let assemblyCounter = 0;
@@ -124,7 +127,7 @@ switch (testCase) {
const { setModuleImports, getAssemblyExports, getConfig, INTERNAL } = await dotnet.create();
const config = getConfig();
const exports = await getAssemblyExports(config.mainAssemblyName);
-const assemblyExtension = config.resources.coreAssembly['System.Private.CoreLib.wasm'] !== undefined ? ".wasm" : ".dll";
+const assemblyExtension = Object.keys(config.resources.coreAssembly)[0].endsWith('.wasm') ? ".wasm" : ".dll";
// Run the test case
try {
diff --git a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/AssetsComputingHelper.cs b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/AssetsComputingHelper.cs
index cf78cbbed1f05..6885bf62343c7 100644
--- a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/AssetsComputingHelper.cs
+++ b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/AssetsComputingHelper.cs
@@ -93,20 +93,49 @@ private static bool IsFromMonoPackage(ITaskItem candidate)
return monoPackageIds.Contains(packageId, StringComparer.Ordinal);
}
- public static string GetCandidateRelativePath(ITaskItem candidate)
+ public static string GetCandidateRelativePath(ITaskItem candidate, bool fingerprintAssets, bool fingerprintDotNetJs)
{
+ const string optionalFingerprint = "#[.{fingerprint}]?";
+ const string requiredFingerprint = "#[.{fingerprint}]!";
+
+ string fileName = candidate.GetMetadata("FileName");
+ string extension = candidate.GetMetadata("Extension");
+ string subPath = string.Empty;
+
var destinationSubPath = candidate.GetMetadata("DestinationSubPath");
if (!string.IsNullOrEmpty(destinationSubPath))
- return $"_framework/{destinationSubPath}";
+ {
+ fileName = Path.GetFileNameWithoutExtension(destinationSubPath);
+ extension = Path.GetExtension(destinationSubPath);
+ subPath = destinationSubPath.Substring(fileName.Length + extension.Length);
+ }
+
+ string relativePath;
+ if (fingerprintAssets)
+ {
+ relativePath = (fileName, extension) switch
+ {
+ ("dotnet", ".js") => string.Concat(fileName, fingerprintDotNetJs ? requiredFingerprint : optionalFingerprint, extension),
+ ("dotnet.runtime", ".js") => string.Concat(fileName, requiredFingerprint, extension),
+ ("dotnet.native", ".js") => string.Concat(fileName, requiredFingerprint, extension),
+ ("dotnet.native.worker", ".mjs") => string.Concat(fileName, requiredFingerprint, extension),
+ ("dotnet.globalization", ".js") => string.Concat(fileName, requiredFingerprint, extension),
+ ("segmentation-rules", ".json") => string.Concat(fileName, requiredFingerprint, extension),
+ _ => string.Concat(fileName, extension)
+ };
+ }
+ else
+ {
+ relativePath = string.Concat(fileName, extension);
+ }
- var relativePath = candidate.GetMetadata("FileName") + candidate.GetMetadata("Extension");
- return $"_framework/{relativePath}";
+ return $"_framework/{subPath}{relativePath}";
}
- public static ITaskItem GetCustomIcuAsset(ITaskItem candidate)
+ public static ITaskItem GetCustomIcuAsset(ITaskItem candidate, bool fingerprintAssets)
{
var customIcuCandidate = new TaskItem(candidate);
- var relativePath = GetCandidateRelativePath(customIcuCandidate);
+ var relativePath = GetCandidateRelativePath(customIcuCandidate, fingerprintAssets, false);
customIcuCandidate.SetMetadata("RelativePath", relativePath);
customIcuCandidate.SetMetadata("AssetTraitName", "BlazorWebAssemblyResource");
customIcuCandidate.SetMetadata("AssetTraitValue", "native");
diff --git a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonData.cs b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonData.cs
index 19c0d1f02e764..8df0f567148cb 100644
--- a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonData.cs
+++ b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonData.cs
@@ -125,6 +125,9 @@ public class ResourcesData
///
public string hash { get; set; }
+ [DataMember(EmitDefaultValue = false)]
+ public Dictionary fingerprinting { get; set; }
+
///
/// .NET Wasm runtime resources (dotnet.wasm, dotnet.js) etc.
///
diff --git a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/ComputeWasmBuildAssets.cs b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/ComputeWasmBuildAssets.cs
index 6e2962593c3a3..e8daa16e60813 100644
--- a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/ComputeWasmBuildAssets.cs
+++ b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/ComputeWasmBuildAssets.cs
@@ -31,9 +31,6 @@ public class ComputeWasmBuildAssets : Task
[Required]
public ITaskItem[] ProjectSatelliteAssemblies { get; set; }
- [Required]
- public string DotNetJsVersion { get; set; }
-
[Required]
public string OutputPath { get; set; }
@@ -52,12 +49,14 @@ public class ComputeWasmBuildAssets : Task
[Required]
public bool CopySymbols { get; set; }
- public bool FingerprintDotNetJs { get; set; }
-
public bool EnableThreads { get; set; }
public bool EmitSourceMap { get; set; }
+ public bool FingerprintAssets { get; set; }
+
+ public bool FingerprintDotNetJs { get; set; }
+
[Output]
public ITaskItem[] AssetCandidates { get; set; }
@@ -117,45 +116,8 @@ public override bool Execute()
continue;
}
- string candidateFileName = candidate.GetMetadata("FileName");
- string extension = candidate.GetMetadata("Extension");
- if (candidateFileName.StartsWith("dotnet") && (extension == ".js" || extension == ".mjs"))
- {
- string newDotnetJSFileName = null;
- string newDotNetJSFullPath = null;
- if (candidateFileName != "dotnet" || FingerprintDotNetJs)
- {
- var itemHash = FileHasher.GetFileHash(candidate.ItemSpec);
- newDotnetJSFileName = $"{candidateFileName}.{DotNetJsVersion}.{itemHash}{extension}";
-
- var originalFileFullPath = Path.GetFullPath(candidate.ItemSpec);
- var originalFileDirectory = Path.GetDirectoryName(originalFileFullPath);
-
- newDotNetJSFullPath = Path.Combine(originalFileDirectory, newDotnetJSFileName);
- }
- else
- {
- newDotNetJSFullPath = candidate.ItemSpec;
- newDotnetJSFileName = Path.GetFileName(newDotNetJSFullPath);
- }
-
- var newDotNetJs = new TaskItem(newDotNetJSFullPath, candidate.CloneCustomMetadata());
- newDotNetJs.SetMetadata("OriginalItemSpec", candidate.ItemSpec);
-
- var newRelativePath = $"_framework/{newDotnetJSFileName}";
- newDotNetJs.SetMetadata("RelativePath", newRelativePath);
-
- newDotNetJs.SetMetadata("AssetTraitName", "WasmResource");
- newDotNetJs.SetMetadata("AssetTraitValue", "native");
-
- assetCandidates.Add(newDotNetJs);
- continue;
- }
- else
- {
- string relativePath = AssetsComputingHelper.GetCandidateRelativePath(candidate);
- candidate.SetMetadata("RelativePath", relativePath);
- }
+ string relativePath = AssetsComputingHelper.GetCandidateRelativePath(candidate, FingerprintAssets, FingerprintDotNetJs);
+ candidate.SetMetadata("RelativePath", relativePath);
// Workaround for https://github.com/dotnet/aspnetcore/issues/37574.
// For items added as "Reference" in project references, the OriginalItemSpec is incorrect.
@@ -265,6 +227,8 @@ private static void ApplyUniqueMetadataProperties(ITaskItem candidate)
break;
case ".wasm":
case ".blat":
+ case ".js" when filename.StartsWith("dotnet"):
+ case ".mjs" when filename.StartsWith("dotnet"):
case ".dat" when filename.StartsWith("icudt"):
case ".json" when filename.StartsWith("segmentation-rules"):
candidate.SetMetadata("AssetTraitName", "WasmResource");
diff --git a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/ComputeWasmPublishAssets.cs b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/ComputeWasmPublishAssets.cs
index 1255494e22e29..3ea146e92e6ed 100644
--- a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/ComputeWasmPublishAssets.cs
+++ b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/ComputeWasmPublishAssets.cs
@@ -55,20 +55,20 @@ public class ComputeWasmPublishAssets : Task
[Required]
public string PublishPath { get; set; }
- [Required]
- public string DotNetJsVersion { get; set; }
-
- public bool FingerprintDotNetJs { get; set; }
-
public bool EnableThreads { get; set; }
public bool EmitSourceMap { get; set; }
public bool IsWebCilEnabled { get; set; }
+ public bool FingerprintAssets { get; set; }
+
[Output]
public ITaskItem[] NewCandidates { get; set; }
+ [Output]
+ public ITaskItem[] PromotedAssets { get; set; }
+
[Output]
public ITaskItem[] FilesToRemove { get; set; }
@@ -76,6 +76,7 @@ public override bool Execute()
{
var filesToRemove = new List();
var newAssets = new List();
+ var promotedAssets = new List();
try
{
@@ -109,33 +110,32 @@ public override bool Execute()
symbolAssets,
compressedRepresentations);
- var newStaticWebAssets = ComputeUpdatedAssemblies(
+ ComputeUpdatedAssemblies(
satelliteAssemblyToPublish,
filesToRemove,
resolvedAssembliesToPublish,
assemblyAssets,
satelliteAssemblyAssets,
- compressedRepresentations);
-
- newAssets.AddRange(newStaticWebAssets);
+ compressedRepresentations,
+ newAssets,
+ promotedAssets);
- var nativeStaticWebAssets = ProcessNativeAssets(
+ ProcessNativeAssets(
nativeAssets,
resolvedFilesToPublishToRemove,
resolvedNativeAssetToPublish,
compressedRepresentations,
- filesToRemove);
-
- newAssets.AddRange(nativeStaticWebAssets);
+ filesToRemove,
+ newAssets,
+ promotedAssets);
- var symbolStaticWebAssets = ProcessSymbolAssets(
+ ProcessSymbolAssets(
symbolAssets,
compressedRepresentations,
resolvedFilesToPublishToRemove,
resolvedSymbolsToPublish,
- filesToRemove);
-
- newAssets.AddRange(symbolStaticWebAssets);
+ filesToRemove,
+ promotedAssets);
foreach (var kvp in resolvedFilesToPublishToRemove)
{
@@ -151,22 +151,28 @@ public override bool Execute()
FilesToRemove = filesToRemove.ToArray();
NewCandidates = newAssets.ToArray();
+ PromotedAssets = promotedAssets.ToArray();
return !Log.HasLoggedErrors;
}
- private List ProcessNativeAssets(
+ private void ProcessNativeAssets(
Dictionary nativeAssets,
IDictionary resolvedPublishFilesToRemove,
Dictionary resolvedNativeAssetToPublish,
Dictionary compressedRepresentations,
- List filesToRemove)
+ List filesToRemove,
+ List newAssets,
+ List promotedAssets)
{
var nativeStaticWebAssets = new List();
// Keep track of the updated assets to determine what compressed assets we can reuse
var updateMap = new Dictionary();
+ // Keep track of not-fingerprinted asset mapped to fingerprinted.
+ var mappedFingerprintedAssets = new Dictionary();
+
foreach (var kvp in nativeAssets)
{
var key = kvp.Key;
@@ -182,11 +188,14 @@ private List ProcessNativeAssets(
{
// This is a native asset like timezones.blat or similar that was not filtered and that needs to be updated
// to a publish asset.
- var newAsset = new TaskItem(asset);
- ApplyPublishProperties(newAsset);
+ ITaskItem newAsset = CreatePromotedAsset(asset);
+ if (newAsset.ItemSpec != asset.ItemSpec)
+ mappedFingerprintedAssets[asset.ItemSpec] = newAsset.ItemSpec;
+
nativeStaticWebAssets.Add(newAsset);
filesToRemove.Add(existing);
updateMap.Add(asset.ItemSpec, newAsset);
+ promotedAssets.Add(newAsset);
Log.LogMessage(MessageImportance.Low, "Promoting asset '{0}' to Publish asset.", asset.ItemSpec);
}
else
@@ -224,22 +233,22 @@ private List ProcessNativeAssets(
newDotNetJs = new TaskItem(Path.GetFullPath(aotDotNetJs.ItemSpec), asset.CloneCustomMetadata());
newDotNetJs.SetMetadata("OriginalItemSpec", aotDotNetJs.ItemSpec);
- string relativePath = baseName != "dotnet" || FingerprintDotNetJs
- ? $"_framework/{$"{baseName}.{DotNetJsVersion}.{FileHasher.GetFileHash(aotDotNetJs.ItemSpec)}{extension}"}"
- : $"_framework/{baseName}{extension}";
-
- newDotNetJs.SetMetadata("RelativePath", relativePath);
+ ApplyPublishProperties(newDotNetJs);
updateMap.Add(asset.ItemSpec, newDotNetJs);
+ newAssets.Add(newDotNetJs);
Log.LogMessage(MessageImportance.Low, "Replacing asset '{0}' with AoT version '{1}'", asset.ItemSpec, newDotNetJs.ItemSpec);
}
else
{
- newDotNetJs = new TaskItem(asset);
+ newDotNetJs = CreatePromotedAsset(asset);
+ if (newDotNetJs.ItemSpec != asset.ItemSpec)
+ mappedFingerprintedAssets[asset.ItemSpec] = newDotNetJs.ItemSpec;
+
+ promotedAssets.Add(newDotNetJs);
Log.LogMessage(MessageImportance.Low, "Promoting asset '{0}' to Publish asset.", asset.ItemSpec);
}
- ApplyPublishProperties(newDotNetJs);
nativeStaticWebAssets.Add(newDotNetJs);
if (resolvedNativeAssetToPublish.TryGetValue($"{baseName}{extension}", out var resolved))
{
@@ -260,16 +269,22 @@ private List ProcessNativeAssets(
{
newDotNetWasm = new TaskItem(Path.GetFullPath(aotDotNetWasm.ItemSpec), asset.CloneCustomMetadata());
newDotNetWasm.SetMetadata("OriginalItemSpec", aotDotNetWasm.ItemSpec);
+ ApplyPublishProperties(newDotNetWasm);
+
updateMap.Add(asset.ItemSpec, newDotNetWasm);
+ newAssets.Add(newDotNetWasm);
Log.LogMessage(MessageImportance.Low, "Replacing asset '{0}' with AoT version '{1}'", asset.ItemSpec, newDotNetWasm.ItemSpec);
}
else
{
- newDotNetWasm = new TaskItem(asset);
+ newDotNetWasm = CreatePromotedAsset(asset);
+ if (newDotNetWasm.ItemSpec != asset.ItemSpec)
+ mappedFingerprintedAssets[asset.ItemSpec] = newDotNetWasm.ItemSpec;
+
+ promotedAssets.Add(newDotNetWasm);
Log.LogMessage(MessageImportance.Low, "Promoting asset '{0}' to Publish asset.", asset.ItemSpec);
}
- ApplyPublishProperties(newDotNetWasm);
nativeStaticWebAssets.Add(newDotNetWasm);
if (resolvedNativeAssetToPublish.TryGetValue("dotnet.native.wasm", out var resolved))
@@ -287,10 +302,18 @@ private List ProcessNativeAssets(
var compressedUpdatedFiles = ProcessCompressedAssets(compressedRepresentations, nativeAssets, updateMap);
foreach (var f in compressedUpdatedFiles)
{
- nativeStaticWebAssets.Add(f);
- }
+ var compressed = f;
+ if (mappedFingerprintedAssets.TryGetValue(compressed.GetMetadata("RelatedAsset"), out var fingerprintedAsset))
+ {
+ Log.LogMessage(MessageImportance.Low, "Changing related asset for compressed asset '{0}' to '{1}'.", compressed.ItemSpec, fingerprintedAsset);
+
+ compressed = new TaskItem(compressed);
+ compressed.SetMetadata("RelatedAsset", fingerprintedAsset);
+ }
- return nativeStaticWebAssets;
+ promotedAssets.Add(compressed);
+ nativeStaticWebAssets.Add(compressed);
+ }
static bool IsAnyDotNetJs(string key)
{
@@ -306,17 +329,45 @@ static bool IsDotNetWasm(string key)
}
}
- private List ProcessSymbolAssets(
+ private TaskItem CreatePromotedAsset(ITaskItem asset)
+ {
+ string newAssetItemSpec = asset.ItemSpec;
+ string newAssetRelativePath = asset.GetMetadata("RelativePath");
+
+ if (FingerprintAssets)
+ {
+ string assetDirectory = Path.GetDirectoryName(asset.ItemSpec);
+ string assetFileNameToFingerprint = Path.GetFileName(newAssetRelativePath);
+ string fingerprint = asset.GetMetadata("Fingerprint");
+ string newAssetFingerprintedFileName = assetFileNameToFingerprint.Replace("#[.{fingerprint}]!", $".{fingerprint}");
+ if (newAssetFingerprintedFileName != assetFileNameToFingerprint)
+ {
+ newAssetItemSpec = $"{assetDirectory}/{newAssetFingerprintedFileName}";
+ newAssetRelativePath = newAssetRelativePath.Replace(assetFileNameToFingerprint, newAssetFingerprintedFileName);
+ }
+ }
+
+ var newAsset = new TaskItem(newAssetItemSpec, asset.CloneCustomMetadata());
+ newAsset.SetMetadata("RelativePath", newAssetRelativePath);
+
+ ApplyPublishProperties(newAsset);
+ return newAsset;
+ }
+
+ private void ProcessSymbolAssets(
Dictionary symbolAssets,
Dictionary compressedRepresentations,
Dictionary resolvedPublishFilesToRemove,
Dictionary resolvedSymbolAssetToPublish,
- List filesToRemove)
+ List filesToRemove,
+ List promotedAssets)
{
var symbolStaticWebAssets = new List();
var updateMap = new Dictionary();
var existingToRemove = new Dictionary();
+ var mappedFingerprintedAssets = new Dictionary();
+
foreach (var kvp in symbolAssets)
{
var asset = kvp.Value;
@@ -326,11 +377,14 @@ private List ProcessSymbolAssets(
{
// This is a symbol asset like classlibrary.pdb or similar that was not filtered and that needs to be updated
// to a publish asset.
- var newAsset = new TaskItem(asset);
- ApplyPublishProperties(newAsset);
+ var newAsset = CreatePromotedAsset(asset);
+ if (newAsset.ItemSpec != asset.ItemSpec)
+ mappedFingerprintedAssets[asset.ItemSpec] = newAsset.ItemSpec;
+
symbolStaticWebAssets.Add(newAsset);
updateMap.Add(newAsset.ItemSpec, newAsset);
filesToRemove.Add(existing);
+ promotedAssets.Add(newAsset);
Log.LogMessage(MessageImportance.Low, "Promoting asset '{0}' to Publish asset.", asset.ItemSpec);
}
else
@@ -350,22 +404,31 @@ private List ProcessSymbolAssets(
}
var compressedFiles = ProcessCompressedAssets(compressedRepresentations, symbolAssets, updateMap, existingToRemove);
-
- foreach (var file in compressedFiles)
+ foreach (var f in compressedFiles)
{
- symbolStaticWebAssets.Add(file);
- }
+ var compressed = f;
+ if (mappedFingerprintedAssets.TryGetValue(compressed.GetMetadata("RelatedAsset"), out var fingerprintedAsset))
+ {
+ Log.LogMessage(MessageImportance.Low, "Changing related asset for compressed asset '{0}' to '{1}'.", compressed.ItemSpec, fingerprintedAsset);
- return symbolStaticWebAssets;
+ compressed = new TaskItem(compressed);
+ compressed.SetMetadata("RelatedAsset", fingerprintedAsset);
+ }
+
+ promotedAssets.Add(compressed);
+ symbolStaticWebAssets.Add(compressed);
+ }
}
- private List ComputeUpdatedAssemblies(
+ private void ComputeUpdatedAssemblies(
IDictionary<(string, string assemblyName), ITaskItem> satelliteAssemblies,
List filesToRemove,
Dictionary resolvedAssembliesToPublish,
Dictionary assemblyAssets,
Dictionary satelliteAssemblyAssets,
- Dictionary compressedRepresentations)
+ Dictionary compressedRepresentations,
+ List newAssets,
+ List promotedAssets)
{
// All assemblies, satellite assemblies and gzip files are initially defined as build assets.
// We need to update them to publish assets when they haven't changed or when they have been linked.
@@ -377,7 +440,7 @@ private List ComputeUpdatedAssemblies(
foreach (var kvp in assemblyAssets)
{
var asset = kvp.Value;
- var fileName = Path.GetFileName(asset.GetMetadata("RelativePath"));
+ var fileName = Path.GetFileName(asset.ItemSpec);
if (IsWebCilEnabled)
fileName = Path.ChangeExtension(fileName, ".dll");
@@ -391,6 +454,11 @@ private List ComputeUpdatedAssemblies(
linkedAssets.Add(asset.ItemSpec, existing);
}
}
+ else
+ {
+ Log.LogMessage(MessageImportance.Low, "Asset '{0}' is not present in resolved files to publish and will be omitted from publish",
+ asset.ItemSpec);
+ }
}
foreach (var kvp in satelliteAssemblyAssets)
@@ -401,7 +469,7 @@ private List ComputeUpdatedAssemblies(
{
assetsToUpdate.Add(satelliteAssembly.ItemSpec, satelliteAssembly);
var culture = satelliteAssembly.GetMetadata("AssetTraitValue");
- var fileName = Path.GetFileName(satelliteAssembly.GetMetadata("RelativePath"));
+ var fileName = Path.GetFileName(satelliteAssembly.ItemSpec);
if (IsWebCilEnabled)
fileName = Path.ChangeExtension(fileName, ".dll");
@@ -448,21 +516,20 @@ private List ComputeUpdatedAssemblies(
}
ApplyPublishProperties(newAsemblyAsset);
+ newAssets.Add(newAsemblyAsset);
updatedAssetsMap.Add(asset.ItemSpec, newAsemblyAsset);
break;
default:
// Satellite assembliess and compressed assets
- var dependentAsset = new TaskItem(asset);
- ApplyPublishProperties(dependentAsset);
- UpdateRelatedAssetProperty(asset, dependentAsset, updatedAssetsMap);
+ TaskItem newAsset = CreatePromotedAsset(asset);
+ UpdateRelatedAssetProperty(asset, newAsset, updatedAssetsMap);
Log.LogMessage(MessageImportance.Low, "Promoting asset '{0}' to Publish asset.", asset.ItemSpec);
- updatedAssetsMap.Add(asset.ItemSpec, dependentAsset);
+ promotedAssets.Add(newAsset);
+ updatedAssetsMap.Add(asset.ItemSpec, newAsset);
break;
}
}
-
- return updatedAssetsMap.Values.ToList();
}
private List ProcessCompressedAssets(
@@ -589,7 +656,7 @@ private void GroupResolvedFilesToPublish(
var resolvedFilesToPublish = ResolvedFilesToPublish.ToList();
if (AssetsComputingHelper.TryGetAssetFilename(CustomIcuCandidate, out string customIcuCandidateFilename))
{
- var customIcuCandidate = AssetsComputingHelper.GetCustomIcuAsset(CustomIcuCandidate);
+ var customIcuCandidate = AssetsComputingHelper.GetCustomIcuAsset(CustomIcuCandidate, FingerprintAssets);
resolvedFilesToPublish.Add(customIcuCandidate);
}
diff --git a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs
index 33f3debdd4ef8..63cee84d412a8 100644
--- a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs
+++ b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs
@@ -34,6 +34,9 @@ public class GenerateWasmBootJson : Task
[Required]
public ITaskItem[] Resources { get; set; }
+ [Required]
+ public ITaskItem[] Endpoints { get; set; }
+
[Required]
public bool DebugBuild { get; set; }
@@ -81,6 +84,8 @@ public class GenerateWasmBootJson : Task
public bool IsMultiThreaded { get; set; }
+ public bool FingerprintAssets { get; set; }
+
public override bool Execute()
{
using var fileStream = File.Create(OutputPath);
@@ -169,8 +174,14 @@ public void WriteBootJson(Stream output, string entryAssemblyName)
// - ContentHash (e.g., "3448f339acf512448")
if (Resources != null)
{
+ var endpointByAsset = Endpoints.ToDictionary(e => e.GetMetadata("AssetFile"));
+
var remainingLazyLoadAssemblies = new List(LazyLoadedAssemblies ?? Array.Empty());
var resourceData = result.resources;
+
+ if (FingerprintAssets)
+ resourceData.fingerprinting = new();
+
foreach (var resource in Resources)
{
ResourceHashesByNameDictionary resourceList = null;
@@ -180,10 +191,12 @@ public void WriteBootJson(Stream output, string entryAssemblyName)
var fileExtension = resource.GetMetadata("Extension");
var assetTraitName = resource.GetMetadata("AssetTraitName");
var assetTraitValue = resource.GetMetadata("AssetTraitValue");
- var resourceName = Path.GetFileName(resource.GetMetadata("RelativePath"));
+ var resourceName = Path.GetFileName(resource.GetMetadata("OriginalItemSpec"));
+ var resourceRoute = Path.GetFileName(endpointByAsset[resource.ItemSpec].ItemSpec);
if (TryGetLazyLoadedAssembly(resourceName, out var lazyLoad))
{
+ MapFingerprintedAsset(resourceData, resourceRoute, resourceName);
Log.LogMessage(MessageImportance.Low, "Candidate '{0}' is defined as a lazy loaded assembly.", resource.ItemSpec);
remainingLazyLoadAssemblies.Remove(lazyLoad);
resourceData.lazyAssembly ??= new ResourceHashesByNameDictionary();
@@ -191,11 +204,12 @@ public void WriteBootJson(Stream output, string entryAssemblyName)
}
else if (string.Equals("Culture", assetTraitName, StringComparison.OrdinalIgnoreCase))
{
+ MapFingerprintedAsset(resourceData, resourceRoute, resourceName);
Log.LogMessage(MessageImportance.Low, "Candidate '{0}' is defined as satellite assembly with culture '{1}'.", resource.ItemSpec, assetTraitValue);
resourceData.satelliteResources ??= new Dictionary(StringComparer.OrdinalIgnoreCase);
if (!IsTargeting80OrLater())
- resourceName = assetTraitValue + "/" + resourceName;
+ resourceRoute = assetTraitValue + "/" + resourceRoute;
if (!resourceData.satelliteResources.TryGetValue(assetTraitValue, out resourceList))
{
@@ -205,6 +219,7 @@ public void WriteBootJson(Stream output, string entryAssemblyName)
}
else if (string.Equals("symbol", assetTraitValue, StringComparison.OrdinalIgnoreCase))
{
+ MapFingerprintedAsset(resourceData, resourceRoute, resourceName);
if (TryGetLazyLoadedAssembly($"{fileName}.dll", out _) || TryGetLazyLoadedAssembly($"{fileName}{Utils.WebcilInWasmExtension}", out _))
{
Log.LogMessage(MessageImportance.Low, "Candidate '{0}' is defined as a lazy loaded symbols file.", resource.ItemSpec);
@@ -229,6 +244,7 @@ public void WriteBootJson(Stream output, string entryAssemblyName)
}
else if (string.Equals("runtime", assetTraitValue, StringComparison.OrdinalIgnoreCase))
{
+ MapFingerprintedAsset(resourceData, resourceRoute, resourceName);
if (IsTargeting90OrLater() && (IsAot || helper.IsCoreAssembly(resourceName)))
{
Log.LogMessage(MessageImportance.Low, "Candidate '{0}' is defined as core assembly.", resource.ItemSpec);
@@ -243,6 +259,7 @@ public void WriteBootJson(Stream output, string entryAssemblyName)
else if (string.Equals(assetTraitName, "WasmResource", StringComparison.OrdinalIgnoreCase) &&
string.Equals(assetTraitValue, "native", StringComparison.OrdinalIgnoreCase))
{
+ MapFingerprintedAsset(resourceData, resourceRoute, resourceName);
Log.LogMessage(MessageImportance.Low, "Candidate '{0}' is defined as a native application resource.", resource.ItemSpec);
if (IsTargeting80OrLater())
@@ -264,7 +281,7 @@ public void WriteBootJson(Stream output, string entryAssemblyName)
{
Log.LogMessage(MessageImportance.Low, "Candidate '{0}' is defined as a library initializer resource.", resource.ItemSpec);
- var targetPath = resource.GetMetadata("TargetPath");
+ var targetPath = endpointByAsset[resource.ItemSpec].ItemSpec;
Debug.Assert(!string.IsNullOrEmpty(targetPath), "Target path for '{0}' must exist.", resource.ItemSpec);
resourceList = resourceData.libraryInitializers ??= new ResourceHashesByNameDictionary();
@@ -324,13 +341,13 @@ public void WriteBootJson(Stream output, string entryAssemblyName)
if (resourceList != null)
{
- AddResourceToList(resource, resourceList, resourceName);
+ AddResourceToList(resource, resourceList, resourceRoute);
}
if (!string.IsNullOrEmpty(behavior))
{
resourceData.runtimeAssets ??= new Dictionary();
- AddToAdditionalResources(resource, resourceData.runtimeAssets, resourceName, behavior);
+ AddToAdditionalResources(resource, resourceData.runtimeAssets, resourceRoute, behavior);
}
}
@@ -406,11 +423,19 @@ void AddResourceToList(ITaskItem resource, ResourceHashesByNameDictionary resour
if (!resourceList.ContainsKey(resourceKey))
{
Log.LogMessage(MessageImportance.Low, "Added resource '{0}' to the manifest.", resource.ItemSpec);
- resourceList.Add(resourceKey, $"sha256-{resource.GetMetadata("FileHash")}");
+ resourceList.Add(resourceKey, $"sha256-{resource.GetMetadata("Integrity")}");
}
}
}
+ private void MapFingerprintedAsset(ResourcesData resources, string resourceRoute, string resourceName)
+ {
+ if (!FingerprintAssets || !IsTargeting90OrLater())
+ return;
+
+ resources.fingerprinting[resourceRoute] = resourceName;
+ }
+
private GlobalizationMode GetGlobalizationMode()
{
if (string.Equals(InvariantGlobalization, "true", StringComparison.OrdinalIgnoreCase))