Skip to content

Share SDK resolution during Pack and Publish discovery#55426

Draft
OvesN wants to merge 1 commit into
dotnet:mainfrom
OvesN:dev/veronikao/shared-evaluation-context-pack-publish
Draft

Share SDK resolution during Pack and Publish discovery#55426
OvesN wants to merge 1 commit into
dotnet:mainfrom
OvesN:dev/veronikao/shared-evaluation-context-pack-publish

Conversation

@OvesN

@OvesN OvesN commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Summary

ReleasePropertyProjectLocator already uses properties-only partial evaluation to discover PackRelease and PublishRelease. This change keeps ProjectEvaluationStage.Properties and reuses one short-lived EvaluationContext across all physical-project evaluations performed by a single discovery operation.

The selected policy is SharedSDKCache. It captures most of the measured benefit without sharing filesystem existence, timestamp, or wildcard-expansion observations.

For solutions, the first analyzable project is evaluated in solution order to seed the SDK resolver cache deterministically. The remaining projects continue to evaluate in parallel without an external lock.

Performance

Measurements used:

  • OrchardCore Pack and Publish assets
  • 12 measured iterations per variant across three independent runs
  • balanced rotated/reversed variant ordering
  • --no-build --no-restore
  • MSBuild server and node reuse disabled
  • a process-local startup hook collecting the Microsoft.Build meter in every dotnet process
  • MSBuild metrics commit aed14721af3b0a8df860e48e19b554ae5a5db289
  • measured Microsoft.Build.dll SHA-256 0A2C98CC577A07D4E5F4783180CF6C792D33E0C12AC89CF13F8C36CFB5F36FAA

Counts come from msbuild.project.evaluations. Durations come from msbuild.project.evaluation.duration; the counter and histogram count are not added together. Aggregated evaluation durations can exceed wall time because solution projects evaluate in parallel.

Pack results

Policy Wall median Wall improvement vs Isolated Hidden properties count Hidden properties duration median Hidden-duration improvement vs Isolated Non-hidden full count Non-hidden full duration median
Isolated 37,024.57 ms baseline 232 38,582.02 ms baseline 861 91,652.85 ms
SharedSDKCache 33,435.72 ms 3,588.85 ms (9.69%) 232 11,873.41 ms 26,708.61 ms (69.23%) 861 94,740.03 ms
Shared 32,416.56 ms 4,608.01 ms (12.45%) 232 11,021.49 ms 27,560.53 ms (71.43%) 861 91,376.50 ms

Full Shared improved Pack wall time by another 1,019.16 ms (3.05% relative to SharedSDKCache), but that incremental benefit does not justify sharing filesystem observations.

Publish results

Policy Wall median Wall improvement vs Isolated Hidden properties count Hidden properties duration median Hidden-duration improvement vs Isolated Non-hidden full count Non-hidden full duration median
Isolated 21,924.11 ms baseline 101 31,714.64 ms baseline 299 43,190.90 ms
SharedSDKCache 21,266.28 ms 657.83 ms (3.00%) 101 14,246.04 ms 17,468.60 ms (55.08%) 299 43,896.60 ms
Shared 21,173.83 ms 750.28 ms (3.42%) 101 13,956.42 ms 17,758.22 ms (55.99%) 299 43,791.23 ms

Full Shared improved Publish wall time by only another 92.45 ms (0.43% relative to SharedSDKCache).

The hidden evaluation count remained unchanged for all three policies, while hidden evaluation duration decreased. Non-hidden MSBuild evaluation remained approximately unchanged.

Correctness

  • The context is created after the existing disable/configuration early exits.
  • Its lifetime is limited to one release-property discovery operation.
  • It is passed through project, directory, .sln, .slnx, lazy-solution, and physical-project evaluation paths.
  • The existing parallel solution evaluation remains parallel after a deterministic first-project cache seed.
  • Properties-only partial evaluation remains enabled.
  • Existing release-property tests pass under Isolated, SharedSDKCache, and Shared prototypes.
  • Custom-resolver experiments covered different SDK names, different requested versions, project-path-sensitive results, and resolver failure.
  • A project-path-sensitive resolver demonstrated that nondeterministic parallel cache seeding could make hidden discovery disagree with the subsequent MSBuild invocation. Seeding the first analyzable solution project in solution order removed that mismatch in repeated parallel runs.
  • A mutation experiment confirmed that full Shared can retain a cached missing import after another evaluation creates the file. SharedSDKCache observes the new file and avoids this compatibility boundary.

Open correctness concern

CachingSdkResolverService caches by (submissionId, sdk.Name). In these standalone discovery evaluations, every project uses the invalid submission ID, so the effective cross-project key is only the SDK name. Requested version, project path, solution path, and other resolver inputs are not included.

For example, both projects can reference Contoso.Sdk/1.0.0, while a custom resolver intentionally uses SdkResolverContext.ProjectFilePath:

A.csproj -> SDK-A -> PackRelease=true
B.csproj -> SDK-B -> PackRelease=false

With isolated evaluation, the locator observes true and false and reports NETSDK1197. With SharedSDKCache, if A resolves first, the cached Contoso.Sdk result is reused for B:

A.csproj -> PackRelease=true
B.csproj -> PackRelease=true  (received A's cached SDK result)

The conflict can therefore disappear. The subsequent real Pack invocation uses a separate resolver cache and can independently choose a different first result. The current first-project seed does not eliminate this for SDK names introduced only by later projects. This concern must be resolved before the PR is made ready for review; one possible follow-up design is an MSBuild policy that shares only successful built-in/default resolver results while keeping workload, NuGet, custom, and failed resolutions project-local.

Virtual/file-based projects

Virtual/file-based project discovery remains unchanged and out of scope. It performs full evaluation because it reads items such as Compile, and sharing its context would require threading the context through all virtual and referenced-virtual-project evaluations with dedicated behavior coverage.

Tests

  • Full SDK build: passed with 0 warnings and 0 errors.
  • GivenThatWeWantToTestAMultitargetedSolutionWithPublishReleaseOrPackRelease: 25 passed, including .sln and .slnx conflict coverage.
  • Focused PackRelease tests: 4 passed.
  • Focused PublishRelease/configuration tests: 8 passed.
  • End-to-end solution build, pack, and publish: passed.

Remaining risk

CachingSdkResolverService caches by SDK name, matching MSBuild's existing build-submission behavior. The deterministic first-project seed aligns hidden solution discovery with the subsequent MSBuild invocation's solution-order cache seed, but custom resolvers whose output depends on project path remain inherently sensitive to this existing cache contract.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).
2 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@baronfel baronfel added Area-CLI cli-ux Issues and PRs that deal with the UX of the CLI (exit codes, log output, verbs/options, and so on) Area-MSBuild labels Jul 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Area-CLI Area-MSBuild cli-ux Issues and PRs that deal with the UX of the CLI (exit codes, log output, verbs/options, and so on)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants