Skip to content

Pass ExcludeRestorePackageImports during restore to avoid redundant evaluations - #14274

Merged
ViktorHofer merged 9 commits into
mainfrom
copilot/consider-passing-excluderestorepackageimports
Jul 9, 2026
Merged

ViktorHofer merged 9 commits into
mainfrom
copilot/consider-passing-excluderestorepackageimports

Conversation

Copilot AI commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Context

During /restore (implicit or /t:restore), every project is evaluated twice. NuGet's restore targets re-invoke each project with the global property ExcludeRestorePackageImports=true (NuGet.targets), which MSBuild does not include in its initial restore evaluation. The differing global property set produces a distinct build configuration, forcing a redundant re-evaluation. Static graph restore already avoids this by passing the property itself.

Changes Made

  • XMake.ExecuteRestore: adds ExcludeRestorePackageImports=true to the restore global properties so NuGet's inner MSBuild call matches the initial evaluation and reuses it. Gated behind change wave 18.10.
  • Value casing is load-bearing: config matching compares global property values case-sensitively (ProjectPropertyInstance.Equals), and the MSBuild task inherits parent globals then overlays its own. NuGet passes the literal lowercase true, so we must too — bool.TrueString ("True") would silently fail to dedupe the evaluation.
  • MSBuildConstants: new ExcludeRestorePackageImports and ExcludeRestorePackageImportsValue ("true") constants.
  • ChangeWaves.md: documented under 18.10.
  • Test: RestoreSetsExcludeRestorePackageImports asserts the property is set during restore.
if (ChangeWaves.AreFeaturesEnabled(ChangeWaves.Wave18_9))
{
    restoreGlobalProperties[MSBuildConstants.ExcludeRestorePackageImports] = MSBuildConstants.ExcludeRestorePackageImportsValue; // "true"
}

Testing

Evaluations counted via ProjectEvaluationStarted events in a binlog, using a repro that mimics NuGet re-invoking each project with ExcludeRestorePackageImports=true:

Scenario Before (18.10 off) After (18.10 on)
/t:Restore (restore phase only) 2 evals/project 1 eval/project
/restore /t:Build (full) 3 evals/project 2 evals/project

All 13 existing *Restore* XMake_Tests pass alongside the new test.

Notes

  • Wall-clock on a trivial single-project repro is unchanged (~0.56s, dominated by process startup); savings scale with project count and per-project evaluation cost in real solutions.
  • Setting the property during restore matches what NuGet already forces in its re-invocation (package imports are excluded during restore anyway), so behavior should be unchanged. The change wave gate provides a per-repo opt-out given this touches restore evaluation.

Copilot AI and others added 3 commits July 6, 2026 21:12
… by change wave 18.9)

Co-authored-by: ViktorHofer <7412651+ViktorHofer@users.noreply.github.com>
…f measurements

Co-authored-by: ViktorHofer <7412651+ViktorHofer@users.noreply.github.com>
Co-authored-by: ViktorHofer <7412651+ViktorHofer@users.noreply.github.com>
@ViktorHofer

ViktorHofer commented Jul 6, 2026

Copy link
Copy Markdown
Member

@jeffkl & @nkolev92 would you mind taking a look at this change? Thanks in advance. EDIT: Happy to react to feedback in a follow-up change.

@ViktorHofer
ViktorHofer requested a review from baronfel July 6, 2026 21:27
Copilot AI changed the title [WIP] Investigate passing ExcludeRestorePackageImports as a restore property Pass ExcludeRestorePackageImports during restore to avoid redundant evaluations Jul 6, 2026
Copilot AI requested a review from ViktorHofer July 6, 2026 21:28
Co-authored-by: ViktorHofer <7412651+ViktorHofer@users.noreply.github.com>
Co-authored-by: ViktorHofer <7412651+ViktorHofer@users.noreply.github.com>
@ViktorHofer
ViktorHofer marked this pull request as ready for review July 6, 2026 21:39
Copilot AI review requested due to automatic review settings July 6, 2026 21:39
Removed duplicate entry for ExcludeRestorePackageImports in Change Waves documentation.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

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 aims to reduce redundant project evaluations during /restore by ensuring MSBuild’s initial restore evaluation uses the same global property (ExcludeRestorePackageImports=true) that NuGet uses when it re-invokes MSBuild, allowing evaluation reuse.

Changes:

  • Sets ExcludeRestorePackageImports=true in XMake.ExecuteRestore restore global properties (wave-gated).
  • Introduces MSBuildConstants.ExcludeRestorePackageImports and a load-bearing lowercase "true" constant value.
  • Adds a unit test asserting the property is set during restore and updates ChangeWaves documentation/rotation.
Show a summary per file
File Description
src/MSBuild/XMake.cs Adds ExcludeRestorePackageImports=true to restore global properties behind a change wave gate.
src/MSBuild.UnitTests/XMake_Tests.cs Adds a test validating ExcludeRestorePackageImports is set during /t:restore.
src/Framework/MSBuildConstants.cs Adds constants for the property name and required lowercase value.
src/Framework/ChangeWaves.cs Adds Wave18_10 and updates AllWaves.
documentation/wiki/ChangeWaves.md Documents the change wave rotation and the new feature entry.

Copilot's findings

  • Files reviewed: 5/5 changed files
  • Comments generated: 2

Comment thread src/MSBuild/XMake.cs Outdated
Comment thread documentation/wiki/ChangeWaves.md Outdated
@ViktorHofer

Copy link
Copy Markdown
Member

/review

@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Expert Code Review (command) completed successfully!

Caution

agentic threat detected
Threat detection flagged this output in warn mode. Manual review is REQUIRED before any follow-up automation.

Details

The threat detection results could not be parsed.

Review the workflow run logs for details.

Evaluating PR #14274 for Performance & Allocation Awareness only.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

agentic threat detected
Threat detection flagged this output in warn mode. Manual review is REQUIRED before any follow-up automation.

Details

The threat detection results could not be parsed.

Review the workflow run logs for details.

Review — PR #14274: Pass ExcludeRestorePackageImports during restore

The optimization goal is sound and the implementation is mostly clean. Two actionable issues need fixing before merge.

# Dimension Verdict
1 Backwards Compatibility 🔴 MAJOR
2 ChangeWave Discipline 🟡 MAJOR
4 Test Coverage 🟡 MAJOR
22 Correctness & Edge Cases 🔴 MAJOR

✅ 22/24 dimensions clean.


Issues to address

  • src/MSBuild/XMake.cs line 2074 — The unconditional dictionary assignment overwrites an explicit user-supplied /p:ExcludeRestorePackageImports=false. Guard the write with !globalProperties.ContainsKey(...) so the optimization is only applied when the user has not already provided a value. (Full recommendation in the inline comment.)

  • src/MSBuild.UnitTests/XMake_Tests.cs — The ChangeWave-disabled path is not tested. A companion test RestoreDoesNotSetExcludeRestorePackageImportsWhenWaveDisabled is needed, setting MSBUILDDISABLEFEATURESFROMVERSION=18.10 and asserting the property is absent from the restore evaluation. (Full snippet in the inline comment.)


Everything else is clean: the "true" casing rationale is correctly documented, Wave18_10 is correctly added to AllWaves, the ChangeWaves.md entry is accurate, constants are internal, there are no concurrency issues, no cross-platform concerns, no API surface changes, and the implementation placement in ExecuteRestore is consistent with the existing MSBuildIsRestoring/MSBuildRestoreSessionId pattern.

Generated by Expert Code Review (command) for #14274 · 1.4K AIC · ⊞ 30.1K
Comment /review to run again

Comment thread src/MSBuild/XMake.cs
Comment thread src/MSBuild.UnitTests/XMake_Tests.cs
@ViktorHofer

Copy link
Copy Markdown
Member

@copilot address the pr feedback

WarperSan pushed a commit to WarperSan/ThunderPipe that referenced this pull request Sep 17, 2026
Updated
[Microsoft.Build.Utilities.Core](https://github.com/dotnet/msbuild) from
18.9.6 to 18.10.1.

<details>
<summary>Release notes</summary>

_Sourced from [Microsoft.Build.Utilities.Core's
releases](https://github.com/dotnet/msbuild/releases)._

## 18.10.1

## What's Changed
* [vs16.11] Update dependencies from dotnet/arcade by
@​dotnet-maestro[bot] in dotnet/msbuild#13103
* [vs17.12] Update dependencies from dotnet/arcade by
@​dotnet-maestro[bot] in dotnet/msbuild#13796
* [vs17.8] Update dependencies from dotnet/arcade by
@​dotnet-maestro[bot] in dotnet/msbuild#13902
* [vs17.11] Update dependencies from dotnet/arcade by
@​dotnet-maestro[bot] in dotnet/msbuild#13903
* [vs17.12] Update dependencies from dotnet/arcade by
@​dotnet-maestro[bot] in dotnet/msbuild#13909
* [vs17.12] Update dependencies from dotnet/arcade by
@​dotnet-maestro[bot] in dotnet/msbuild#13986
* Add vs18.9 to merge-flow config; retire vs18.3 by @​JanProvaznik in
dotnet/msbuild#14214
* Bump labeler-cache-retention to use issue-labeler v2.1.0 by
@​jeffhandley in dotnet/msbuild#14171
* Bump main to 18.10.0 after vs18.9 snap by @​JanProvaznik in
dotnet/msbuild#14216
* Improve release skill: Phase 2 DARC rules, VMR backflow, deterministic
baseline by @​JanProvaznik in
dotnet/msbuild#14220
* Determinize release: hardcode OptProf baseline + Phase 3.2 baseline
resolver by @​JanProvaznik in
dotnet/msbuild#14222
* Serialize BuildRequestConfiguration.RequestedTargets to fix solution
metaproject MSB4057 in parallel builds by @​ViktorHofer in
dotnet/msbuild#14223
* [main] Update dependencies from nuget/nuget.client by
@​dotnet-maestro[bot] in dotnet/msbuild#14203
* Core support for AbsolutePath/FileInfo/DirectoryInfo and ITaskItem<T>
as task parameters by @​baronfel in
dotnet/msbuild#13971
* [main] Update dependencies from dotnet/roslyn by @​dotnet-maestro[bot]
in dotnet/msbuild#14206
* Fix existence cache kind poisoning by @​AlesProkop in
dotnet/msbuild#14249
* [main] Source code updates from dotnet/dotnet by @​dotnet-maestro[bot]
in dotnet/msbuild#14226
* Don't disable the MSBuild server for /mt builds when node reuse is off
by @​AR-May in dotnet/msbuild#14248
* Enhance expert reviewer guidelines with additional checks. by @​AR-May
in dotnet/msbuild#14255
* [main] Source code updates from dotnet/dotnet by @​dotnet-maestro[bot]
in dotnet/msbuild#14253
* [main] Update dependencies from dotnet/roslyn by @​dotnet-maestro[bot]
in dotnet/msbuild#14268
* [main] Update dependencies from nuget/nuget.client by
@​dotnet-maestro[bot] in dotnet/msbuild#14267
* Bump github/gh-aw-actions/setup from 0.81.6 to 0.82.2 by
@​dependabot[bot] in dotnet/msbuild#14266
* Avoid boxing the struct enumerator in
PropertyDictionary<T>.GetEnumerator() by @​nareshjo in
dotnet/msbuild#14272
* Refresh copy marker when implementation output changes by @​AlesProkop
in dotnet/msbuild#14231
* Send task-host build process environment as delta by @​OvesN in
dotnet/msbuild#14126
* Add regression coverage for metadata newline preservation by
@​VolPlita in dotnet/msbuild#14261
* Fix EmbedInBinlog items with relative paths from child projects by
@​huulinhnguyen-dev in dotnet/msbuild#13990
* Stop requiring VersionPrefix updates in servicing - insert prerelease
versions to VS by @​ViktorHofer in
dotnet/msbuild#14277
* Fix WriteLinesToFile rewriting unchanged file when custom encoding is
used by @​huulinhnguyen-dev in
dotnet/msbuild#14146
* Enable trim/AOT analyzers for Microsoft.Build and clean up annotations
by @​JeremyKuhne in dotnet/msbuild#14064
* [automated] Merge branch 'vs18.9' => 'main' by @​github-actions[bot]
in dotnet/msbuild#14291
* Fix MicroBuild plugin feed URL to use allowed pkgs.dev.azure.com
format by @​AlesProkop in dotnet/msbuild#14295
* Pass ExcludeRestorePackageImports during restore to avoid redundant
evaluations by @​ViktorHofer with @​Copilot in
dotnet/msbuild#14274
* [vs18.7] Update dependencies from dotnet/arcade by
@​dotnet-maestro[bot] in dotnet/msbuild#13988
* Adopt Clever Test Selection (CTS) as parallel, non-blocking PR
pipeline by @​jankratochvilcz in
dotnet/msbuild#14212
* Harden exceptions when connecting to server by @​JanProvaznik in
dotnet/msbuild#14292
* Update MicrosoftBuildVersion in analyzer template by
@​github-actions[bot] in dotnet/msbuild#13886
* Fix MSBuild Server client dropping build result under WaitAny race
(#​14172) by @​JanProvaznik in
dotnet/msbuild#14251
* Partially revert #​13660: remove NuGet RestoreTask transient TaskHost
workaround by @​JanProvaznik in
dotnet/msbuild#14297
* Disable daily AI credits guardrail for Expert Code Review workflow by
@​JanProvaznik with @​Copilot in
dotnet/msbuild#14314
* Localized file check-in by OneLocBuild Task: Build definition ID 9434:
Build ID 14614733 by @​dotnet-bot in
dotnet/msbuild#14246
* Add opt-in partial (stop-after-pass) project evaluation by
@​ViktorHofer in dotnet/msbuild#14290
* Use partial evaluation for -getProperty/-getItem without a target by
@​ViktorHofer in dotnet/msbuild#14296
* [main] Source code updates from dotnet/dotnet by @​dotnet-maestro[bot]
in dotnet/msbuild#14324
* [main] Update dependencies from dotnet/roslyn by @​dotnet-maestro[bot]
in dotnet/msbuild#14333
* [main] Update dependencies from nuget/nuget.client by
@​dotnet-maestro[bot] in dotnet/msbuild#14330
* Bump github/gh-aw-actions/setup from 0.82.2 to 0.82.8 by
@​dependabot[bot] in dotnet/msbuild#14328
* Restrict partial evaluation to ProjectInstance by @​ViktorHofer in
dotnet/msbuild#14340
 ... (truncated)

Commits viewable in [compare
view](dotnet/msbuild@v18.9.6...v18.10.1).
</details>

[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=Microsoft.Build.Utilities.Core&package-manager=nuget&previous-version=18.9.6&new-version=18.10.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
This was referenced Sep 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Consider passing ExcludeRestorePackageImports as a restore property in

8 participants