Pass ExcludeRestorePackageImports during restore to avoid redundant evaluations - #14274
Conversation
… 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>
Co-authored-by: ViktorHofer <7412651+ViktorHofer@users.noreply.github.com>
Co-authored-by: ViktorHofer <7412651+ViktorHofer@users.noreply.github.com>
Removed duplicate entry for ExcludeRestorePackageImports in Change Waves documentation.
There was a problem hiding this comment.
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=trueinXMake.ExecuteRestorerestore global properties (wave-gated). - Introduces
MSBuildConstants.ExcludeRestorePackageImportsand 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
|
/review |
|
✅ Expert Code Review (command) completed successfully! Caution agentic threat detected Evaluating PR #14274 for Performance & Allocation Awareness only. |
There was a problem hiding this comment.
Caution
agentic threat detected
Threat detection flagged this output in warn mode. Manual review is REQUIRED before any follow-up automation.
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.csline 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 testRestoreDoesNotSetExcludeRestorePackageImportsWhenWaveDisabledis needed, settingMSBUILDDISABLEFEATURESFROMVERSION=18.10and 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
|
@copilot address the pr feedback |
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> [](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>
Context
During
/restore(implicit or/t:restore), every project is evaluated twice. NuGet's restore targets re-invoke each project with the global propertyExcludeRestorePackageImports=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: addsExcludeRestorePackageImports=trueto the restore global properties so NuGet's inner MSBuild call matches the initial evaluation and reuses it. Gated behind change wave 18.10.ProjectPropertyInstance.Equals), and the MSBuild task inherits parent globals then overlays its own. NuGet passes the literal lowercasetrue, so we must too —bool.TrueString("True") would silently fail to dedupe the evaluation.MSBuildConstants: newExcludeRestorePackageImportsandExcludeRestorePackageImportsValue("true") constants.RestoreSetsExcludeRestorePackageImportsasserts the property is set during restore.Testing
Evaluations counted via
ProjectEvaluationStartedevents in a binlog, using a repro that mimics NuGet re-invoking each project withExcludeRestorePackageImports=true:/t:Restore(restore phase only)/restore /t:Build(full)All 13 existing
*Restore*XMake_Testspass alongside the new test.Notes