Skip to content

Commit e8aab6a

Browse files
[release/6.0] [wasm] Require workloads if using @(NativeFileReference) (#58290)
* [wasm] Require workloads, if a project is using native references Currently, if the `wasm-tools` workload is not installed, and a project uses AOT, then the build fails with an error saying that the workload is needed. But if the project is using native references, but not AOT, then the build does not fail. Instead, the `@(NativeFileReference)` just gets ignored. Even though the wasm workload is needed to relink dotnet.wasm with the native libraries. Implementation: - `$(RunAOTCompilation)` is a property, so it can be checked, and wasm workload imports can be enabled. - But `@(NativeFileReference)` is an item, and that gets evaluated in the second phase, so we can't use that to affect the imports. - Instead, we emit a warning from a target run before Build, if the project has any native references, but the workload isn't enabled. - Users can explicitly enable the workload by setting `$(WasmBuildNative)==true`. * Bump sdk for workload testing to 6.0.100-rc.2.21425.12 * Fix path to workload.stamp file Co-authored-by: Ankit Jain <[email protected]>
1 parent 08b249b commit e8aab6a

6 files changed

Lines changed: 156 additions & 53 deletions

File tree

eng/Versions.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@
163163
<SQLitePCLRawbundle_greenVersion>2.0.4</SQLitePCLRawbundle_greenVersion>
164164
<MoqVersion>4.12.0</MoqVersion>
165165
<FsCheckVersion>2.14.3</FsCheckVersion>
166-
<SdkVersionForWorkloadTesting>6.0.100-rc.1.21412.8</SdkVersionForWorkloadTesting>
166+
<SdkVersionForWorkloadTesting>6.0.100-rc.2.21425.12</SdkVersionForWorkloadTesting>
167167
<!-- Docs -->
168168
<MicrosoftPrivateIntellisenseVersion>5.0.0-preview-20201009.2</MicrosoftPrivateIntellisenseVersion>
169169
<!-- ILLink -->

src/libraries/Directory.Build.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@
138138
<SdkWithNoWorkloadForTestingPath>$([MSBuild]::NormalizeDirectory($(SdkWithNoWorkloadForTestingPath)))</SdkWithNoWorkloadForTestingPath>
139139

140140
<SdkWithNoWorkloadStampPath>$(SdkWithNoWorkloadForTestingPath)version-$(SdkVersionForWorkloadTesting).stamp</SdkWithNoWorkloadStampPath>
141-
<SdkWithNoWorkload_WorkloadStampPath>$(SdkWithWorkloadForTestingPath)workload.stamp</SdkWithNoWorkload_WorkloadStampPath>
141+
<SdkWithNoWorkload_WorkloadStampPath>$(SdkWithNoWorkloadForTestingPath)workload.stamp</SdkWithNoWorkload_WorkloadStampPath>
142142

143143
<SdkWithWorkloadForTestingPath>$(ArtifactsBinDir)dotnet-workload\</SdkWithWorkloadForTestingPath>
144144
<SdkWithWorkloadForTestingPath>$([MSBuild]::NormalizeDirectory($(SdkWithWorkloadForTestingPath)))</SdkWithWorkloadForTestingPath>

src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/WorkloadManifest.targets.in

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,9 @@
77
!$([MSBuild]::VersionGreaterThanOrEquals('$(TargetFrameworkVersion)', '6.0'))">true</BrowserWorkloadDisabled>
88
</PropertyGroup>
99

10-
<PropertyGroup Condition="'$(RuntimeIdentifier)' == 'browser-wasm' and '$(BrowserWorkloadDisabled)' == 'true'">
11-
<_NativeBuildNeeded Condition="'$(RunAOTCompilation)' == 'true'">true</_NativeBuildNeeded>
12-
<WorkloadDisabledWithReason Condition="'$(_NativeBuildNeeded)' == 'true'">WebAssembly workloads (required for AOT) are only supported for projects targeting net6.0+</WorkloadDisabledWithReason>
13-
</PropertyGroup>
14-
1510
<PropertyGroup Condition="'$(RuntimeIdentifier)' == 'browser-wasm' AND '$(UsingBrowserRuntimeWorkload)' == ''">
16-
<UsingBrowserRuntimeWorkload Condition="'$(RunAOTCompilation)' == 'true' or '$(UsingMicrosoftNETSdkBlazorWebAssembly)' != 'true'" >true</UsingBrowserRuntimeWorkload>
11+
<!-- $(WasmBuildNative)==true is needed to enable workloads, when using native references, without AOT -->
12+
<UsingBrowserRuntimeWorkload Condition="'$(RunAOTCompilation)' == 'true' or '$(WasmBuildNative)' == 'true' or '$(UsingMicrosoftNETSdkBlazorWebAssembly)' != 'true'" >true</UsingBrowserRuntimeWorkload>
1713
<UsingBrowserRuntimeWorkload Condition="'$(UsingBrowserRuntimeWorkload)' == ''" >$(WasmNativeWorkload)</UsingBrowserRuntimeWorkload>
1814
</PropertyGroup>
1915

@@ -128,7 +124,21 @@
128124
/>
129125
</ItemGroup>
130126

131-
<Target Name="ErrorDisabledWorkload" Condition="'$(WorkloadDisabledWithReason)' != ''" BeforeTargets="Publish">
132-
<Error Text="$(WorkloadDisabledWithReason)" />
127+
<!-- we can't condition sdk imports on the item @(NativeFileReference). Instead, explicitly check before the build
128+
and emit a warning -->
129+
<Target Name="_CheckBrowserWorkloadNeededButNotAvailable"
130+
Condition="'$(RuntimeIdentifier)' == 'browser-wasm' and '$(BrowserWorkloadDisabled)' != 'true' and '$(WasmNativeWorkload)' != 'true'"
131+
BeforeTargets="Build">
132+
133+
<Warning Condition="@(NativeFileReference->Count()) > 0"
134+
Text="%40(NativeFileReference) is not empty, but the native references won't be linked in, because neither %24(WasmBuildNative), nor %24(RunAOTCompilation) are 'true'. NativeFileReference=@(NativeFileReference)" />
135+
</Target>
136+
137+
<Target Name="_ErrorDisabledWorkload" Condition="'$(BrowserWorkloadDisabled)' == 'true'" BeforeTargets="Build">
138+
<Error Condition="'$(RunAOTCompilation)' == 'true'"
139+
Text="WebAssembly workloads, required for AOT, are only supported for projects targeting net6.0+ . Set %24(RunAOTCompilation)=false to disable it." />
140+
141+
<Error Condition="@(NativeFileReference->Count()) > 0"
142+
Text="WebAssembly workloads, required for linking native files (from %40(NativeFileReference)), are only supported for projects targeting net6.0+ ." />
133143
</Target>
134144
</Project>

src/tests/BuildWasmApps/Wasm.Build.Tests/BlazorWasmTests.cs

Lines changed: 93 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -26,27 +26,17 @@ public BlazorWasmTests(ITestOutputHelper output, SharedBuildPerTestClassFixture
2626
[InlineData("Release", true)]
2727
public void PublishTemplateProject(string config, bool aot)
2828
{
29-
string id = $"blazorwasm_{config}_aot_{aot}";
30-
InitPaths(id);
31-
if (Directory.Exists(_projectDir))
32-
Directory.Delete(_projectDir, recursive: true);
33-
Directory.CreateDirectory(_projectDir);
34-
Directory.CreateDirectory(Path.Combine(_projectDir, ".nuget"));
35-
36-
File.Copy(Path.Combine(BuildEnvironment.TestDataPath, "nuget6.config"), Path.Combine(_projectDir, "nuget.config"));
37-
File.Copy(Path.Combine(BuildEnvironment.TestDataPath, "Blazor.Directory.Build.props"), Path.Combine(_projectDir, "Directory.Build.props"));
38-
File.Copy(Path.Combine(BuildEnvironment.TestDataPath, "Blazor.Directory.Build.targets"), Path.Combine(_projectDir, "Directory.Build.targets"));
39-
40-
string logPath = Path.Combine(s_buildEnv.LogRootPath, id);
29+
string id = $"blazorwasm_{config}_aot_{aot}_{Path.GetRandomFileName()}";
30+
InitBlazorWasmProjectDir(id);
4131

4232
new DotNetCommand(s_buildEnv, useDefaultArgs: false)
43-
.WithWorkingDirectory(_projectDir)
33+
.WithWorkingDirectory(_projectDir!)
4434
.ExecuteWithCapturedOutput("new blazorwasm")
4535
.EnsureSuccessful();
4636

47-
string publishLogPath = Path.Combine(logPath, $"{id}.binlog");
37+
string publishLogPath = Path.Combine(s_buildEnv.LogRootPath, id, $"{id}.binlog");
4838
new DotNetCommand(s_buildEnv)
49-
.WithWorkingDirectory(_projectDir)
39+
.WithWorkingDirectory(_projectDir!)
5040
.ExecuteWithCapturedOutput("publish", $"-bl:{publishLogPath}", aot ? "-p:RunAOTCompilation=true" : "", $"-p:Configuration={config}")
5141
.EnsureSuccessful();
5242

@@ -57,6 +47,76 @@ public void PublishTemplateProject(string config, bool aot)
5747
// playwright?
5848
}
5949

50+
[ConditionalTheory(typeof(BuildTestBase), nameof(IsNotUsingWorkloads))]
51+
[InlineData("Debug")]
52+
[InlineData("Release")]
53+
public void NativeRef_EmitsWarningBecauseItRequiresWorkload(string config)
54+
{
55+
CommandResult res = PublishForRequiresWorkloadTest(config, extraItems: "<NativeFileReference Include=\"native-lib.o\" />");
56+
res.EnsureSuccessful();
57+
58+
Assert.Contains("but the native references won't be linked in", res.Output);
59+
}
60+
61+
[ConditionalTheory(typeof(BuildTestBase), nameof(IsNotUsingWorkloads))]
62+
[InlineData("Debug")]
63+
[InlineData("Release")]
64+
public void AOT_FailsBecauseItRequiresWorkload(string config)
65+
{
66+
CommandResult res = PublishForRequiresWorkloadTest(config, extraProperties: "<RunAOTCompilation>true</RunAOTCompilation>");
67+
Assert.NotEqual(0, res.ExitCode);
68+
Assert.Contains("following workloads must be installed: wasm-tools", res.Output);
69+
}
70+
71+
[ConditionalTheory(typeof(BuildTestBase), nameof(IsNotUsingWorkloads))]
72+
[InlineData("Debug")]
73+
[InlineData("Release")]
74+
public void AOT_And_NativeRef_FailsBecauseItRequireWorkload(string config)
75+
{
76+
CommandResult res = PublishForRequiresWorkloadTest(config,
77+
extraProperties: "<RunAOTCompilation>true</RunAOTCompilation>",
78+
extraItems: "<NativeFileReference Include=\"native-lib.o\" />");
79+
80+
Assert.NotEqual(0, res.ExitCode);
81+
Assert.Contains("following workloads must be installed: wasm-tools", res.Output);
82+
}
83+
84+
private CommandResult PublishForRequiresWorkloadTest(string config, string extraItems="", string extraProperties="")
85+
{
86+
string id = $"needs_workload_{config}_{Path.GetRandomFileName()}";
87+
InitBlazorWasmProjectDir(id);
88+
89+
new DotNetCommand(s_buildEnv, useDefaultArgs: false)
90+
.WithWorkingDirectory(_projectDir!)
91+
.ExecuteWithCapturedOutput("new blazorwasm")
92+
.EnsureSuccessful();
93+
94+
if (IsNotUsingWorkloads)
95+
{
96+
// no packs installed, so no need to update the paths for runtime pack etc
97+
File.WriteAllText(Path.Combine(_projectDir!, "Directory.Build.props"), "<Project />");
98+
File.WriteAllText(Path.Combine(_projectDir!, "Directory.Build.targets"), "<Project />");
99+
}
100+
101+
AddItemsPropertiesToProject(Path.Combine(_projectDir!, $"{id}.csproj"),
102+
extraProperties: extraProperties,
103+
extraItems: extraItems);
104+
105+
string publishLogPath = Path.Combine(s_buildEnv.LogRootPath, id, $"{id}.binlog");
106+
return new DotNetCommand(s_buildEnv)
107+
.WithWorkingDirectory(_projectDir!)
108+
.ExecuteWithCapturedOutput("publish",
109+
$"-bl:{publishLogPath}",
110+
$"-p:Configuration={config}",
111+
"-p:MSBuildEnableWorkloadResolver=true"); // WasmApp.LocalBuild.* disables this, but it is needed for this test
112+
}
113+
114+
[Theory]
115+
[InlineData("Debug")]
116+
[InlineData("Release")]
117+
public void Net50Projects_NativeReference(string config)
118+
=> BuildNet50Project(config, aot: false, expectError: true, @"<NativeFileReference Include=""native-lib.o"" />");
119+
60120
public static TheoryData<string, bool, bool> Net50TestData = new()
61121
{
62122
{ "Debug", /*aot*/ true, /*expectError*/ true },
@@ -65,51 +125,43 @@ public void PublishTemplateProject(string config, bool aot)
65125
{ "Release", /*aot*/ false, /*expectError*/ false }
66126
};
67127

68-
[ConditionalTheory(typeof(BuildTestBase), nameof(IsNotUsingWorkloads))]
128+
[Theory]
69129
[MemberData(nameof(Net50TestData))]
70-
public void Net50ProjectsWithNoPacksInstalled(string config, bool aot, bool expectError)
71-
=> BuildNet50Project(config, aot, expectError);
130+
public void Net50Projects_AOT(string config, bool aot, bool expectError)
131+
=> BuildNet50Project(config, aot: aot, expectError: expectError);
72132

73-
[ConditionalTheory(typeof(BuildTestBase), nameof(IsUsingWorkloads))]
74-
[MemberData(nameof(Net50TestData))]
75-
public void Net50ProjectsWithPacksInstalled(string config, bool aot, bool expectError)
76-
=> BuildNet50Project(config, aot, expectError);
77-
78-
private void BuildNet50Project(string config, bool aot, bool errorExpected)
133+
private void BuildNet50Project(string config, bool aot, bool expectError, string? extraItems=null)
79134
{
80-
string id = $"Blazor_net50_{config}_{aot}";
81-
InitPaths(id);
82-
if (Directory.Exists(_projectDir))
83-
Directory.Delete(_projectDir, recursive: true);
84-
Directory.CreateDirectory(_projectDir);
85-
Directory.CreateDirectory(Path.Combine(_projectDir, ".nuget"));
135+
string id = $"Blazor_net50_{config}_{aot}_{Path.GetRandomFileName()}";
136+
InitBlazorWasmProjectDir(id);
86137

87138
string directoryBuildTargets = @"<Project>
88139
<Target Name=""PrintAllProjects"" BeforeTargets=""Build"">
89140
<Message Text=""** UsingBrowserRuntimeWorkload: '$(UsingBrowserRuntimeWorkload)'"" Importance=""High"" />
90141
</Target>
91142
</Project>";
92143

93-
File.Copy(Path.Combine(BuildEnvironment.TestDataPath, "nuget6.config"), Path.Combine(_projectDir, "nuget.config"));
94-
File.WriteAllText(Path.Combine(_projectDir, "Directory.Build.props"), "<Project />");
95-
File.WriteAllText(Path.Combine(_projectDir, "Directory.Build.targets"), directoryBuildTargets);
144+
File.WriteAllText(Path.Combine(_projectDir!, "Directory.Build.props"), "<Project />");
145+
File.WriteAllText(Path.Combine(_projectDir!, "Directory.Build.targets"), directoryBuildTargets);
96146

97147
string logPath = Path.Combine(s_buildEnv.LogRootPath, id);
98148
Utils.DirectoryCopy(Path.Combine(BuildEnvironment.TestAssetsPath, "Blazor_net50"), Path.Combine(_projectDir!));
99149

150+
string projectFile = Path.Combine(_projectDir!, "Blazor_net50.csproj");
151+
AddItemsPropertiesToProject(projectFile, extraItems: extraItems);
152+
100153
string publishLogPath = Path.Combine(logPath, $"{id}.binlog");
101154
CommandResult result = new DotNetCommand(s_buildEnv)
102-
.WithWorkingDirectory(_projectDir)
103-
.ExecuteWithCapturedOutput("publish",
104-
$"-bl:{publishLogPath}",
105-
(aot ? "-p:RunAOTCompilation=true" : ""),
106-
$"-p:Configuration={config}");
155+
.WithWorkingDirectory(_projectDir!)
156+
.ExecuteWithCapturedOutput("publish",
157+
$"-bl:{publishLogPath}",
158+
(aot ? "-p:RunAOTCompilation=true" : ""),
159+
$"-p:Configuration={config}");
107160

108-
if (errorExpected)
161+
if (expectError)
109162
{
110163
result.EnsureExitCode(1);
111-
Assert.Contains("** UsingBrowserRuntimeWorkload: 'false'", result.Output);
112-
Assert.Contains("error : WebAssembly workloads (required for AOT) are only supported for projects targeting net6.0+", result.Output);
164+
Assert.Contains("are only supported for projects targeting net6.0+", result.Output);
113165
}
114166
else
115167
{

src/tests/BuildWasmApps/Wasm.Build.Tests/BuildTestBase.cs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
using System.Runtime.InteropServices;
1212
using System.Text;
1313
using System.Text.RegularExpressions;
14+
using System.Xml;
1415
using Xunit;
1516
using Xunit.Abstractions;
1617
using Xunit.Sdk;
@@ -357,6 +358,19 @@ protected static BuildArgs ExpandBuildArgs(BuildArgs buildArgs, string extraProp
357358
}
358359
}
359360

361+
public void InitBlazorWasmProjectDir(string id)
362+
{
363+
InitPaths(id);
364+
if (Directory.Exists(_projectDir))
365+
Directory.Delete(_projectDir, recursive: true);
366+
Directory.CreateDirectory(_projectDir);
367+
Directory.CreateDirectory(Path.Combine(_projectDir, ".nuget"));
368+
369+
File.Copy(Path.Combine(BuildEnvironment.TestDataPath, "nuget6.config"), Path.Combine(_projectDir, "nuget.config"));
370+
File.Copy(Path.Combine(BuildEnvironment.TestDataPath, "Blazor.Directory.Build.props"), Path.Combine(_projectDir, "Directory.Build.props"));
371+
File.Copy(Path.Combine(BuildEnvironment.TestDataPath, "Blazor.Directory.Build.targets"), Path.Combine(_projectDir, "Directory.Build.targets"));
372+
}
373+
360374
static void AssertRuntimePackPath(string buildOutput)
361375
{
362376
var match = s_runtimePackPathRegex.Match(buildOutput);
@@ -601,6 +615,33 @@ void LogData(string label, string? message)
601615
}
602616
}
603617

618+
public static string AddItemsPropertiesToProject(string projectFile, string? extraProperties=null, string? extraItems=null)
619+
{
620+
if (extraProperties == null && extraItems == null)
621+
return projectFile;
622+
623+
XmlDocument doc = new();
624+
doc.Load(projectFile);
625+
626+
if (extraItems != null)
627+
{
628+
XmlNode node = doc.CreateNode(XmlNodeType.Element, "ItemGroup", null);
629+
node.InnerXml = extraItems;
630+
doc.DocumentElement!.AppendChild(node);
631+
}
632+
633+
if (extraProperties != null)
634+
{
635+
XmlNode node = doc.CreateNode(XmlNodeType.Element, "PropertyGroup", null);
636+
node.InnerXml = extraProperties;
637+
doc.DocumentElement!.AppendChild(node);
638+
}
639+
640+
doc.Save(projectFile);
641+
642+
return projectFile;
643+
}
644+
604645
public void Dispose()
605646
{
606647
if (_projectDir != null && _enablePerTestCleanup)

src/tests/BuildWasmApps/Wasm.Build.Tests/NativeLibraryTests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ public NativeLibraryTests(ITestOutputHelper output, SharedBuildPerTestClassFixtu
1717
{
1818
}
1919

20-
[Theory]
20+
[ConditionalTheory(typeof(BuildTestBase), nameof(IsUsingWorkloads))]
2121
[BuildAndRun(aot: false)]
2222
[BuildAndRun(aot: true)]
2323
public void ProjectWithNativeReference(BuildArgs buildArgs, RunHost host, string id)
@@ -48,7 +48,7 @@ public void ProjectWithNativeReference(BuildArgs buildArgs, RunHost host, string
4848
Assert.Contains("from pinvoke: 142", output);
4949
}
5050

51-
[Theory]
51+
[ConditionalTheory(typeof(BuildTestBase), nameof(IsUsingWorkloads))]
5252
[BuildAndRun(aot: false)]
5353
[BuildAndRun(aot: true)]
5454
public void ProjectUsingSkiaSharp(BuildArgs buildArgs, RunHost host, string id)

0 commit comments

Comments
 (0)