Skip to content

Migrate 5 MSBuild tasks to IMultiThreadableTask (Group 2: Bundle, Deps, Config & Hosts)#52936

Closed
SimaTian wants to merge 15 commits intodotnet:mainfrom
SimaTian:merge-group-2
Closed

Migrate 5 MSBuild tasks to IMultiThreadableTask (Group 2: Bundle, Deps, Config & Hosts)#52936
SimaTian wants to merge 15 commits intodotnet:mainfrom
SimaTian:merge-group-2

Conversation

@SimaTian
Copy link
Copy Markdown
Member

Migrate 5 MSBuild tasks to IMultiThreadableTask (Group 2: Bundle, Deps, Config & Hosts)

Context

This PR migrates 5 MSBuild tasks to support multithreaded execution by implementing IMultiThreadableTask.

Dependency: This PR depends on #52909 (polyfills PR) which adds the IMultiThreadableTask interface, TaskEnvironment, and AbsolutePath polyfills. The diff will reduce once that PR is merged.

Migrated Tasks

Task Changes
GenerateBundle Absolutize OutputDir and FilesToBundle[].ItemSpec via TaskEnvironment.GetAbsolutePath()
GenerateDepsFile Absolutize DepsFilePath before File.Create()
WriteAppConfigWithSupportedRuntime Absolutize OutputAppConfigFile.ItemSpec and input app.config path
ResolveAppHosts Absolutize RuntimeGraphPath and TargetingPackRoot
ShowPreviewMessage Attribute-only (no path I/O)

Changes Per Task

Each task receives:

  • [MSBuildMultiThreadableTask] attribute
  • IMultiThreadableTask interface implementation
  • TaskEnvironment property
  • Forbidden API replacements (relative path → TaskEnvironment.GetAbsolutePath())

New Test Files

  • GivenAGenerateBundleMultiThreading.cs
  • GivenAGenerateDepsFileMultiThreading.cs
  • GivenAWriteAppConfigWithSupportedRuntimeMultiThreading.cs
  • GivenAResolveAppHostsMultiThreading.cs
  • GivenAShowPreviewMessageMultiThreading.cs

Testing

  • All multithreading unit tests pass
  • No regressions in existing tests

Copilot AI review requested due to automatic review settings February 10, 2026 13:14
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR updates a set of SDK MSBuild tasks to participate in MSBuild’s multithreaded task execution model by implementing IMultiThreadableTask, wiring in TaskEnvironment, and replacing relative-path I/O with TaskEnvironment.GetAbsolutePath(...). It also adds unit-test coverage and introduces .NET Framework-gated polyfills for the new MSBuild APIs.

Changes:

  • Migrate 4 tasks to IMultiThreadableTask + TaskEnvironment and absolutize relevant paths (GenerateBundle, GenerateDepsFile, WriteAppConfigWithSupportedRuntime, ResolveAppHosts).
  • Mark ShowPreviewMessage as multithreadable via [MSBuildMultiThreadableTask].
  • Add unit tests + a TaskEnvironmentHelper for constructing TaskEnvironment instances in tests, plus .NET Framework-gated polyfill implementations.

Reviewed changes

Copilot reviewed 17 out of 17 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/Tasks/Microsoft.NET.Build.Tasks/GenerateBundle.cs Implements IMultiThreadableTask and absolutizes OutputDir/bundle inputs via TaskEnvironment.
src/Tasks/Microsoft.NET.Build.Tasks/GenerateDepsFile.cs Implements IMultiThreadableTask and absolutizes DepsFilePath before writing.
src/Tasks/Microsoft.NET.Build.Tasks/WriteAppConfigWithSupportedRuntime.cs Implements IMultiThreadableTask and absolutizes input/output app.config paths.
src/Tasks/Microsoft.NET.Build.Tasks/ResolveAppHosts.cs Implements IMultiThreadableTask and absolutizes runtime graph + targeting pack root usage.
src/Tasks/Microsoft.NET.Build.Tasks/ShowPreviewMessage.cs Adds [MSBuildMultiThreadableTask] attribute.
src/Tasks/Microsoft.NET.Build.Tasks.UnitTests/GivenAGenerateBundleMultiThreading.cs Adds tests asserting interface/attribute and a smoke path-resolution scenario.
src/Tasks/Microsoft.NET.Build.Tasks.UnitTests/GivenAGenerateDepsFileMultiThreading.cs Adds interface/attribute/property presence tests.
src/Tasks/Microsoft.NET.Build.Tasks.UnitTests/GivenAWriteAppConfigWithSupportedRuntimeMultiThreading.cs Adds tests validating TaskEnvironment-based input/output path resolution.
src/Tasks/Microsoft.NET.Build.Tasks.UnitTests/GivenAResolveAppHostsMultiThreading.cs Adds test validating TargetingPackRoot resolution via TaskEnvironment.
src/Tasks/Microsoft.NET.Build.Tasks.UnitTests/GivenAShowPreviewMessageMultiThreading.cs Adds test validating [MSBuildMultiThreadableTask] presence.
src/Tasks/Microsoft.NET.Build.Tasks.UnitTests/TaskEnvironmentHelper.cs Adds helper for constructing TaskEnvironment via reflection/DispatchProxy.
src/Tasks/Microsoft.NET.Build.Tasks.UnitTests/TaskEnvironmentHelperTests.cs Adds smoke tests for helper behavior and path resolution.
src/Tasks/Common/IMultiThreadableTask.cs Adds .NET Framework-gated polyfill for IMultiThreadableTask.
src/Tasks/Common/TaskEnvironment.cs Adds .NET Framework-gated polyfill for TaskEnvironment.
src/Tasks/Common/AbsolutePath.cs Adds .NET Framework-gated polyfill for AbsolutePath.
src/Tasks/Common/ITaskEnvironmentDriver.cs Adds .NET Framework-gated polyfill for the internal driver interface.
src/Tasks/Common/ProcessTaskEnvironmentDriver.cs Adds .NET Framework-gated polyfill driver implementation.

Comment thread src/Tasks/Common/TaskEnvironment.cs Outdated
Comment thread src/Tasks/Common/AbsolutePath.cs Outdated
Copy link
Copy Markdown
Member Author

@SimaTian SimaTian left a comment

Choose a reason for hiding this comment

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

Addressed all review comments:

  1. TaskEnvironment internal -> public: Fixed in the polyfills PR (#52909). \TaskEnvironment\ is now \public sealed class.

  2. AbsolutePath internal -> public: Fixed in the polyfills PR (#52909). \AbsolutePath\ is now \public readonly struct.

  3. GenerateBundle test assertion: Added \NotBeOfType\ assertion to verify that OutputDir is absolutized via TaskEnvironment rather than used as a relative path.

Branch rebased on updated polyfills and force-pushed.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 17 out of 17 changed files in this pull request and generated 3 comments.

Comment thread src/Tasks/Microsoft.NET.Build.Tasks/ResolveAppHosts.cs Outdated
Comment thread src/Tasks/Microsoft.NET.Build.Tasks/GenerateDepsFile.cs Outdated
@SimaTian
Copy link
Copy Markdown
Member Author

Deep Audit: Forbidden API Usage in Helper Functions (Group 2)

Traced all helper functions and referenced libraries called by the 5 Group 2 tasks for hidden forbidden API usage that bypasses the TaskEnvironment migration.


🔴 CRITICAL: FrameworkReferenceResolver.GetDefaultReferenceAssembliesPath() uses Environment.GetEnvironmentVariable

Called by: GenerateDepsFile.WriteDepsFile()DependencyContextBuilder.WithReferenceAssembliesPath(FrameworkReferenceResolver.GetDefaultReferenceAssembliesPath())

Source: FrameworkReferenceResolver.cs

public static string GetDefaultReferenceAssembliesPath()
{
    var referenceAssembliesPath = DotNetReferenceAssembliesPathResolver.Resolve();
    // ...
    var programFiles = Environment.GetEnvironmentVariable(""ProgramFiles(x86)"");
    if (string.IsNullOrEmpty(programFiles))
    {
        programFiles = Environment.GetEnvironmentVariable(""ProgramFiles"");
    }
    // ...
}

This static method directly calls Environment.GetEnvironmentVariable — a forbidden API. Per the migration guide, this should use TaskEnvironment.GetEnvironmentVariable(). Additionally, DotNetReferenceAssembliesPathResolver.Resolve() (external NuGet library) likely also reads environment variables internally.

Impact: Medium-high. In multithreaded execution, Environment.GetEnvironmentVariable reads from process-global state, which could return stale or wrong values if another thread has modified the environment.

Fix needed: FrameworkReferenceResolver.GetDefaultReferenceAssembliesPath() needs to accept a TaskEnvironment parameter and use it for env var access. Since it's a static utility shared across tasks, this is a cross-cutting change.


🔴 CRITICAL: ResolvedFile constructor uses Path.GetFullPath()

Called by: GenerateDepsFile.WriteDepsFile()new ResolvedFile(f, false) and new ResolvedFile(f, true)

Source: ResolvedFile.cs (4-arg constructor, not the ITaskItem constructor):

public ResolvedFile(string sourcePath, string destinationSubDirectory, PackageIdentity package, ...)
{
    SourcePath = Path.GetFullPath(sourcePath);  // ← FORBIDDEN API
    // ...
}

The 4-arg constructor calls Path.GetFullPath() directly. However, in GenerateDepsFile, the 2-arg constructor ResolvedFile(ITaskItem item, bool isRuntimeTarget) is used, which does not call Path.GetFullPath(). So this is a latent issue rather than an active bug for this task — but other tasks using the 4-arg constructor would be affected.

Impact for GenerateDepsFile: Low (not hit). But ResolvedFile should be migrated as part of a utility sweep.


🟡 WARNING: GenerateDepsFileAssetsFilePath and RuntimeGraphPath NOT absolutized

Already flagged in PR review, confirming via deep trace:

  1. AssetsFilePath is passed directly to LockFileCache.GetLockFile(AssetsFilePath) without absolutization. LockFileCache has a guard (Path.IsPathRooted check and throws), so it won't silently fail — but it will throw if a relative path is provided instead of resolving it correctly.

  2. RuntimeGraphPath is passed directly to RuntimeGraphCache.GetRuntimeGraph(RuntimeGraphPath) without absolutization. Same guard exists there.

  3. ProjectPath is passed to SingleProjectInfo.Create(ProjectPath, ...) — this just stores the string without file I/O, so no immediate issue.

Good news: Both LockFileCache and RuntimeGraphCache have defensive Path.IsPathRooted() guards that throw on relative paths, preventing silent CWD-dependent behavior. But the correct fix is to absolutize at the task level via TaskEnvironment.GetAbsolutePath().


🟡 WARNING: GenerateDepsFileDotNetReferenceAssembliesPathResolver.Resolve()

This is from the Microsoft.Extensions.DependencyModel NuGet package. It likely reads DOTNET_REFERENCE_ASSEMBLIES_PATH environment variable internally. Since it's an external library, it can't be directly migrated, but should be documented as a known limitation.


GenerateBundle — CLEAN

All paths absolutized correctly:

  • OutputDirTaskEnvironment.GetAbsolutePath(OutputDir)
  • FilesToBundle[].ItemSpecTaskEnvironment.GetAbsolutePath(item.ItemSpec)

The Bundler class (from Microsoft.NET.HostModel) receives already-absolutized paths. No forbidden APIs in the task's own code path.


WriteAppConfigWithSupportedRuntime — CLEAN

Both file I/O paths absolutized:

  • OutputAppConfigFile.ItemSpecTaskEnvironment.GetAbsolutePath(...) before new FileStream(...)
  • AppConfigFile.ItemSpecTaskEnvironment.GetAbsolutePath(...) before XDocument.Load(...)

Helper methods (AddSupportedRuntimeToAppconfig, TryGetSupportRuntimeNode) are pure XML manipulation with no file I/O.


ResolveAppHosts — CLEAN

All paths absolutized:

  • RuntimeGraphPathTaskEnvironment.GetAbsolutePath(RuntimeGraphPath) before RuntimeGraphCache.GetRuntimeGraph()
  • TargetingPackRootTaskEnvironment.GetAbsolutePath(TargetingPackRoot) before Path.Combine + Directory.Exists

RuntimeGraphCache.GetRuntimeGraph() has a Path.IsPathRooted() guard — receives already-rooted path. Helper methods (GetHostItem, ProcessFrameworkReferences.NormalizeVersion) are pure computation.


ShowPreviewMessage — CLEAN (Pattern A)

No file I/O, no environment access, no forbidden APIs. Uses only BuildEngine4 task object registration.


Summary

Task Status Issues
GenerateBundle ✅ Clean None
GenerateDepsFile 🔴 Issues FrameworkReferenceResolver uses Environment.GetEnvironmentVariable; AssetsFilePath/RuntimeGraphPath not absolutized; ResolvedFile ctor has Path.GetFullPath (not hit in this path)
WriteAppConfig ✅ Clean None
ResolveAppHosts ✅ Clean None
ShowPreviewMessage ✅ Clean None

Recommended actions:

  1. GenerateDepsFile: Absolutize AssetsFilePath and RuntimeGraphPath before passing to helpers
  2. FrameworkReferenceResolver: Needs TaskEnvironment parameter (cross-cutting — affects multiple tasks)
  3. ResolvedFile 4-arg ctor: Replace Path.GetFullPath with caller-side absolutization (latent, not active in this path)

@SimaTian
Copy link
Copy Markdown
Member Author

Fixes applied (248cb91)

Resolved the forbidden API issues identified in the deep audit:

1. GenerateDepsFileAssetsFilePath and RuntimeGraphPath now absolutized

Added TaskEnvironment.GetAbsolutePath() calls in ExecuteCore() before these paths reach LockFileCache and RuntimeGraphCache helpers:

if (AssetsFilePath != null)
{
    AssetsFilePath = TaskEnvironment.GetAbsolutePath(AssetsFilePath);
}
RuntimeGraphPath = TaskEnvironment.GetAbsolutePath(RuntimeGraphPath);

2. FrameworkReferenceResolver.GetDefaultReferenceAssembliesPath() — now accepts TaskEnvironment

Added a new overload that uses TaskEnvironment.GetEnvironmentVariable() instead of Environment.GetEnvironmentVariable():

  • GetDefaultReferenceAssembliesPath() — backward-compatible, delegates to overload with null
  • GetDefaultReferenceAssembliesPath(TaskEnvironment) — uses TaskEnvironment when provided

GenerateDepsFile now calls the TaskEnvironment-aware overload.

3. ResolvedFile 4-arg constructor Path.GetFullPathnot changed (documented)

This is a latent issue only. The constructor is called by AssetsFileResolver with paths from Path.Combine(absoluteLibraryPath, relativePart) — always producing absolute paths. Path.GetFullPath acts as canonicalization only (no CWD dependency). Changing it would require threading TaskEnvironment through AssetsFileResolver and all its callers — a larger refactor best handled in a separate PR.

Test results

  • 231 passed, 11 failed (same pre-existing ToolsetInfo failures, unrelated)
  • No regressions

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 18 out of 18 changed files in this pull request and generated 8 comments.

Comment thread src/Tasks/Microsoft.NET.Build.Tasks/GenerateDepsFile.cs Outdated
Comment thread src/Tasks/Microsoft.NET.Build.Tasks/ResolveAppHosts.cs Outdated
Comment thread src/Tasks/Microsoft.NET.Build.Tasks/GenerateBundle.cs Outdated
@SimaTian SimaTian self-assigned this Feb 11, 2026
@SimaTian SimaTian force-pushed the merge-group-2 branch 5 times, most recently from 391a30e to 56f7474 Compare February 22, 2026 15:01
Comment thread src/Tasks/Microsoft.NET.Build.Tasks/FrameworkReferenceResolver.cs Outdated
Comment thread src/Tasks/Microsoft.NET.Build.Tasks/FrameworkReferenceResolver.cs Outdated
Comment thread src/Tasks/Microsoft.NET.Build.Tasks/FrameworkReferenceResolver.cs Outdated
Copy link
Copy Markdown
Member Author

@SimaTian SimaTian left a comment

Choose a reason for hiding this comment

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

As good as I can force the copilot to make it.
There is one open question - what do we do about the Runtime dependency which accesses Environment Variable directly? Is it safe enough to risk it or do we need to clone the method?

@SimaTian SimaTian requested a review from a team as a code owner February 24, 2026 09:13
SimaTian and others added 5 commits March 30, 2026 10:13
…for merge-group-2

- Guard NotBeOfType<NullReferenceException> with null check (FluentAssertions
  fails on null subject even when no exception was thrown)
- Add TaskEnvironmentDefaults.cs for NETFRAMEWORK lazy-init fallback
- Apply lazy-init pattern to WriteAppConfigWithSupportedRuntime, GenerateDepsFile,
  ResolveAppHosts, GenerateBundle

Co-authored-by: Copilot <[email protected]>
The combining constructor AbsolutePath(string, AbsolutePath) used
Path.Combine without Path.GetFullPath, leaving '..' segments
unresolved. This caused output paths like 'dir\..\ClassLib\...'
instead of 'ClassLib\...', breaking string-based path comparisons
in downstream MSBuild targets and tests.

Co-authored-by: Copilot <[email protected]>
- Revert AbsolutePath.cs: remove Path.GetFullPath wrapping (matching real MSBuild)
- Add UseShellExecute=false in ProcessTaskEnvironmentDriver.GetProcessStartInfo

Co-authored-by: Copilot <[email protected]>
Production changes:
- GenerateBundle: OutputDir null/empty guard (RC1), absolutize item.ItemSpec
- GenerateDepsFile: absolutize ProjectPath (RC3), AssetsFilePath, RuntimeGraphPath
- FrameworkReferenceResolver: static->instance with Func<string,string> delegate (C1)
- ResolveAppHosts: field pattern for RuntimeGraphPath + TargetingPackRoot
- WriteAppConfig: absolutize OutputAppConfigFile + appConfigItem paths
- ShowPreviewMessage: attribute-only (no interface/property)

Test improvements:
- Dedicated test files per task (replacing monolithic GivenTasksUseAbsolutePaths)
- [Collection(CWD-Dependent)] for CWD-mutating tests
- result.Should().BeTrue() instead of if(result) guards (F3)
- AssetsFilePath absolutization test with minimal project.assets.json (F1)
- TargetingPackRoot + empty TargetingPackRoot tests (F2)
- Removed old GivenAWriteAppConfigWithSupportedRuntimeMultiThreading.cs

Co-authored-by: Copilot <[email protected]>
…ectory

Empty OutputDir is handled by falling back to TaskEnvironment.ProjectDirectory
(matching the old behavior of using Environment.CurrentDirectory). The test
should not assert that an exception is thrown during path resolution.

Co-authored-by: Copilot <[email protected]>
Replace DotNetReferenceAssembliesPathResolver.Resolve() with
_getEnvironmentVariable("DOTNET_REFERENCE_ASSEMBLIES_PATH"). The static
library method used process-global Environment.GetEnvironmentVariable,
bypassing the injected delegate entirely. This defeats the refactoring
that was specifically introduced for multithreaded MSBuild task support.

Add GivenAFrameworkReferenceResolver tests verifying the delegate is used.

Co-authored-by: Copilot <[email protected]>
SimaTian and others added 2 commits March 30, 2026 13:36
… restore

- GenerateDepsFile: use IsNullOrEmpty guard (not just null) for
  AssetsFilePath and RuntimeGraphPath before absolutizing
- ResolveAppHosts: guard null/empty RuntimeGraphPath before absolutizing
- GivenTasksUseAbsolutePaths: add TaskEnvironment + RuntimeGraphPath to
  ResolveAppHosts_NoFileIO test
- GivenAGenerateDepsFileMultiThreading: restore CWD to Path.GetTempPath()
  instead of savedCwd to avoid race with parallel test cleanup

Co-authored-by: Copilot <[email protected]>
On macOS, GetCurrentDirectory() throws FileNotFoundException when CWD
was deleted by a parallel test. Remove all savedCwd variables from
GivenAGenerateDepsFileMultiThreading (already using Path.GetTempPath()
for restore). Add try-catch fallback in TaskTestEnvironment for both
save and restore operations.

Co-authored-by: Copilot <[email protected]>
Copy link
Copy Markdown
Member

@AR-May AR-May left a comment

Choose a reason for hiding this comment

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

There are couple of problematic places in the tasks still.

}

private void WriteDepsFile(string depsFilePath)
private void WriteDepsFile(string depsFilePath, string absoluteProjectPath, string absoluteAssetsFilePath, string absoluteRuntimeGraphPath)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Although this seems correct, it’s not very clean. There’s no reason to compute the absolute paths outside this function.

string absoluteProjectPath = TaskEnvironment.GetAbsolutePath(ProjectPath);
string absoluteAssetsFilePath = !string.IsNullOrEmpty(AssetsFilePath) ? (string)TaskEnvironment.GetAbsolutePath(AssetsFilePath) : null;
string absoluteRuntimeGraphPath = !string.IsNullOrEmpty(RuntimeGraphPath) ? (string)TaskEnvironment.GetAbsolutePath(RuntimeGraphPath) : null;
WriteDepsFile(absoluteDepsFilePath, absoluteProjectPath, absoluteAssetsFilePath, absoluteRuntimeGraphPath);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Passing the absoluteDepsFilePath here loses the initial data about the potentially relative path. This results in FilesWritten being absolute path instead of potentially relative. This is a behavioral change, as this is the task output. To avoid it, prefer makingAbsolutePath objects and not casting them back to string. Rather make functions take AbsolutePath objects. This way you can access the initial potentially relative path via OriginalValue field and make correct output items. Also use the original value in logging.

I instead suggest backing the property by the AbsolutePath object and using it in the code. Like it is done here.


protected override void ExecuteCore()
{
_absoluteRuntimeGraphPath = !string.IsNullOrEmpty(RuntimeGraphPath)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Consider backing the property with AbsolutePath objects.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

problem: Absolute path goes to this metadata and changes the output of the task from relative to absolute paths, this a behavioral change. See #52936 (comment). We need to use the absolute path only in file io, not allow it to leak to outputs when it was not there.

…puts

- GenerateDepsFile: move absolutization from ExecuteCore into WriteDepsFile
- ResolveAppHosts: use AbsolutePath? fields, preserve original TargetingPackRoot
  for SetMetadata output (fixes behavioral change where metadata became absolute)
- Update tests: verify output metadata preserves relative path form

Addresses @AR-May review comments on PR dotnet#52936.

Co-authored-by: Copilot <[email protected]>
Same pattern as prior DepsFile fix: wrap Directory.SetCurrentDirectory(savedCwd)
in try-catch with Path.GetTempPath() fallback. On macOS, parallel tests can
delete the captured CWD directory, causing DirectoryNotFoundException.

Co-authored-by: Copilot <[email protected]>
SimaTian added a commit to SimaTian/sdk that referenced this pull request Apr 17, 2026
Implements IMultiThreadableTask to enable safe multi-threaded execution:
- Added [MSBuildMultiThreadableTask] attribute
- Added TaskEnvironment property with NETFRAMEWORK polyfill
- Absolutize ProjectPath at task level before passing to DependencyContextBuilder
- Moved AssetsFilePath and RuntimeGraphPath absolutization into WriteDepsFile
- Use AbsolutePath.OriginalValue for FilesWritten output to preserve relativity

Addresses all 3 AR-May findings from PR dotnet#52936:
1. ProjectPath absolutization: Now absolutized at ExecuteCore level via TaskEnvironment.GetAbsolutePath,
   passes .Value (absolute) to DependencyContextBuilder
2. Cleanliness: AssetsFilePath and RuntimeGraphPath absolutization moved into WriteDepsFile where consumed
3. Output fidelity: FilesWritten uses AbsolutePath.OriginalValue to preserve user-supplied relative paths

Added comprehensive behavioral tests in GivenAGenerateDepsFileMultiThreading.cs:
- OutputRelativityIsPreserved: Verifies FilesWritten preserves relative paths
- ProjectPathIsAbsolutized: Ensures ProjectPath is absolutized before use
- DecoyCwdDoesNotAffectPathResolution: Validates CWD independence
- CrossThreadEnvVarIsolation: Tests environment variable isolation
- MultiProcessParity: Confirms identical output with/without TaskEnvironment
- ConcurrentExecutionProducesCorrectResults: Parallel execution test

Co-authored-by: Copilot <[email protected]>
@SimaTian
Copy link
Copy Markdown
Member Author

Closing this PR — the 5 tasks have been split into individual PRs for independent review and merge:

Each split addresses the AR-May review findings from this merge group (ProjectPath absolutization, output-path relativity via AbsolutePath.OriginalValue, cross-thread isolation) and has been through an adversarial per-branch code-review pass before being opened.

@SimaTian SimaTian closed this Apr 17, 2026
SimaTian added a commit to SimaTian/sdk that referenced this pull request Apr 29, 2026
Implements IMultiThreadableTask to enable safe multi-threaded execution:
- Added [MSBuildMultiThreadableTask] attribute
- Added TaskEnvironment property with NETFRAMEWORK polyfill
- Absolutize ProjectPath at task level before passing to DependencyContextBuilder
- Moved AssetsFilePath and RuntimeGraphPath absolutization into WriteDepsFile
- Use AbsolutePath.OriginalValue for FilesWritten output to preserve relativity

Addresses all 3 AR-May findings from PR dotnet#52936:
1. ProjectPath absolutization: Now absolutized at ExecuteCore level via TaskEnvironment.GetAbsolutePath,
   passes .Value (absolute) to DependencyContextBuilder
2. Cleanliness: AssetsFilePath and RuntimeGraphPath absolutization moved into WriteDepsFile where consumed
3. Output fidelity: FilesWritten uses AbsolutePath.OriginalValue to preserve user-supplied relative paths

Added comprehensive behavioral tests in GivenAGenerateDepsFileMultiThreading.cs:
- OutputRelativityIsPreserved: Verifies FilesWritten preserves relative paths
- ProjectPathIsAbsolutized: Ensures ProjectPath is absolutized before use
- DecoyCwdDoesNotAffectPathResolution: Validates CWD independence
- CrossThreadEnvVarIsolation: Tests environment variable isolation
- MultiProcessParity: Confirms identical output with/without TaskEnvironment
- ConcurrentExecutionProducesCorrectResults: Parallel execution test

Co-authored-by: Copilot <[email protected]>
SimaTian added a commit to SimaTian/sdk that referenced this pull request Apr 29, 2026
Implements IMultiThreadableTask to enable safe multi-threaded execution:
- Added [MSBuildMultiThreadableTask] attribute
- Added TaskEnvironment property with NETFRAMEWORK polyfill
- Absolutize ProjectPath at task level before passing to DependencyContextBuilder
- Moved AssetsFilePath and RuntimeGraphPath absolutization into WriteDepsFile
- Use AbsolutePath.OriginalValue for FilesWritten output to preserve relativity

Addresses all 3 AR-May findings from PR dotnet#52936:
1. ProjectPath absolutization: Now absolutized at ExecuteCore level via TaskEnvironment.GetAbsolutePath,
   passes .Value (absolute) to DependencyContextBuilder
2. Cleanliness: AssetsFilePath and RuntimeGraphPath absolutization moved into WriteDepsFile where consumed
3. Output fidelity: FilesWritten uses AbsolutePath.OriginalValue to preserve user-supplied relative paths

Added comprehensive behavioral tests in GivenAGenerateDepsFileMultiThreading.cs:
- OutputRelativityIsPreserved: Verifies FilesWritten preserves relative paths
- ProjectPathIsAbsolutized: Ensures ProjectPath is absolutized before use
- DecoyCwdDoesNotAffectPathResolution: Validates CWD independence
- CrossThreadEnvVarIsolation: Tests environment variable isolation
- MultiProcessParity: Confirms identical output with/without TaskEnvironment
- ConcurrentExecutionProducesCorrectResults: Parallel execution test

Co-authored-by: Copilot <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants