Migrate ComputeEndpointsForReferenceStaticWebAssetst to IMultiThreadableTask - #54767
#54767Migrate ComputeEndpointsForReferenceStaticWebAssetst to IMultiThreadableTask#54767OvesN merged 3 commits intomaindotnet/sdk:mainfrom dev/veronikao/migrate-ComputeEndpointsForReferenceStaticWebAssetst-to-mtdotnet/sdk:dev/veronikao/migrate-ComputeEndpointsForReferenceStaticWebAssetst-to-mtCopy head branch name to clipboard
Conversation
There was a problem hiding this comment.
Pull request overview
This PR updates the Static Web Assets MSBuild task ComputeEndpointsForReferenceStaticWebAssets to participate in MSBuild’s multi-threaded task execution model, addressing dotnet/msbuild#11835 by making the task explicitly multi-threadable and environment-aware for path resolution.
Changes:
- Marked
ComputeEndpointsForReferenceStaticWebAssetsas[MSBuildMultiThreadableTask]and implementedIMultiThreadableTask. - Added
TaskEnvironmentsupport and forwarded it intoStaticWebAsset.ToAssetDictionary(...)so asset parsing/path handling uses the MSBuild-provided environment when available.
… migration - Add <inheritdoc/> on TaskEnvironment property to match the ApplyCompressionNegotiation precedent. - Add ComputeEndpointsForReferenceStaticWebAssetsMultiThreadingTest.cs: a focused smoke test that runs the task with TaskEnvironment.ProjectDirectory pointing into <testRoot>/project while the process CWD is set to <testRoot>/decoy/spawn. The asset item is constructed with a relative ContentRoot and Normalize() is deliberately skipped so the env-driven branch in StaticWebAsset.ToAssetDictionary(Assets, TaskEnvironment) actually runs. Deeper env-resolution semantics remain covered by StaticWebAssetTaskEnvironmentTests. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
OvesN
left a comment
There was a problem hiding this comment.
Review
Headline
Production change is correct and MT-safe, but the new test does not exercise the migration — it passes identically whether Execute() calls ToAssetDictionary(Assets, TaskEnvironment) or the pre-PR ToAssetDictionary(Assets). The test gives false regression coverage.
Major: new test does not pin the migration behavior
test/Microsoft.NET.Sdk.StaticWebAssets.Tests/StaticWebAssets/ComputeEndpointsForReferenceStaticWebAssetsMultiThreadingTest.cs
The only production diff is at src/StaticWebAssetsSdk/Tasks/ComputeEndpointsForReferenceStaticWebAssets.cs:27 — passing TaskEnvironment into ToAssetDictionary. The env is plumbed solely into StaticWebAsset.Normalize(env) (StaticWebAsset.cs:1086-1092), which mutates four fields:
ContentRoot = !string.IsNullOrEmpty(ContentRoot) ? NormalizeContentRootPath(ContentRoot, env) : ContentRoot;
BasePath = Normalize(BasePath); // env-independent: pure string Replace+Trim
RelativePath = Normalize(RelativePath, allowEmpyPath:true); // env-independent
RelatedAsset = !string.IsNullOrEmpty(RelatedAsset) ? Path.GetFullPath(env.GetAbsolutePath(RelatedAsset)) : RelatedAsset;In this test, RelatedAsset = "", so only ContentRoot is env-sensitive. But in Execute() (lines 34–76) the dictionary value is consumed for asset.BasePath only (and asset.Identity for a log message). ContentRoot is never observed. The dictionary key is Identity, and the test pre-absolutizes it (projectAbsoluteContentRoot/candidate.js) and uses the same string for candidateEndpoint.AssetFile — so the lookup succeeds regardless of env.
Concrete demonstration. Revert line 27 to var assets = StaticWebAsset.ToAssetDictionary(Assets); and trace:
FromTaskItem(item, TaskEnvironment.Fallback)is called.Normalize(Fallback)setsContentRootto<spawnDir>/wwwroot/(wrong) instead of<projectDir>/wwwroot/.dictionary.Add(asset.Identity, asset)adds the same key the test fixed (absolute).assets.TryGetValue(assetIdentity, out asset)→ hit.RouteHasPathPrefix("candidate.js", "base", …)→ false (pure string).CombineNormalizedPaths("", "base", "candidate.js", '/')→"base/candidate.js".Endpoints[0].ItemSpec == "base/candidate.js"✅ — test still passes.
The wrong ContentRoot is silently materialized in the dictionary value and discarded.
Contrast with MergeConfigurationPropertiesMultiThreadingTest: it feeds a relative MSBuildSourceProjectFile and asserts on task.ProjectConfigurations[0].GetMetadata("Source") == "myRcl", which only succeeds if the env-aware Path.GetFullPath(env.GetAbsolutePath(...)) rooted the path under projectDir. Reverting the env wiring there does flip the test. The new test has no analogous env-dependent assertion.
Suggested test improvement
ComputeEndpointsForReferenceStaticWebAssets.Execute() does not consume any env-sensitive property of the matched asset, so there is no input shape that makes the task's Endpoints[] output depend on env. Any task-level test must therefore assert on intermediate state or be honest about being a smoke test.
Two reasonable options:
-
Move the regression to
StaticWebAsset.ToAssetDictionary(items, env)— that's the actual migration surface and a clean unit. One assertion covers every task that gets migrated this way:var dict = StaticWebAsset.ToAssetDictionary(items, env); dict[absId].ContentRoot.Should().StartWith(projectDir); // not spawnDir
This fails immediately if any caller drops the env argument, and it would also fail if the production change here were reverted.
-
Keep this test as a smoke test and rename the method + comment to reflect that ("task runs to completion with a non-Fallback env and produces the expected route shape"), so future readers don't mistake it for a regression test for the env plumbing.
I'd lean (1).
Minor
MergeConfigurationPropertiesMultiThreadingTest.cs:13-16documents that CWD mutation is safe because[assembly:CollectionBehavior(DisableTestParallelization = true)]is set inLegacyStaticWebAssetsV1IntegrationTest.cs. The new file relies on the same invariant but omits the comment. Not a bug (the attribute is still in place), just makes the safety contract less discoverable.- The trailing "st" in
ComputeEndpointsForReferenceStaticWebAsset**st**(PR title, branch, test class) is a typo carried through. Cosmetic.
Verified
Normalize(env)(StaticWebAsset.cs:1086) touches onlyContentRoot/BasePath/RelativePath/RelatedAsset. Identity is untouched.Identitygetter (StaticWebAsset.cs:86-102) is env-independent for in-memoryStaticWebAssetitems.Execute()body reads onlyasset.BasePathandasset.Identityfrom the dictionary value.CombineNormalizedPaths,RouteHasPathPrefix,StaticWebAssetEndpoint.FromTaskItemare pure — no CWD reads.- MT-safety of
Execute(): locals only, no static cache writes, noEnvironment.CurrentDirectoryor unscopedPath.GetFullPath. Looks correct. [assembly:CollectionBehavior(DisableTestParallelization = true)]is still present.
Fixes dotnet/msbuild#14039
Migrate ComputeEndpointsForReferenceStaticWebAssetst to IMultiThreadableTask