Auto-derive BootstrapSdkVersion from global.json so it can never lag tools.dotnet
- Dominant language
- C#
- Stars
- 5.5k
- Forks
- 1.5k
- Avg merge
- 1d 13h
- Merged PRs (30d)
- 133
Description
## Background
`eng/Versions.props::BootstrapSdkVersion` and `global.json::tools.dotnet` are two pinned .NET SDK versions in this
repo that **must** stay in sync, but today they are maintained independently. When they drift, the bootstrap shared
runtime (installed at the version `BootstrapSdkVersion` says) ends up older than the runtime the tools shipped in
`global.json::tools.dotnet`'s SDK require — and host-resolution fails.
We hit this in real life:
- PR https://github.com/dotnet/msbuild/pull/13434 (Maestro arcade flow) bumped `global.json::tools.dotnet` from
`10.0.104` → `10.0.106` but left `BootstrapSdkVersion` at `10.0.104`.
- Several days later, PRs started failing in
`MSBuild.EndToEndTests.MultithreadedExecution_Tests.MultithreadedBuild_BinaryLogging` on Windows with:
> `error : You must install or update .NET to run this application.`
> `App: D:\a\_work\1\s\.dotnet\sdk\10.0.106\Roslyn\bincore\csc.exe`
> `Framework: 'Microsoft.NETCore.App', version '10.0.6' (x64)`
> `The following frameworks were found:`
> ` 10.0.4 at [D:\a\_work\1\s\artifacts\bin\bootstrap\core\shared\Microsoft.NETCore.App]`
i.e. csc.exe (from SDK 10.0.106) needs runtime 10.0.6, but the bootstrap was installed against SDK 10.0.104 and
only has runtime 10.0.4. Roll-forward, not roll-back.
- The "fix" was a manual second PR ([#13687](https://github.com/dotnet/msbuild/pull/13687)) that simply mirrors the
value `global.json` already had. This is busywork, easy to forget, and doesn't fail fast on the original PR. Reviewer
(@ViktorHofer) suggested:
> _"We should really start using some kind of Math.Min function here and pass the NetCoreSdkVersion in so that we
are never behind the SDK version."_
Why PR #13434's CI didn't catch this:
- The `Linux Core Multithreaded Mode` lane in `.vsts-dotnet-ci.yml` runs with `--skipTests`, so it doesn't execute
the failing xunit test.
- There is no `Windows Core Multithreaded Mode` PR-CI lane at all.
- The `MultithreadedBuild_BinaryLogging` test only fires `/m:8 /mt` end-to-end and is the one that surfaces the
runtime-mismatch — every other lane uses paths that mask it.
## Proposal
Make drift structurally impossible by **deriving `BootstrapSdkVersion` from `global.json::tools.dotnet`** rather than
maintaining a separate value. We never legitimately want bootstrap to be a *different* SDK from what Arcade installs
— only ever the same — so a single source of truth is the correct model.
### 1. `eng/Versions.props` — derive at evaluation time
Replace the hard-coded `` with a parse of `global.json`:
```xml
<_GlobalJsonContent>$([System.IO.File]::ReadAllText('$(MSBuildThisFileDirectory)..\global.json'))
$([System.Text.RegularExpressions.Regex]::Match($(_GlobalJsonContent),
'"dotnet"\s*:\s*"([^"]+)"').Groups[1].Value)
Add a defensive check: if the regex returns empty, fail evaluation with a clear error ( in a target that runs before bootstrap consumers).
2. eng/cibuild_bootstrapped_msbuild.ps1 — read from global.json too
The launcher currently parses Versions.props as raw XML, which means it would see the literal $(...) expression after
change (1) instead of the evaluated value. Switch it to global.json:
# was:
# $propsFile = Join-Path $PSScriptRoot "Versions.props"
# $bootstrapSdkVersion = ([xml](Get-Content
$propsFile)).SelectSingleNode("//PropertyGroup/BootstrapSdkVersion").InnerText
$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."
}
3. eng/cibuild_bootstrapped_msbuild.sh — same on Linux/macOS
# was:
# props_file="$script_dir/Versions.props"
# sdk_version=$(grep -A1 "BootstrapSdkVersion" "$props_file" | grep -o ">.*<" | sed 's/[><]//g')
global_json="$script_dir/../global.json"
sdk_version=$(grep -o '"dotnet"[[:space:]]*:[[:space:]]*"[^"]*"' "$global_json" | head -1 | sed
's/.*"dotnet"[[:space:]]*:[[:space:]]*"\([^"]*\)"/\1/')
if [ -z "$sdk_version" ]; then
echo "ERROR: Could not read tools.dotnet from $global_json." >&2
exit 1
fi
Contributor guide
No contributing guide indexed for this repository
Assessment
This issue has not been assessed yet.