You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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.
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) 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."
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 <BootstrapSdkVersion> with a parse of global.json:
<PropertyGroup>
<_GlobalJsonContent>$([System.IO.File]::ReadAllText('$(MSBuildThisFileDirectory)..\global.json'))</_GlobalJsonContent>
<BootstrapSdkVersion>$([System.Text.RegularExpressions.Regex]::Match($(_GlobalJsonContent),
'"dotnet"\s*:\s*"([^"]+)"').Groups[1].Value)</BootstrapSdkVersion>
</PropertyGroup>
Add a defensive check: if the regex returns empty, fail evaluation with a clear error (<Error
Condition="'$(BootstrapSdkVersion)' == ''"Text="Could not parse tools.dotnet from global.json. Verify the file isvalid JSON." /> 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
Background
eng/Versions.props::BootstrapSdkVersionandglobal.json::tools.dotnetare two pinned .NET SDK versions in thisrepo that must stay in sync, but today they are maintained independently. When they drift, the bootstrap shared
runtime (installed at the version
BootstrapSdkVersionsays) ends up older than the runtime the tools shipped inglobal.json::tools.dotnet's SDK require — and host-resolution fails.We hit this in real life:
PR [main] Update dependencies from dotnet/arcade #13434 (Maestro arcade flow) bumped
global.json::tools.dotnetfrom10.0.104→10.0.106but leftBootstrapSdkVersionat10.0.104.Several days later, PRs started failing in
MSBuild.EndToEndTests.MultithreadedExecution_Tests.MultithreadedBuild_BinaryLoggingon Windows with: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) that simply mirrors the
value
global.jsonalready had. This is busywork, easy to forget, and doesn't fail fast on the original PR. Reviewer(@ViktorHofer) suggested:
Why PR #13434's CI didn't catch this:
Linux Core Multithreaded Modelane in.vsts-dotnet-ci.ymlruns with--skipTests, so it doesn't executethe failing xunit test.
Windows Core Multithreaded ModePR-CI lane at all.MultithreadedBuild_BinaryLoggingtest only fires/m:8 /mtend-to-end and is the one that surfaces theruntime-mismatch — every other lane uses paths that mask it.
Proposal
Make drift structurally impossible by deriving
BootstrapSdkVersionfromglobal.json::tools.dotnetrather thanmaintaining 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 timeReplace the hard-coded
<BootstrapSdkVersion>with a parse ofglobal.json: