Skip to content

Auto-derive BootstrapSdkVersion from global.json so it can never lag tools.dotnet#13694

Open
Copilot wants to merge 13 commits into
mainfrom
copilot/auto-derive-bootstrap-sdk-version
Open

Auto-derive BootstrapSdkVersion from global.json so it can never lag tools.dotnet#13694
Copilot wants to merge 13 commits into
mainfrom
copilot/auto-derive-bootstrap-sdk-version

Conversation

Copilot AI commented May 5, 2026

Copy link
Copy Markdown
Contributor

Fixes #13693

eng/Versions.props's hard-coded bootstrap SDK version and global.json's tools.dotnet were maintained independently and could drift. When the bootstrap SDK lags tools.dotnet, its shared runtime ends up older than the building SDK delivers, causing host-resolution failures (You must install or update .NET to run this application).

Changes

  • eng/Versions.props — Rename BootstrapSdkVersion -> BootstrapSdkVersionFloor: it is a minimum, not the literal version.
  • eng/BootStrapMsBuild.props — Derive the effective BootstrapSdkVersion = Max(BootstrapSdkVersionFloor, $(NETCoreSdkVersion)), so the bootstrap SDK can never lag global.json, while a higher floor can still pin a newer SDK.
  • eng/BootStrapMsBuild.targets — Add VerifyBootstrapRuntimeFloor: after the SDK is acquired, fail the build if none of the laid-down Microsoft.NETCore.App runtimes is at least $(BundledNETCoreAppPackageVersion) (the runtime the building SDK delivers).
  • eng/cibuild_bootstrapped_msbuild.ps1 / .sh — Resolve the version via dotnet msbuild -getProperty:BootstrapSdkVersion on MSBuild.Bootstrap.csproj instead of parsing Versions.props, so stage 2 locates sdk/<version>/MSBuild.dll using the effective value. Fail fast if it cannot be read.
  • Docs — Update documentation/release-checklist.md and .github/skills/release/SKILL.md for the rename.

Tests

src/Build.UnitTests/BootstrapRuntimeFloor_Tests.cs covers the guard: runtime equal/newer/older than the floor, multiple runtimes, prerelease-suffix handling, non-version directories, and absent/empty runtime root.

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Hello @copilot, I noticed that you’re changing an .swr file or any file under src/Package/MSBuild.VSSetup.. Please make sure to validate this change by an experimental VS insertion. This is accomplished by pushing to an exp/* branch, which requires write permissions to this repo.

Copilot AI changed the title [WIP] Auto-derive BootstrapSdkVersion from global.json Auto-derive BootstrapSdkVersion from global.json so it can never lag tools.dotnet May 5, 2026
Copilot finished work on behalf of OvesN May 5, 2026 16:25
Copilot AI requested a review from OvesN May 5, 2026 16:25
Comment thread eng/Versions.props Outdated
<PropertyGroup>
<BootstrapSdkVersion>10.0.106</BootstrapSdkVersion>
<!-- Derived from global.json so it can never lag tools.dotnet -->
<BootstrapSdkVersion>$(DotNetCliVersion)</BootstrapSdkVersion>

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.

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.

What we are missing here is doing some kind of Math.Min('$(NetCoreSdkVersion)', '10.0.106'))

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.

Yes, I like being able to differ from the initial SDK version. What we should need to guarantee is that we will never construct a bootstrap with a lower .NET runtime version than the SDK delivers (since that version can be embedded into outputs e.g. the apphosts). Unfortunately that's a much harder computation since the runtime can be embedded in different SDKs.

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.

I fixed it - now we use NetCoreSdkVersion, and we have a new target that checks that we do not use a lower runtime in bootstrap.

Comment thread eng/cibuild_bootstrapped_msbuild.ps1 Outdated
Comment on lines +88 to +92
$globalJsonPath = Join-Path $PSScriptRoot "..\global.json"
$bootstrapSdkVersion = (Get-Content $globalJsonPath -Raw | ConvertFrom-Json).tools.dotnet
if ([string]::IsNullOrWhiteSpace($bootstrapSdkVersion)) {
throw "Could not read tools.dotnet from $globalJsonPath."
}

@ViktorHofer ViktorHofer May 5, 2026

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.

This change seems wrong. It now always uses the SDK version from global.json. It would be better to still allow to hardcode a newer version via the BootstrapSdkVersion property but make sure that is never older than the SDK provided one.

I would use the msbuild -getproperty function here to avoid direct XML parsing and invoke some project that imports the microsoft.net.sdk, i.e. the msbuild.bootstrap.csproj.

This also applies to the bash changes.

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.

Addressed - we can hardcode a newer version via BootstrapSdkVersionFloor, later we use Max(BootstrapSdkVersionFloor, NETCoreSdkVersion). XML parsing replaced by -getproperty function

Comment thread eng/Versions.props Outdated
<PropertyGroup>
<BootstrapSdkVersion>10.0.106</BootstrapSdkVersion>
<!-- Derived from global.json so it can never lag tools.dotnet -->
<BootstrapSdkVersion>$(DotNetCliVersion)</BootstrapSdkVersion>

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.

Yes, I like being able to differ from the initial SDK version. What we should need to guarantee is that we will never construct a bootstrap with a lower .NET runtime version than the SDK delivers (since that version can be embedded into outputs e.g. the apphosts). Unfortunately that's a much harder computation since the runtime can be embedded in different SDKs.

OvesN and others added 8 commits May 25, 2026 15:44
Reworks the bootstrap SDK derivation based on PR review feedback:

- Restore hardcoded BootstrapSdkVersion floor in eng/Versions.props so
  developers can pin a NEWER SDK when needed (per Viktor / Rainer).
- In eng/BootStrapMsBuild.props, override BootstrapSdkVersion to
  `\$(NETCoreSdkVersion)` when the SDK actually resolving the project is
  newer than the floor. Uses `[MSBuild]::VersionGreaterThan`. The
  override runs after Microsoft.NET.Sdk's implicit props import sets
  NETCoreSdkVersion. Result: `BootstrapSdkVersion = Max(floor,
  NETCoreSdkVersion)` — bootstrap can never be older than what
  global.json delivers, but explicit pins of newer SDKs are preserved.
- In cibuild_bootstrapped_msbuild.{ps1,sh}, replace direct global.json
  parsing with `dotnet msbuild -getProperty:BootstrapSdkVersion` against
  src/MSBuild.Bootstrap/MSBuild.Bootstrap.csproj. This evaluates the SDK
  and applies the override, returning the same value the stage1 build
  used to lay out `sdk/<version>`. User properties are forwarded so
  /p:BootstrapSdkVersion=... overrides remain consistent across stage1
  and the version query.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
BootstrapSdkVersion selects which SDK is acquired, but the bundled runtime is what actually matters: build outputs (apphosts, runtimeconfig.json) embed the runtime version, so a bootstrap with an older runtime fails host resolution. Because SDK feature bands are serviced independently, a higher SDK version does not guarantee a newer runtime and cannot be predicted cheaply up front.

Add VerifyBootstrapRuntimeFloor (AfterTargets=AcquireSdk) which compares the runtime actually laid down under the bootstrap shared/Microsoft.NETCore.App against BundledNETCoreAppPackageVersion (the runtime the resolving SDK delivers) and fails fast if it is older. This closes the SDK-version-vs-runtime-version gap raised in PR review.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Covers the bootstrap runtime floor guard in eng/BootStrapMsBuild.targets by importing the real targets file and running the target against synthetic bootstrap shared-runtime layouts:

- passes when the laid-down runtime equals/exceeds the floor (incl. numeric-not-lexical comparison and prerelease-suffix insensitivity); - fails with a clear error when the runtime is older than BundledNETCoreAppPackageVersion; - passes when any present runtime satisfies the floor (roll-forward semantics); - passes (no-op) when no bootstrap runtime is present, so the guard never false-positives.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Review-driven fixes to VerifyBootstrapRuntimeFloor:

- Fix a latent crash: when the shared-runtime root exists but contains no version directory, the empty item batch evaluated [MSBuild]::VersionGreaterThanOrEquals on an empty string and threw MSB4184. Guard the comparison with a non-empty identity check (and short-circuit).

- Ignore non-version directories under shared/Microsoft.NETCore.App via a regex filter so a stray folder cannot crash the build.

- Make the error message accurate (can fail, not will fail; embeds the SDK runtime version) and document two known limitations (stricter-than-roll-forward patch comparison; prerelease-suffix-insensitive comparison shared with the Max override).

Tests: strengthen the fail assertion to avoid a 10.0.3 vs 10.0.300 substring trap (use 9.7.2), and add regression tests for unknown floor (no-op), empty-but-present root, only-non-version-directories, and all-runtimes-older. 12/12 pass on net10.0, warning-clean.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The value in eng/Versions.props is a minimum floor, not the literal bootstrap SDK version, so name it accordingly. eng/BootStrapMsBuild.props now derives the effective BootstrapSdkVersion = Max(BootstrapSdkVersionFloor, NETCoreSdkVersion).

Verified: floor<SDK -> SDK wins; floor>SDK -> floor wins; explicit /p:BootstrapSdkVersion still overrides; both props consumers (MSBuild.Bootstrap and Microsoft.Build.UnitTests.Shared) resolve the same value. Also trims now-redundant explanatory comments.

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

OvesN commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

/review

@github-actions

github-actions Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

⚠️ Security scanning failed for Expert Code Review (command). Review the logs for details.

Performance review complete for PR #13694.

@github-actions

Copy link
Copy Markdown
Contributor

Design-level notes on PR #13694

🟡 Stale documentation — property rename not reflected

Two files still reference the old BootstrapSdkVersion property name (now BootstrapSdkVersionFloor in eng/Versions.props):

  • documentation/release-checklist.md line 232: Update BootstrapSdkVersion in eng/Versions.props if a fresh SDK was released.
  • .github/skills/release/SKILL.md line 119: table entry lists BootstrapSdkVersion in eng/Versions.props

Both references will mislead the next release engineer (the property no longer exists under that name). They also need a guidance update: under the new model the floor only needs raising when pinning a newer SDK than global.json would naturally deliver — a different trigger condition than before.

🟡 Stale PR description

The description states:

eng/cibuild_bootstrapped_msbuild.sh — Uses jq when available for proper JSON path traversal (.tools.dotnet); falls back to grep/sed.

The actual implementation uses dotnet msbuild -getProperty:BootstrapSdkVersion — no jq and no global.json read. The description appears to be from an earlier iteration of the PR and should be corrected to avoid confusion during review or future bisection.

Warning

Firewall blocked 2 domains

The following domains were blocked by the firewall during workflow execution:

  • dnceng.pkgs.visualstudio.com
  • pkgs.dev.azure.com

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "dnceng.pkgs.visualstudio.com"
    - "pkgs.dev.azure.com"

See Network Configuration for more information.

Generated by Expert Code Review (command) for issue #13694 · 2.9K AIC · ⊞ 29.8K ambient context ·
Comment /review to run again

@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.

Security — LGTM

Checked all seven dimensions against the diff:

Dimension Finding
Script injection ($properties unquoted in SH) $properties is word-split by bash, but it originates from the Arcade build harness, not external input. No concrete injection path.
Path traversal (_BootstrapSharedRuntimeRoot) Derived from $(ArtifactsBinDir) set by the build system. Used read-only via GetDirectories(); directory names are then regex-filtered (^[0-9]+\.[0-9]+) before any version comparison. No traversal risk.
SecurityElement.Escape() in test XML Escapes XML special chars (&<>"'). Does not escape MSBuild evaluation sequences ($(...)). However, all three escaped values are: a temp filesystem path (from TestEnvironment.Create), a version string from inline test data, and a resolved .targets file path — none will ever contain $(. No concrete failing scenario.
@properties splat in PS1 Passes CI-controlled MSBuild property flags as individual arguments to dotnet msbuild. Trusted CI input; no user-facing surface.
GetDirectories() exposure Scoped to the bootstrap artifacts directory. Read-only listing; no sensitive data exposed.
CI infra context All changed code runs inside the repo's own CI pipeline on controlled agents. Threat model requires prior CI compromise.
Secrets/credentials None handled or exposed anywhere in the diff.

No actionable security issue found.

Warning

Firewall blocked 2 domains

The following domains were blocked by the firewall during workflow execution:

  • dnceng.pkgs.visualstudio.com
  • pkgs.dev.azure.com

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "dnceng.pkgs.visualstudio.com"
    - "pkgs.dev.azure.com"

See Network Configuration for more information.

Generated by Expert Code Review (command) for issue #13694 · 2.9K AIC · ⊞ 29.8K ambient context
Comment /review to run again

Comment thread src/Build.UnitTests/BootstrapRuntimeFloor_Tests.cs
Comment thread eng/cibuild_bootstrapped_msbuild.ps1
Comment thread src/Build.UnitTests/BootstrapRuntimeFloor_Tests.cs
eng/Versions.props now defines BootstrapSdkVersionFloor (effective bootstrap SDK = Max(floor, global.json tools.dotnet)). Update the release checklist and release skill to the new name and clarify the floor only needs raising to pin an SDK newer than tools.dotnet.

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

github-actions Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

🔍 Skill Validator Results

⚠️ Warnings or advisories found

Scope Checked
Skills 1
Agents 0
Total 1
Severity Count
--- ---:
❌ Errors 0
⚠️ Warnings 2
ℹ️ Advisories 0

Summary

Level Finding
ℹ️ Found 1 skill(s)
ℹ️ [release] 📊 release: 2,904 BPE tokens [chars/4: 2,869] (standard ~), 12 sections, 0 code blocks
ℹ️ [release] ⚠ Skill is 2,904 BPE tokens (chars/4 estimate: 2,869) — approaching "comprehensive" range where gains diminish.
ℹ️ [release] ⚠ No code blocks — agents perform better with concrete snippets and commands.
ℹ️ ✅ All checks passed (1 skill(s))
Full validator output ```text Found 1 skill(s) [release] 📊 release: 2,904 BPE tokens [chars/4: 2,869] (standard ~), 12 sections, 0 code blocks [release] ⚠ Skill is 2,904 BPE tokens (chars/4 estimate: 2,869) — approaching "comprehensive" range where gains diminish. [release] ⚠ No code blocks — agents perform better with concrete snippets and commands. ✅ All checks passed (1 skill(s)) ```

OvesN and others added 2 commits June 19, 2026 16:51
…ivation

Imports eng/BootStrapMsBuild.props directly with explicit BootstrapSdkVersionFloor and NETCoreSdkVersion and asserts the derived BootstrapSdkVersion, covering all three branches (SDK wins, floor wins, empty NETCoreSdkVersion -> floor). Previously only the targets guard was tested, leaving the props derivation uncovered.

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

The bootstrap SDK is Max(BootstrapSdkVersionFloor, tools.dotnet), so a normal tools.dotnet bump suffices; fold the now-conditional floor step into 5.4 (removing the orphaned 5.4b) while keeping guidance to adjust the floor when pinning a newer SDK or when it exceeds tools.dotnet on a branch.

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

OvesN commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

/review

@github-actions

github-actions Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

⚠️ Security scanning failed for Expert Code Review (command). Review the logs for details.

Expert-reviewer subagent launched for PR #13694 (24 dimensions, parallel sub-agent batches). The subagent will post its own inline review comments and submit a final review verdict directly to the PR once complete.

@github-actions github-actions Bot mentioned this pull request Jun 19, 2026
@OvesN OvesN marked this pull request as ready for review June 19, 2026 16:30
@OvesN OvesN requested a review from a team as a code owner June 19, 2026 16:30
Copilot AI review requested due to automatic review settings June 19, 2026 16:30
@OvesN

OvesN commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

@ViktorHofer @rainersigwald
I converted this pr to ready, can you re-review it please?

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 prevents the repo’s bootstrap SDK from drifting behind the SDK pinned in global.json by (1) redefining the version in eng/Versions.props as a floor, (2) deriving the effective BootstrapSdkVersion as the max of that floor and the resolving SDK’s NETCoreSdkVersion, and (3) adding a post-acquire guard that fails early if the bootstrap’s laid-down runtime is older than the runtime bundled with the building SDK.

Changes:

  • Rename BootstrapSdkVersion to BootstrapSdkVersionFloor and compute the effective BootstrapSdkVersion in eng/BootStrapMsBuild.props.
  • Add VerifyBootstrapRuntimeFloor after SDK acquisition to detect runtime mismatches and fail fast.
  • Update stage-2 bootstrap scripts to query the resolved BootstrapSdkVersion via dotnet msbuild -getProperty:BootstrapSdkVersion, and add unit tests + doc updates.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
src/Build.UnitTests/BootstrapRuntimeFloor_Tests.cs Adds unit coverage for the derived bootstrap SDK version and the runtime-floor guard behavior.
eng/Versions.props Renames the pinned bootstrap SDK value to a floor (BootstrapSdkVersionFloor) and clarifies intent via comments.
eng/BootStrapMsBuild.props Derives BootstrapSdkVersion as Max(BootstrapSdkVersionFloor, NETCoreSdkVersion).
eng/BootStrapMsBuild.targets Adds VerifyBootstrapRuntimeFloor to validate the bootstrap runtime laid down on disk meets the building SDK’s runtime floor.
eng/cibuild_bootstrapped_msbuild.ps1 Resolves the effective bootstrap SDK version via dotnet msbuild -getProperty:BootstrapSdkVersion instead of parsing Versions.props.
eng/cibuild_bootstrapped_msbuild.sh Same as PS1: resolve the effective bootstrap SDK version via dotnet msbuild -getProperty:BootstrapSdkVersion.
documentation/release-checklist.md Updates release instructions to reflect the new floor semantics and reduced manual sync work.
.github/skills/release/SKILL.md Updates the release skill doc to reference BootstrapSdkVersionFloor.

Comment on lines +268 to +272
<_BootstrapRuntimeLeaf Include="@(_BootstrapRuntimeDir->'%(Filename)%(Extension)')" />
<_BootstrapRuntimeVersion Include="@(_BootstrapRuntimeLeaf)"
Condition="$([System.Text.RegularExpressions.Regex]::IsMatch('%(Identity)', '^[0-9]+\.[0-9]+'))" />
<_BootstrapRuntimeAtLeastFloor Include="@(_BootstrapRuntimeVersion)"
Condition="'%(Identity)' != '' and $([MSBuild]::VersionGreaterThanOrEquals('%(Identity)', '$(BundledNETCoreAppPackageVersion)'))" />
resolving this project; the floor wins only when it is higher (pinning a newer SDK). -->
<PropertyGroup>
<BootstrapSdkVersion>$(BootstrapSdkVersionFloor)</BootstrapSdkVersion>
<BootstrapSdkVersion Condition="'$(NETCoreSdkVersion)' != '' and $([MSBuild]::VersionGreaterThan('$(NETCoreSdkVersion)', '$(BootstrapSdkVersion)'))">$(NETCoreSdkVersion)</BootstrapSdkVersion>

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.

Do we need this condition, or should we just have the better error when we violate the runtime-version constraint? I lean toward "don't silently fix a misconfiguration" for this.

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.

@rainersigwald I thought that this condition is the whole point of this PR though. So we do not have a chore of manually bumping BootstapSdkVersion to match global.json.

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.

Ah, looking back I think that is what @ViktorHofer was suggesting. However, I'm not sure it's the right move. For a long time the bootstrap base and the SDK we used to build were very strongly coupled, and that sucked. We got them decoupled, which is great--but then we uncovered a more subtle coupling in the packaged-runtime-version stuff. I would personally rate things

{current state} < {reintroduce coupling but only in one direction} < {fully decoupled but clear deterministic errors when things aren't satisfied}

However I'd like to get a bit more consensus.

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.

Auto-derive BootstrapSdkVersion from global.json so it can never lag tools.dotnet

5 participants