Skip to content

Migrate GetRestoreSettingsTask to the multithreadable task model#7543

Open
OvesN wants to merge 10 commits into
NuGet:devfrom
OvesN:veronikao/migrate-GetRestoreSettingsTask-to-mt
Open

Migrate GetRestoreSettingsTask to the multithreadable task model#7543
OvesN wants to merge 10 commits into
NuGet:devfrom
OvesN:veronikao/migrate-GetRestoreSettingsTask-to-mt

Conversation

@OvesN

@OvesN OvesN commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Bug

Fixes: dotnet/msbuild#14186

Description

Migrates GetRestoreSettingsTask to MSBuild's multithreaded (in-process) task model so it runs safely in parallel builds.

  • Implements IMultiThreadableTask / [MSBuildMultiThreadableTask] (net472 uses a polyfill attribute) and absolutizes path inputs through TaskEnvironment, so path resolution no longer depends on the process working directory.
  • UriUtility.GetAbsolutePath now anchors resolution to the root directory on .NET, fixing a current-directory/drive leak for rooted-but-relative paths such as C: or \foo.
  • Adds tests for per-instance path resolution and working-directory independence.

PR Checklist

  • Meaningful title, helpful description and a linked NuGet/Home issue
  • Added tests
  • Link to an issue or pull request to update docs if this PR changes settings, environment variables, new feature, etc.

OvesN and others added 7 commits July 1, 2026 01:58
…rk build

The [MSBuildMultiThreadableTask] marker attribute only exists in Microsoft.Build.Framework
18.6 and later. The .NET (SDK) build of NuGet's MSBuild tasks gets it from the package, but
the .NET Framework build compiles against the in-box MSBuild reference assemblies, which do
not contain the type. This adds a self-defined compatibility shim (guarded by #if NETFRAMEWORK,
in the Microsoft.Build.Framework namespace) so the shared task source can apply the marker
without conditional compilation at every task. MSBuild detects the attribute by namespace and
name only, so the shim is recognized by 18.6+ hosts and ignored by older ones.

This is a prerequisite for migrating relative-path-resolving restore tasks to the
multithreadable execution model.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Mark the task with [MSBuildMultiThreadableTask] and, on the .NET (SDK) build, implement
IMultiThreadableTask so MSBuild can run it in-process during multithreaded builds (dotnet build -mt).

Add a MakeAbsolute helper that anchors relative directory inputs to the project directory via
TaskEnvironment (guarded for null/empty, which GetAbsolutePath throws on). It is applied to the
directory inputs that are NOT reserved-guaranteed absolute and are used as path-resolution bases:
RestoreSolutionDirectory (= $(SolutionDir), user-overridable) and RestoreRootConfigDirectory.
MSBuildStartupDirectory and ProjectUniqueName are reserved MSBuild values that are always absolute,
so no separate absolutization is required for them. On .NET Framework MakeAbsolute is a pass-through,
preserving the existing behavior.

Caveat: command-line override paths (RestoreConfigFile, RestorePackagesPathOverride,
RestoreRepositoryPathOverride) flow through the shared UriUtility.GetAbsolutePath, whose
Path.GetFullPath(local) / Path.Combine behavior can still consult the current directory/drive for
rooted or drive-relative inputs such as "\foo" or "C:". That is pre-existing UriUtility behavior
(identical on .NET Framework), affects only pathological inputs, and is out of scope for this task.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Rename the local variables (local -> localPath, relativeUri -> relativeUriReference,
absoluteUri -> absoluteUriReference) and rewrite the comments to describe what the method actually
does: it distinguishes a local filesystem path from a remote source URL. A value with no URI scheme
parses as a "relative" reference - which covers every local path, including rooted ones such as
C:\x, \foo and \\server\share - and is resolved against the root directory; values with a scheme
(http://, https://, ...) are remote URLs and are returned unchanged. Comment/rename only; no
behavior change.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@OvesN OvesN requested a review from a team as a code owner July 7, 2026 08:56
@OvesN OvesN requested review from martinrrm and zivkan July 7, 2026 08:56
@dotnet-policy-service dotnet-policy-service Bot added the Community PRs created by someone not in the NuGet team label Jul 7, 2026
@OvesN OvesN marked this pull request as draft July 7, 2026 17:02
@OvesN OvesN marked this pull request as ready for review July 8, 2026 07:31
@OvesN

OvesN commented Jul 8, 2026

Copy link
Copy Markdown
Contributor Author

@nkolev92 @zivkan ptal

@jankratochvilcz jankratochvilcz left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

(AI review, results were not that good)

Reviewed with two automated passes: an MT-migration call-chain review and an MSBuild code review. Overall the migration is correct and net472-safe — every path input reaching Execute() is absolutized before hitting a filesystem API, the netcore-only APIs are correctly #if-guarded, and the TaskEnvironment.Fallback default is right. Inline notes below; none are hard blockers.

Minor (no inline, line unchanged): the #if DEBUG read of DEBUG_RESTORE_SETTINGS_TASK still goes through _environmentVariableReader (process env) rather than TaskEnvironment.GetEnvironmentVariable — negligible since it's a debugger-attach flag.

@jankratochvilcz jankratochvilcz left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Left two smaller comments (the AI reviews on this repo seem not that great with our skills compared to running them in MSBuild / SDK 😢)

/// Collection for tests that mutate the process-wide current directory.
/// </summary>
[CollectionDefinition(nameof(UriUtilityCurrentDirectoryCollection), DisableParallelization = true)]
public class UriUtilityCurrentDirectoryCollection

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Should we add a test that actually tests when we pass absolute paths?

[InlineData("nested/../child")]
[InlineData("./relative")]
[InlineData("https://api.nuget.org/v3/index.json")]
public void UriUtility_GetAbsolutePath_WithAbsoluteRoot_IsIndependentOfCurrentDirectory(string source)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

(nit) - personally, I find these tests hard to read as we're not testing against a static expected value - I think it's good if assertions themselves are trivial as it makes it easier to review that the test is asserting what we expect.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Community PRs created by someone not in the NuGet team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Multithreaded] Migrate GetRestoreSettingsTask in NuGet

4 participants