-
Notifications
You must be signed in to change notification settings - Fork 5.4k
Fix WASM publish path for Framework SourceType materialization #126210
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
Changes from all commits
1c6e5b6
91040a0
2965259
fa33973
69c5452
7f518a5
2c63e09
0d9edce
d92ebd8
7847a96
659ff63
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -101,4 +101,160 @@ public void BugRegression_60479_WithRazorClassLib() | |
|
|
||
| Assert.Contains(((AssetsData)bootJson.resources).lazyAssembly, f => f.name.StartsWith(razorClassLibraryName)); | ||
| } | ||
|
|
||
| [Theory] | ||
| [InlineData(Configuration.Debug)] | ||
| [InlineData(Configuration.Release)] | ||
| public void MultiClientHostedBuild(Configuration config) | ||
| { | ||
| // Test that two Blazor WASM client projects can be hosted by a single server project | ||
| // without duplicate static web asset Identity collisions. This validates the Framework | ||
| // SourceType materialization path that gives each client unique per-project Identity | ||
| // for shared runtime pack files. | ||
| ProjectInfo info = CopyTestAsset(config, aot: false, TestAsset.BlazorBasicTestApp, "multi_hosted"); | ||
|
|
||
| // _projectDir is now .../App. Go up to the root and create a second client + server. | ||
| string rootDir = Path.GetDirectoryName(_projectDir)!; | ||
| string client1Dir = _projectDir; | ||
| string client2Dir = Path.Combine(rootDir, "App2"); | ||
| string serverDir = Path.Combine(rootDir, "Server"); | ||
|
|
||
| // Duplicate App as App2 with a different StaticWebAssetBasePath | ||
| Utils.DirectoryCopy(client1Dir, client2Dir); | ||
| string client2Csproj = Path.Combine(client2Dir, "BlazorBasicTestApp.csproj"); | ||
| File.Move(client2Csproj, Path.Combine(client2Dir, "BlazorBasicTestApp2.csproj")); | ||
| client2Csproj = Path.Combine(client2Dir, "BlazorBasicTestApp2.csproj"); | ||
|
|
||
| // Set different base paths so the two clients don't collide on routes | ||
| AddItemsPropertiesToProject(Path.Combine(client1Dir, "BlazorBasicTestApp.csproj"), | ||
| extraProperties: "<StaticWebAssetBasePath>client1</StaticWebAssetBasePath>"); | ||
| AddItemsPropertiesToProject(client2Csproj, | ||
| extraProperties: "<StaticWebAssetBasePath>client2</StaticWebAssetBasePath><RootNamespace>BlazorBasicTestApp</RootNamespace>"); | ||
|
|
||
| // Create a minimal server project that references both clients | ||
| Directory.CreateDirectory(serverDir); | ||
| string serverCsproj = Path.Combine(serverDir, "Server.csproj"); | ||
| File.WriteAllText(serverCsproj, $""" | ||
| <Project Sdk="Microsoft.NET.Sdk.Web"> | ||
| <PropertyGroup> | ||
| <TargetFramework>{DefaultTargetFrameworkForBlazor}</TargetFramework> | ||
| <Nullable>enable</Nullable> | ||
| <ImplicitUsings>enable</ImplicitUsings> | ||
| </PropertyGroup> | ||
| <ItemGroup> | ||
| <ProjectReference Include="..\App\BlazorBasicTestApp.csproj" /> | ||
| <ProjectReference Include="..\App2\BlazorBasicTestApp2.csproj" /> | ||
| </ItemGroup> | ||
| </Project> | ||
| """); | ||
| File.WriteAllText(Path.Combine(serverDir, "Program.cs"), """ | ||
| var builder = WebApplication.CreateBuilder(args); | ||
| var app = builder.Build(); | ||
| app.UseStaticFiles(); | ||
| app.Run(); | ||
| """); | ||
|
|
||
| // Build the server project — this will transitively build both clients. | ||
| // Without Framework materialization, this would fail with duplicate Identity | ||
| // for shared runtime pack files (dotnet.native.js, ICU data, etc.) | ||
| string logPath = Path.Combine(s_buildEnv.LogRootPath, info.ProjectName, $"{info.ProjectName}-multi-hosted.binlog"); | ||
| using ToolCommand cmd = new DotNetCommand(s_buildEnv, _testOutput) | ||
| .WithWorkingDirectory(serverDir); | ||
| CommandResult result = cmd | ||
| .WithEnvironmentVariable("NUGET_PACKAGES", _nugetPackagesDir) | ||
| .ExecuteWithCapturedOutput("build", $"-p:Configuration={config}", $"-bl:{logPath}") | ||
| .EnsureSuccessful(); | ||
|
|
||
| // Verify both clients produced framework files in their own bin directories | ||
| string client1Framework = Path.Combine(client1Dir, "bin", config.ToString(), DefaultTargetFrameworkForBlazor, "wwwroot", "_framework"); | ||
| string client2Framework = Path.Combine(client2Dir, "bin", config.ToString(), DefaultTargetFrameworkForBlazor, "wwwroot", "_framework"); | ||
|
|
||
| Assert.True(Directory.Exists(client1Framework), $"Client1 framework dir missing: {client1Framework}"); | ||
| Assert.True(Directory.Exists(client2Framework), $"Client2 framework dir missing: {client2Framework}"); | ||
|
|
||
| // Both should have dotnet.js (verifies framework files were materialized per-client) | ||
| var client1Files = Directory.GetFiles(client1Framework); | ||
| var client2Files = Directory.GetFiles(client2Framework); | ||
| Assert.Contains(client1Files, f => Path.GetFileName(f).StartsWith("dotnet.") && f.EndsWith(".js")); | ||
| Assert.Contains(client2Files, f => Path.GetFileName(f).StartsWith("dotnet.") && f.EndsWith(".js")); | ||
| } | ||
|
|
||
| [Theory] | ||
| [InlineData(Configuration.Debug)] | ||
| [InlineData(Configuration.Release)] | ||
| public void MultiClientHostedPublish(Configuration config) | ||
| { | ||
| // Test that two Blazor WASM client projects can be published by a single server project | ||
| // without duplicate static web asset Identity collisions during publish. | ||
| // This validates the Framework SourceType materialization in the PUBLISH path | ||
| // (ProcessPublishFilesForWasm), complementing MultiClientHostedBuild which tests build only. | ||
| ProjectInfo info = CopyTestAsset(config, aot: false, TestAsset.BlazorBasicTestApp, "multi_pub"); | ||
|
|
||
| string rootDir = Path.GetDirectoryName(_projectDir)!; | ||
| string client1Dir = _projectDir; | ||
| string client2Dir = Path.Combine(rootDir, "App2"); | ||
| string serverDir = Path.Combine(rootDir, "Server"); | ||
|
|
||
|
Comment on lines
+193
to
+197
|
||
| // Duplicate App as App2 with a different StaticWebAssetBasePath | ||
| Utils.DirectoryCopy(client1Dir, client2Dir); | ||
| string client2Csproj = Path.Combine(client2Dir, "BlazorBasicTestApp.csproj"); | ||
| File.Move(client2Csproj, Path.Combine(client2Dir, "BlazorBasicTestApp2.csproj")); | ||
| client2Csproj = Path.Combine(client2Dir, "BlazorBasicTestApp2.csproj"); | ||
|
|
||
| AddItemsPropertiesToProject(Path.Combine(client1Dir, "BlazorBasicTestApp.csproj"), | ||
| extraProperties: "<StaticWebAssetBasePath>client1</StaticWebAssetBasePath>"); | ||
| AddItemsPropertiesToProject(client2Csproj, | ||
| extraProperties: "<StaticWebAssetBasePath>client2</StaticWebAssetBasePath><RootNamespace>BlazorBasicTestApp</RootNamespace>"); | ||
|
|
||
| // Create a minimal server project that references both clients | ||
| Directory.CreateDirectory(serverDir); | ||
| string serverCsproj = Path.Combine(serverDir, "Server.csproj"); | ||
| File.WriteAllText(serverCsproj, $""" | ||
| <Project Sdk="Microsoft.NET.Sdk.Web"> | ||
| <PropertyGroup> | ||
| <TargetFramework>{DefaultTargetFrameworkForBlazor}</TargetFramework> | ||
| <Nullable>enable</Nullable> | ||
| <ImplicitUsings>enable</ImplicitUsings> | ||
| </PropertyGroup> | ||
| <ItemGroup> | ||
| <ProjectReference Include="..\App\BlazorBasicTestApp.csproj" /> | ||
| <ProjectReference Include="..\App2\BlazorBasicTestApp2.csproj" /> | ||
| </ItemGroup> | ||
| </Project> | ||
| """); | ||
| File.WriteAllText(Path.Combine(serverDir, "Program.cs"), """ | ||
| var builder = WebApplication.CreateBuilder(args); | ||
| var app = builder.Build(); | ||
| app.UseStaticFiles(); | ||
| app.Run(); | ||
| """); | ||
|
|
||
| // Publish the server project — this transitively publishes both WASM clients. | ||
| // Without Framework materialization in the publish path, this crashes with | ||
| // duplicate Identity for shared runtime pack files (dotnet.js.map, ICU data, etc.) | ||
| // in DiscoverPrecompressedAssets. | ||
| string logPath = Path.Combine(s_buildEnv.LogRootPath, info.ProjectName, $"{info.ProjectName}-multi-pub.binlog"); | ||
| using ToolCommand cmd = new DotNetCommand(s_buildEnv, _testOutput) | ||
| .WithWorkingDirectory(serverDir); | ||
| CommandResult result = cmd | ||
| .WithEnvironmentVariable("NUGET_PACKAGES", _nugetPackagesDir) | ||
| .ExecuteWithCapturedOutput("publish", $"-p:Configuration={config}", $"-bl:{logPath}") | ||
| .EnsureSuccessful(); | ||
|
|
||
| // Verify both clients produced publish output with framework files | ||
| string publishDir = Path.Combine(serverDir, "bin", config.ToString(), DefaultTargetFrameworkForBlazor, "publish"); | ||
| string client1Publish = Path.Combine(publishDir, "wwwroot", "client1", "_framework"); | ||
| string client2Publish = Path.Combine(publishDir, "wwwroot", "client2", "_framework"); | ||
|
|
||
| Assert.True(Directory.Exists(client1Publish), $"Client1 publish framework dir missing: {client1Publish}"); | ||
| Assert.True(Directory.Exists(client2Publish), $"Client2 publish framework dir missing: {client2Publish}"); | ||
|
|
||
| // Both should have dotnet.js and dotnet.native.wasm (verifies framework files were materialized per-client) | ||
| var client1Files = Directory.GetFiles(client1Publish); | ||
| var client2Files = Directory.GetFiles(client2Publish); | ||
| Assert.Contains(client1Files, f => Path.GetFileName(f).StartsWith("dotnet.") && f.EndsWith(".js")); | ||
| Assert.Contains(client2Files, f => Path.GetFileName(f).StartsWith("dotnet.") && f.EndsWith(".js")); | ||
| Assert.Contains(client1Files, f => Path.GetFileName(f).Contains("dotnet.native") && f.EndsWith(".wasm")); | ||
| Assert.Contains(client2Files, f => Path.GetFileName(f).Contains("dotnet.native") && f.EndsWith(".wasm")); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These tests create
App2andServeras siblings of_projectDir(underrootDir). Per-test cleanup deletes_projectDir(theAppdirectory) but will leaveApp2/Serverbehind, which can bloat temp storage across runs. Consider creating these directories under_projectDirso they get cleaned up automatically, or adjust cleanup to deleterootDir(e.g., via a try/finally that updates_projectDirtorootDir).