From 7e20b2a9f02f09066f59d001d01a3b13f3831dae Mon Sep 17 00:00:00 2001 From: Jan Provaznik Date: Mon, 4 May 2026 17:36:36 +0200 Subject: [PATCH 01/10] Enable -mt mode in Windows PR CI bootstrapped build (#13602) Mirrors the existing Linux BootstrapMSBuildWithMTMode job for Windows so that PR CI exercises stage-2 builds with /mt on both platforms. Two parts: 1. eng/cibuild_bootstrapped_msbuild.ps1 * Export DOTNET_HOST_PATH (mirrors cibuild_bootstrapped_msbuild.sh:96). /mt routes every unmigrated task to a sidecar TaskHost via NodeProviderOutOfProcTaskHost.ResolveAppHostOrFallback, which prefers the SDK apphost (MSBuild.exe) and needs DOTNET_ROOT in the child env. The SDK CLI (`dotnet msbuild`) sets DOTNET_HOST_PATH automatically; `dotnet exec MSBuild.dll` (used by this script) does not, so we set it here explicitly. * Add -stage2Properties parameter (mirrors --stage2Properties in the bash script). Lets callers pass /mt to stage 2 only without contaminating the stage 1 build that uses the SDK MSBuild. * Add -skipTests switch (mirrors --skipTests in the bash script). 2. .vsts-dotnet-ci.yml * Add BootstrapMSBuildWithMTModeOnWindows job, mirroring the existing BootstrapMSBuildWithMTMode (Linux) job. Local verification: the Debug stage-2 build with /mt produces shipping binaries that are byte-for-byte identical to the same build without /mt across all 1,628 files in artifacts/bin/{Microsoft.Build*, MSBuild*, StringTools, MSBuild.Bootstrap, MSBuildTaskHost} and across every file inside the four shipping nupkgs. The C# compiler is invoked the same 242 times with byte-identical command lines. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .vsts-dotnet-ci.yml | 36 ++++++++++++++++++++++++++++ eng/cibuild_bootstrapped_msbuild.ps1 | 16 ++++++++++--- 2 files changed, 49 insertions(+), 3 deletions(-) diff --git a/.vsts-dotnet-ci.yml b/.vsts-dotnet-ci.yml index 5cd79b6af33..dc7aef89585 100644 --- a/.vsts-dotnet-ci.yml +++ b/.vsts-dotnet-ci.yml @@ -291,6 +291,42 @@ jobs: continueOnError: true condition: always() +- job: BootstrapMSBuildWithMTModeOnWindows + displayName: "Windows Core Multithreaded Mode" + pool: + ${{ if eq(variables['System.TeamProject'], 'public') }}: + name: NetCore-Public + demands: ImageOverride -equals windows.vs2026preview.scout.amd64.open + ${{ if ne(variables['System.TeamProject'], 'public') }}: + name: VSEng-MicroBuildVSStable + demands: agent.os -equals Windows_NT + timeoutInMinutes: 120 + steps: + - template: azure-pipelines/check-documentation-only-change.yml + - ${{ if eq(variables['System.TeamProject'], 'DevDiv') }}: + - task: PowerShell@2 + displayName: Setup Private Feeds Credentials + inputs: + filePath: $(Build.SourcesDirectory)/eng/common/SetupNugetSources.ps1 + arguments: -ConfigFile $(Build.SourcesDirectory)/NuGet.config -Password $Env:Token + env: + Token: $(dn-bot-dnceng-artifact-feeds-rw) + - task: BatchScript@1 + displayName: 'Build with bootstrapped MSBuild and -mt mode' + inputs: + filename: 'eng/cibuild_bootstrapped_msbuild.cmd' + arguments: '-msbuildEngine dotnet -onlyDocChanged $(onlyDocChanged) -skipTests -stage2Properties "/mt /p:BuildAnalyzer=true"' + condition: eq(variables.onlyDocChanged, 0) + env: + MSBUILDUSESERVER: "1" + - task: PublishBuildArtifacts@1 + displayName: 'Publish Artifact: logs' + inputs: + PathtoPublish: 'artifacts/log/Debug' + ArtifactName: 'MTModeOnWindows build logs' + continueOnError: true + condition: always() + - job: FullReleaseOnWindows displayName: "Windows Full Release (no bootstrap)" variables: diff --git a/eng/cibuild_bootstrapped_msbuild.ps1 b/eng/cibuild_bootstrapped_msbuild.ps1 index a61ecbe7d4a..cc91e1cd6de 100644 --- a/eng/cibuild_bootstrapped_msbuild.ps1 +++ b/eng/cibuild_bootstrapped_msbuild.ps1 @@ -5,6 +5,8 @@ Param( [switch] $prepareMachine, [bool] $buildStage1 = $True, [bool] $onlyDocChanged = 0, + [switch] $skipTests, + [string] $stage2Properties = "", [Parameter(ValueFromRemainingArguments=$true)][String[]]$properties ) @@ -117,14 +119,22 @@ try { # Opt into performance logging. https://github.com/dotnet/msbuild/issues/5900 $env:DOTNET_PERFLOG_DIR=$PerfLogDir + # Mirrors cibuild_bootstrapped_msbuild.sh. Some MSBuild scenarios (notably /mt routing tasks + # to a sidecar TaskHost) require this so the apphost-based child can locate the runtime. + # The SDK CLI (`dotnet msbuild`) sets it automatically; `dotnet exec MSBuild.dll` does not. + $env:DOTNET_HOST_PATH=$dotnetExePath + # When using bootstrapped MSBuild: # - Turn off node reuse (so that bootstrapped MSBuild processes don't stay running and lock files) # - Create bootstrap environment as it's required when also running tests - if ($onlyDocChanged) { - & $PSScriptRoot\Common\Build.ps1 -restore -build -ci /p:CreateBootstrap=false /nr:false @properties + # - $stage2Properties are appended to the stage 2 build only (matching cibuild_bootstrapped_msbuild.sh). + # Use this for switches like /mt that should not be passed to the SDK MSBuild used in stage 1. + $stage2Args = if ($stage2Properties) { $stage2Properties -split '\s+' | Where-Object { $_ } } else { @() } + if ($onlyDocChanged -or $skipTests) { + & $PSScriptRoot\Common\Build.ps1 -restore -build -ci /p:CreateBootstrap=false /nr:false @properties @stage2Args } else { - & $PSScriptRoot\Common\Build.ps1 -restore -build -test -ci /nr:false @properties + & $PSScriptRoot\Common\Build.ps1 -restore -build -test -ci /nr:false @properties @stage2Args } exit $lastExitCode From 4cc0d38eba72f05cab23e9e1641abcf22abdd381 Mon Sep 17 00:00:00 2001 From: Jan Provaznik Date: Mon, 4 May 2026 17:41:10 +0200 Subject: [PATCH 02/10] Enable -mt in experimental insertion pipeline (#13602) Adds an `additionalBuildArgs` template parameter to `.vsts-dotnet-build-jobs.yml`, defaulted to empty, and sets it to `/mt` from `.vsts-dotnet-exp-perf.yml` only. The official `.vsts-dotnet.yml` pipeline is intentionally untouched in this commit; it will follow as a stacked change once the experimental run validates parity end-to-end. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- azure-pipelines/.vsts-dotnet-build-jobs.yml | 4 ++++ azure-pipelines/.vsts-dotnet-exp-perf.yml | 1 + 2 files changed, 5 insertions(+) diff --git a/azure-pipelines/.vsts-dotnet-build-jobs.yml b/azure-pipelines/.vsts-dotnet-build-jobs.yml index c8fffe6904b..f95fb422203 100644 --- a/azure-pipelines/.vsts-dotnet-build-jobs.yml +++ b/azure-pipelines/.vsts-dotnet-build-jobs.yml @@ -12,6 +12,9 @@ parameters: - name: enableOptProf type: boolean default: false +- name: additionalBuildArgs + type: string + default: '' jobs: - job: Windows_NT @@ -147,6 +150,7 @@ jobs: /p:GenerateSbom=true /p:SuppressFinalPackageVersion=${{ parameters.isExperimental }} /p:IsExperimental=${{ parameters.isExperimental }} + ${{ parameters.additionalBuildArgs }} displayName: Build condition: succeeded() diff --git a/azure-pipelines/.vsts-dotnet-exp-perf.yml b/azure-pipelines/.vsts-dotnet-exp-perf.yml index ac9aab87104..953c5db4ee3 100644 --- a/azure-pipelines/.vsts-dotnet-exp-perf.yml +++ b/azure-pipelines/.vsts-dotnet-exp-perf.yml @@ -94,6 +94,7 @@ extends: enableComponentGovernance: false signTypeParameter: ${{ parameters.signTypeParameter }} enableOptProf: ${{ parameters.enableOptProf }} + additionalBuildArgs: '/mt' - template: /eng/common/templates-official/post-build/post-build.yml@self parameters: From e05c9c00073de4d95b245af83abebd10a70e93d0 Mon Sep 17 00:00:00 2001 From: Jan Provaznik Date: Mon, 4 May 2026 17:51:14 +0200 Subject: [PATCH 03/10] Switch every PR CI bootstrapped job to -mt in stage 2 (#13602) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follow-up on review feedback: drop the two dedicated BootstrapMSBuildWithMTMode* jobs and instead append `-stage2Properties /mt` (`--stage2Properties /mt` for bash) to every existing bootstrapped PR CI job so each job exercises /mt in its stage 2 (with the test suite still running, unlike the dedicated MT job which had to skip tests). Affected jobs: * BootstrapMSBuildOnFullFrameworkWindows * BootstrapMSBuildOnCoreWindows * CoreBootstrappedOnLinux * CoreOnMac Removed jobs (now redundant): * BootstrapMSBuildWithMTMode (Linux dedicated MT mode) * BootstrapMSBuildWithMTModeOnWindows (added earlier in this PR) No /p:BuildAnalyzer=true added — the analyzer is independent of /mt and out of scope for this change. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .vsts-dotnet-ci.yml | 74 +++------------------------------------------ 1 file changed, 4 insertions(+), 70 deletions(-) diff --git a/.vsts-dotnet-ci.yml b/.vsts-dotnet-ci.yml index dc7aef89585..172f05419cd 100644 --- a/.vsts-dotnet-ci.yml +++ b/.vsts-dotnet-ci.yml @@ -92,7 +92,7 @@ jobs: displayName: cibuild_bootstrapped_msbuild.cmd inputs: filename: 'eng/cibuild_bootstrapped_msbuild.cmd' - arguments: -onlyDocChanged $(onlyDocChanged) + arguments: '-onlyDocChanged $(onlyDocChanged) -stage2Properties /mt' env: ForceUseXCopyMSBuild: 1 # Task to collect code coverage on Windows. Disabled by default due to being unstable and sometimes it stucks forever @@ -204,7 +204,7 @@ jobs: displayName: cibuild_bootstrapped_msbuild.cmd inputs: filename: 'eng/cibuild_bootstrapped_msbuild.cmd' - arguments: '-msbuildEngine dotnet -onlyDocChanged $(onlyDocChanged)' + arguments: '-msbuildEngine dotnet -onlyDocChanged $(onlyDocChanged) -stage2Properties /mt' env: MSBUILDUSESERVER: "1" # Task to collect code coverage on Windows. Disabled by default due to being unstable and sometimes it stucks forever @@ -261,72 +261,6 @@ jobs: continueOnError: true condition: eq(variables.onlyDocChanged, 0) -- job: BootstrapMSBuildWithMTMode - displayName: "Linux Core Multithreaded Mode" - pool: - vmImage: 'ubuntu-latest' - timeoutInMinutes: 120 - steps: - - template: azure-pipelines/check-documentation-only-change.yml - - ${{ if eq(variables['System.TeamProject'], 'DevDiv') }}: - - task: Bash@3 - displayName: Setup Private Feeds Credentials - inputs: - filePath: $(Build.SourcesDirectory)/eng/common/SetupNugetSources.sh - arguments: $(Build.SourcesDirectory)/NuGet.config $Token - env: - Token: $(dn-bot-dnceng-artifact-feeds-rw) - - bash: sudo apt-get update - - bash: sudo apt-get install -y libxml2 - - bash: . 'eng/cibuild_bootstrapped_msbuild.sh' --onlyDocChanged $(onlyDocChanged) --skipTests --stage2Properties '/mt /p:BuildAnalyzer=true' - displayName: 'Build with bootstrapped MSBuild and -mt mode' - condition: eq(variables.onlyDocChanged, 0) - env: - MSBUILDUSESERVER: "1" - - task: PublishBuildArtifacts@1 - displayName: 'Publish Artifact: logs' - inputs: - PathtoPublish: 'artifacts/log/Debug' - ArtifactName: 'MTModeOnLinux build logs' - continueOnError: true - condition: always() - -- job: BootstrapMSBuildWithMTModeOnWindows - displayName: "Windows Core Multithreaded Mode" - pool: - ${{ if eq(variables['System.TeamProject'], 'public') }}: - name: NetCore-Public - demands: ImageOverride -equals windows.vs2026preview.scout.amd64.open - ${{ if ne(variables['System.TeamProject'], 'public') }}: - name: VSEng-MicroBuildVSStable - demands: agent.os -equals Windows_NT - timeoutInMinutes: 120 - steps: - - template: azure-pipelines/check-documentation-only-change.yml - - ${{ if eq(variables['System.TeamProject'], 'DevDiv') }}: - - task: PowerShell@2 - displayName: Setup Private Feeds Credentials - inputs: - filePath: $(Build.SourcesDirectory)/eng/common/SetupNugetSources.ps1 - arguments: -ConfigFile $(Build.SourcesDirectory)/NuGet.config -Password $Env:Token - env: - Token: $(dn-bot-dnceng-artifact-feeds-rw) - - task: BatchScript@1 - displayName: 'Build with bootstrapped MSBuild and -mt mode' - inputs: - filename: 'eng/cibuild_bootstrapped_msbuild.cmd' - arguments: '-msbuildEngine dotnet -onlyDocChanged $(onlyDocChanged) -skipTests -stage2Properties "/mt /p:BuildAnalyzer=true"' - condition: eq(variables.onlyDocChanged, 0) - env: - MSBUILDUSESERVER: "1" - - task: PublishBuildArtifacts@1 - displayName: 'Publish Artifact: logs' - inputs: - PathtoPublish: 'artifacts/log/Debug' - ArtifactName: 'MTModeOnWindows build logs' - continueOnError: true - condition: always() - - job: FullReleaseOnWindows displayName: "Windows Full Release (no bootstrap)" variables: @@ -436,7 +370,7 @@ jobs: Token: $(dn-bot-dnceng-artifact-feeds-rw) - bash: sudo apt-get update - bash: sudo apt-get install -y libxml2 - - bash: . 'eng/cibuild_bootstrapped_msbuild.sh' --onlyDocChanged $(onlyDocChanged) + - bash: . 'eng/cibuild_bootstrapped_msbuild.sh' --onlyDocChanged $(onlyDocChanged) --stage2Properties '/mt' displayName: CI Build env: MSBUILDUSESERVER: "1" @@ -524,7 +458,7 @@ jobs: arguments: $(Build.SourcesDirectory)/NuGet.config $Token env: Token: $(dn-bot-dnceng-artifact-feeds-rw) - - bash: . 'eng/cibuild_bootstrapped_msbuild.sh' --onlyDocChanged $(onlyDocChanged) + - bash: . 'eng/cibuild_bootstrapped_msbuild.sh' --onlyDocChanged $(onlyDocChanged) --stage2Properties '/mt' displayName: CI Build env: MSBUILDUSESERVER: "1" From a9e4c8681e6988b4f7ff3527df84555b2accee2a Mon Sep 17 00:00:00 2001 From: Jan Provaznik Date: Mon, 4 May 2026 17:59:06 +0200 Subject: [PATCH 04/10] Trigger PR sync (no-op) From a414f22bb2f39695eb11e10c8916030ebe6604a1 Mon Sep 17 00:00:00 2001 From: Jan Provaznik Date: Mon, 4 May 2026 18:07:22 +0200 Subject: [PATCH 05/10] Restore Linux MT-with-analyzer job, add /mt to FullReleaseOnWindows, fix -skipTests branch (#13602) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three follow-ups from review: 1. Restore the dedicated `BootstrapMSBuildWithMTMode` (Linux Core) job that runs `--stage2Properties /mt /p:BuildAnalyzer=true --skipTests`. This validates the ThreadSafeTaskAnalyzer in addition to /mt; the regular bootstrapped jobs do not enable the analyzer because it is more expensive. 2. Add `/mt` to `FullReleaseOnWindows` (both cibuild.cmd invocations). Now every PR-CI job that builds the repo runs with /mt, except the Source-Build template (which is intentionally untouched). 3. Fix a regression in `cibuild_bootstrapped_msbuild.ps1` where `-skipTests` accidentally also passed `/p:CreateBootstrap=false`, diverging from the bash reference. Restored the bash semantics: - onlyDocChanged=1 → bootstrap not created - skipTests → bootstrap IS created, tests omitted - default → bootstrap created, tests run Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .vsts-dotnet-ci.yml | 37 ++++++++++++++++++++++++++-- eng/cibuild_bootstrapped_msbuild.ps1 | 9 ++++++- 2 files changed, 43 insertions(+), 3 deletions(-) diff --git a/.vsts-dotnet-ci.yml b/.vsts-dotnet-ci.yml index 172f05419cd..6d827738ce1 100644 --- a/.vsts-dotnet-ci.yml +++ b/.vsts-dotnet-ci.yml @@ -261,6 +261,39 @@ jobs: continueOnError: true condition: eq(variables.onlyDocChanged, 0) +# Dedicated /mt validation that turns on the ThreadSafeTaskAnalyzer (BuildAnalyzer=true), +# kept as a separate Linux Core job because the analyzer is more expensive and the regular +# bootstrapped CI jobs (which all run /mt now too) do not need it. +- job: BootstrapMSBuildWithMTMode + displayName: "Linux Core Multithreaded Mode" + pool: + vmImage: 'ubuntu-latest' + timeoutInMinutes: 120 + steps: + - template: azure-pipelines/check-documentation-only-change.yml + - ${{ if eq(variables['System.TeamProject'], 'DevDiv') }}: + - task: Bash@3 + displayName: Setup Private Feeds Credentials + inputs: + filePath: $(Build.SourcesDirectory)/eng/common/SetupNugetSources.sh + arguments: $(Build.SourcesDirectory)/NuGet.config $Token + env: + Token: $(dn-bot-dnceng-artifact-feeds-rw) + - bash: sudo apt-get update + - bash: sudo apt-get install -y libxml2 + - bash: . 'eng/cibuild_bootstrapped_msbuild.sh' --onlyDocChanged $(onlyDocChanged) --skipTests --stage2Properties '/mt /p:BuildAnalyzer=true' + displayName: 'Build with bootstrapped MSBuild and -mt mode' + condition: eq(variables.onlyDocChanged, 0) + env: + MSBUILDUSESERVER: "1" + - task: PublishBuildArtifacts@1 + displayName: 'Publish Artifact: logs' + inputs: + PathtoPublish: 'artifacts/log/Debug' + ArtifactName: 'MTModeOnLinux build logs' + continueOnError: true + condition: always() + - job: FullReleaseOnWindows displayName: "Windows Full Release (no bootstrap)" variables: @@ -288,13 +321,13 @@ jobs: displayName: cibuild.cmd inputs: filename: 'eng/cibuild.cmd' - arguments: '-configuration Release -test' + arguments: '-configuration Release -test /mt' condition: eq(variables.onlyDocChanged, 0) - task: BatchScript@1 displayName: cibuild.cmd without test inputs: filename: 'eng/cibuild.cmd' - arguments: '-configuration Release' + arguments: '-configuration Release /mt' condition: eq(variables.onlyDocChanged, 1) # Task to collect code coverage on Windows. Disabled by default due to being unstable and sometimes it stucks forever # - task: PowerShell@2 diff --git a/eng/cibuild_bootstrapped_msbuild.ps1 b/eng/cibuild_bootstrapped_msbuild.ps1 index cc91e1cd6de..250304335f4 100644 --- a/eng/cibuild_bootstrapped_msbuild.ps1 +++ b/eng/cibuild_bootstrapped_msbuild.ps1 @@ -129,10 +129,17 @@ try { # - Create bootstrap environment as it's required when also running tests # - $stage2Properties are appended to the stage 2 build only (matching cibuild_bootstrapped_msbuild.sh). # Use this for switches like /mt that should not be passed to the SDK MSBuild used in stage 1. + # Branches mirror cibuild_bootstrapped_msbuild.sh exactly: + # onlyDocChanged=1 → bootstrap not created (artifacts not needed downstream) + # skipTests → bootstrap IS created (downstream MAY consume it), tests omitted + # default → bootstrap created, tests run $stage2Args = if ($stage2Properties) { $stage2Properties -split '\s+' | Where-Object { $_ } } else { @() } - if ($onlyDocChanged -or $skipTests) { + if ($onlyDocChanged) { & $PSScriptRoot\Common\Build.ps1 -restore -build -ci /p:CreateBootstrap=false /nr:false @properties @stage2Args } + elseif ($skipTests) { + & $PSScriptRoot\Common\Build.ps1 -restore -build -ci /nr:false @properties @stage2Args + } else { & $PSScriptRoot\Common\Build.ps1 -restore -build -test -ci /nr:false @properties @stage2Args } From abbb981e15eed2e9cbf74c709f197bbdeb5e842f Mon Sep 17 00:00:00 2001 From: Jan Provaznik Date: Mon, 4 May 2026 18:30:21 +0200 Subject: [PATCH 06/10] Address Rainer's review: only enable -mt in 2-stage bootstrapped jobs (#13602) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Revert /mt from pipelines that run a single MSBuild invocation against the agent's stable VS MSBuild (no bootstrap). Those would test whatever /mt support the agent's pre-installed MSBuild has, not the freshly built one. Reverted: * .vsts-dotnet-ci.yml: FullReleaseOnWindows (cibuild.cmd, single-stage with VS MSBuild) * azure-pipelines/.vsts-dotnet-exp-perf.yml: experimental insertion (CIBuild.cmd, single-stage with VS MSBuild) * azure-pipelines/.vsts-dotnet-build-jobs.yml: drop the now-unused additionalBuildArgs template parameter Kept: * All 4 bootstrapped PR-CI jobs (Linux/macOS/Windows Core/Windows Full) — these do stage 1 with VS/SDK MSBuild then stage 2 with the freshly built bootstrapped MSBuild, which is the correct surface to exercise /mt. * Dedicated BootstrapMSBuildWithMTMode (Linux + BuildAnalyzer=true) — unchanged. Also clarify the script comment to call out that the apphost path requires DOTNET_HOST_PATH, and explain why we don't switch to `dotnet msbuild` (the SDK CLI mangles `/mt` argument splitting). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .vsts-dotnet-ci.yml | 4 ++-- azure-pipelines/.vsts-dotnet-build-jobs.yml | 4 ---- azure-pipelines/.vsts-dotnet-exp-perf.yml | 1 - eng/cibuild_bootstrapped_msbuild.ps1 | 13 +++++++++---- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.vsts-dotnet-ci.yml b/.vsts-dotnet-ci.yml index 5a894ca144f..518e53c89a3 100644 --- a/.vsts-dotnet-ci.yml +++ b/.vsts-dotnet-ci.yml @@ -327,13 +327,13 @@ jobs: displayName: cibuild.cmd inputs: filename: 'eng/cibuild.cmd' - arguments: '-configuration Release -test /mt' + arguments: '-configuration Release -test' condition: eq(variables.onlyDocChanged, 0) - task: BatchScript@1 displayName: cibuild.cmd without test inputs: filename: 'eng/cibuild.cmd' - arguments: '-configuration Release /mt' + arguments: '-configuration Release' condition: eq(variables.onlyDocChanged, 1) # Task to collect code coverage on Windows. Disabled by default due to being unstable and sometimes it stucks forever # - task: PowerShell@2 diff --git a/azure-pipelines/.vsts-dotnet-build-jobs.yml b/azure-pipelines/.vsts-dotnet-build-jobs.yml index f95fb422203..c8fffe6904b 100644 --- a/azure-pipelines/.vsts-dotnet-build-jobs.yml +++ b/azure-pipelines/.vsts-dotnet-build-jobs.yml @@ -12,9 +12,6 @@ parameters: - name: enableOptProf type: boolean default: false -- name: additionalBuildArgs - type: string - default: '' jobs: - job: Windows_NT @@ -150,7 +147,6 @@ jobs: /p:GenerateSbom=true /p:SuppressFinalPackageVersion=${{ parameters.isExperimental }} /p:IsExperimental=${{ parameters.isExperimental }} - ${{ parameters.additionalBuildArgs }} displayName: Build condition: succeeded() diff --git a/azure-pipelines/.vsts-dotnet-exp-perf.yml b/azure-pipelines/.vsts-dotnet-exp-perf.yml index 953c5db4ee3..ac9aab87104 100644 --- a/azure-pipelines/.vsts-dotnet-exp-perf.yml +++ b/azure-pipelines/.vsts-dotnet-exp-perf.yml @@ -94,7 +94,6 @@ extends: enableComponentGovernance: false signTypeParameter: ${{ parameters.signTypeParameter }} enableOptProf: ${{ parameters.enableOptProf }} - additionalBuildArgs: '/mt' - template: /eng/common/templates-official/post-build/post-build.yml@self parameters: diff --git a/eng/cibuild_bootstrapped_msbuild.ps1 b/eng/cibuild_bootstrapped_msbuild.ps1 index 250304335f4..ed290aec5cf 100644 --- a/eng/cibuild_bootstrapped_msbuild.ps1 +++ b/eng/cibuild_bootstrapped_msbuild.ps1 @@ -119,16 +119,21 @@ try { # Opt into performance logging. https://github.com/dotnet/msbuild/issues/5900 $env:DOTNET_PERFLOG_DIR=$PerfLogDir - # Mirrors cibuild_bootstrapped_msbuild.sh. Some MSBuild scenarios (notably /mt routing tasks - # to a sidecar TaskHost) require this so the apphost-based child can locate the runtime. - # The SDK CLI (`dotnet msbuild`) sets it automatically; `dotnet exec MSBuild.dll` does not. + # Mirrors cibuild_bootstrapped_msbuild.sh:96. Required so the apphost-based child task host + # (NodeProviderOutOfProcTaskHost.ResolveAppHostOrFallback) can locate the runtime when the + # parent MSBuild is launched as `dotnet exec MSBuild.dll` (which, unlike the SDK CLI + # `dotnet msbuild`, does not set DOTNET_HOST_PATH automatically). Trying to switch the + # invocation to `dotnet msbuild` here would be cleaner but the SDK CLI's argument + # translation mangles `/mt` into `/m` + `t`, so we keep the explicit `MSBuild.dll` path + # and set DOTNET_HOST_PATH ourselves. $env:DOTNET_HOST_PATH=$dotnetExePath # When using bootstrapped MSBuild: # - Turn off node reuse (so that bootstrapped MSBuild processes don't stay running and lock files) # - Create bootstrap environment as it's required when also running tests # - $stage2Properties are appended to the stage 2 build only (matching cibuild_bootstrapped_msbuild.sh). - # Use this for switches like /mt that should not be passed to the SDK MSBuild used in stage 1. + # Use this for switches like /mt that should not be passed to the stable MSBuild used in stage 1 + # until a stable version of MT is available in the images. # Branches mirror cibuild_bootstrapped_msbuild.sh exactly: # onlyDocChanged=1 → bootstrap not created (artifacts not needed downstream) # skipTests → bootstrap IS created (downstream MAY consume it), tests omitted From 73362b3a85785bb92ebd3b0957e2ac1b0bdc921f Mon Sep 17 00:00:00 2001 From: Jan Provaznik Date: Mon, 4 May 2026 18:38:48 +0200 Subject: [PATCH 07/10] Fix /mt mangling: wrap -split result in @() to keep array even for one token (#13602) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `stage2Properties -split '\s+'` returns a string when there is exactly one token, and `& cmd @stringVar` then splats that string's characters one per argument — so `-stage2Properties "/mt"` reaches MSBuild as `/`, `m`, `t`. Wrapping the result in @() keeps it an array (length 0/1/N all behave the same), and `/mt` flows through unchanged. Found by interactive repro: $a = "/mt" -split '\s+' | Where-Object { $_ } # → string "/mt" & some.ps1 @a # → arg list: "/", "m", "t" $a = @("/mt" -split '\s+' | Where-Object { $_ }) # → array ["/mt"] & some.ps1 @a # → arg list: "/mt" Verified end-to-end with `cibuild_bootstrapped_msbuild.ps1 -skipTests -stage2Properties "/mt"` succeeding (Build succeeded, 0 errors). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- eng/cibuild_bootstrapped_msbuild.ps1 | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/eng/cibuild_bootstrapped_msbuild.ps1 b/eng/cibuild_bootstrapped_msbuild.ps1 index ed290aec5cf..cc76dcece12 100644 --- a/eng/cibuild_bootstrapped_msbuild.ps1 +++ b/eng/cibuild_bootstrapped_msbuild.ps1 @@ -138,7 +138,11 @@ try { # onlyDocChanged=1 → bootstrap not created (artifacts not needed downstream) # skipTests → bootstrap IS created (downstream MAY consume it), tests omitted # default → bootstrap created, tests run - $stage2Args = if ($stage2Properties) { $stage2Properties -split '\s+' | Where-Object { $_ } } else { @() } + # The @(...) wrapper is important: when -split returns exactly one element PowerShell + # gives back a string, and `& cmd @stringVar` splats its characters one-per-argument + # (so "/mt" becomes "/", "m", "t"). Wrapping with @() forces an array even for a + # single token. + $stage2Args = @(if ($stage2Properties) { $stage2Properties -split '\s+' | Where-Object { $_ } } else { @() }) if ($onlyDocChanged) { & $PSScriptRoot\Common\Build.ps1 -restore -build -ci /p:CreateBootstrap=false /nr:false @properties @stage2Args } From d4c72fc703b7f7e61061410586346de52542f62e Mon Sep 17 00:00:00 2001 From: Jan Provaznik Date: Mon, 4 May 2026 19:04:39 +0200 Subject: [PATCH 08/10] Use `dotnet msbuild` instead of `dotnet exec MSBuild.dll` (#13602) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per Rainer's suggestion. Cleaner: the SDK CLI sets DOTNET_HOST_PATH for the MSBuild process automatically, so we don't need to export it ourselves and can drop the Versions.props lookup for the bootstrap SDK MSBuild.dll path. Verified locally that /mt flows through unmangled (no MSB1001, no `/m t` argument mangling) and the build produces the same outputs: binlog shows 243 Csc invocations, no errors. The earlier suspicion that `dotnet msbuild` mangled /mt was a separate bug — fixed in 73362b3a85 (stage2Args splat). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- eng/cibuild_bootstrapped_msbuild.ps1 | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/eng/cibuild_bootstrapped_msbuild.ps1 b/eng/cibuild_bootstrapped_msbuild.ps1 index cc76dcece12..21ab6927d3e 100644 --- a/eng/cibuild_bootstrapped_msbuild.ps1 +++ b/eng/cibuild_bootstrapped_msbuild.ps1 @@ -87,9 +87,7 @@ try { else { $buildToolPath = "$bootstrapRoot\core\dotnet.exe" - $propsFile = Join-Path $PSScriptRoot "Versions.props" - $bootstrapSdkVersion = ([xml](Get-Content $propsFile)).SelectSingleNode("//PropertyGroup/BootstrapSdkVersion").InnerText - $buildToolCommand = "$bootstrapRoot\core\sdk\$bootstrapSdkVersion\MSBuild.dll" + $buildToolCommand = "msbuild" $buildToolFramework = "net" $env:DOTNET_ROOT="$bootstrapRoot\core" @@ -119,15 +117,6 @@ try { # Opt into performance logging. https://github.com/dotnet/msbuild/issues/5900 $env:DOTNET_PERFLOG_DIR=$PerfLogDir - # Mirrors cibuild_bootstrapped_msbuild.sh:96. Required so the apphost-based child task host - # (NodeProviderOutOfProcTaskHost.ResolveAppHostOrFallback) can locate the runtime when the - # parent MSBuild is launched as `dotnet exec MSBuild.dll` (which, unlike the SDK CLI - # `dotnet msbuild`, does not set DOTNET_HOST_PATH automatically). Trying to switch the - # invocation to `dotnet msbuild` here would be cleaner but the SDK CLI's argument - # translation mangles `/mt` into `/m` + `t`, so we keep the explicit `MSBuild.dll` path - # and set DOTNET_HOST_PATH ourselves. - $env:DOTNET_HOST_PATH=$dotnetExePath - # When using bootstrapped MSBuild: # - Turn off node reuse (so that bootstrapped MSBuild processes don't stay running and lock files) # - Create bootstrap environment as it's required when also running tests From 96fff5dfb2e31ca732f4a4512e30d049c2a0c393 Mon Sep 17 00:00:00 2001 From: Jan Provaznik Date: Thu, 7 May 2026 17:56:13 +0200 Subject: [PATCH 09/10] Restore DOTNET_HOST_PATH script-env export (#13602) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CI on the previous commit revealed Windows test failures (MSB4216 launching .NET task hosts, mostly net472 x86 testhosts spawning .NET Core MSBuild grandchildren under /mt). Linux/macOS pass because cibuild_bootstrapped_msbuild.sh has been exporting DOTNET_HOST_PATH at line 96 all along. Restore the export in the .ps1 to match. Keep the `dotnet msbuild` invocation from the prior cleanup — it's orthogonal: the SDK CLI sets DOTNET_HOST_PATH for the MSBuild process it spawns, but does not propagate it into the parent script environment, which is what test grandchildren inherit. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- eng/cibuild_bootstrapped_msbuild.ps1 | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/eng/cibuild_bootstrapped_msbuild.ps1 b/eng/cibuild_bootstrapped_msbuild.ps1 index 21ab6927d3e..b30cb2f772f 100644 --- a/eng/cibuild_bootstrapped_msbuild.ps1 +++ b/eng/cibuild_bootstrapped_msbuild.ps1 @@ -117,6 +117,14 @@ try { # Opt into performance logging. https://github.com/dotnet/msbuild/issues/5900 $env:DOTNET_PERFLOG_DIR=$PerfLogDir + # Mirrors cibuild_bootstrapped_msbuild.sh:96. Required for some test scenarios that spawn + # MSBuild grandchildren which need to launch .NET task hosts (notably net472 x86 testhosts + # invoking .NET Core MSBuild → /mt → sidecar TaskHost). The SDK CLI `dotnet msbuild` sets + # DOTNET_HOST_PATH for the MSBuild process it spawns, but does not propagate it into the + # parent script's environment, so we set it here for child processes (tests, their MSBuild + # grandchildren) to inherit. + $env:DOTNET_HOST_PATH=$dotnetExePath + # When using bootstrapped MSBuild: # - Turn off node reuse (so that bootstrapped MSBuild processes don't stay running and lock files) # - Create bootstrap environment as it's required when also running tests From bfc4a0328953bdb0a01b752b97d2d84d43d9110d Mon Sep 17 00:00:00 2001 From: Jan Provaznik Date: Thu, 7 May 2026 18:45:19 +0200 Subject: [PATCH 10/10] Apply Option B: revert dotnet msbuild back to dotnet exec MSBuild.dll (#13602) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Option A (restoring DOTNET_HOST_PATH script-env export) didn't fix the Windows Core / Windows Full failures. Both jobs still fail in the same SDK-style EndToEnd tests (net10.0 x64) under /mt, while the same tests pass on Linux Core. The non-SDK variants and net472 x86 testhost all pass. Reverting `dotnet msbuild` → `dotnet exec MSBuild.dll` brings the .ps1 back to the bash script's exact shape (which works on Linux/macOS). This is the lowest-risk way to determine whether the SDK CLI's argument forwarding contributes to the test process tree breakage. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- eng/cibuild_bootstrapped_msbuild.ps1 | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/eng/cibuild_bootstrapped_msbuild.ps1 b/eng/cibuild_bootstrapped_msbuild.ps1 index b30cb2f772f..871ca5b741a 100644 --- a/eng/cibuild_bootstrapped_msbuild.ps1 +++ b/eng/cibuild_bootstrapped_msbuild.ps1 @@ -87,7 +87,9 @@ try { else { $buildToolPath = "$bootstrapRoot\core\dotnet.exe" - $buildToolCommand = "msbuild" + $propsFile = Join-Path $PSScriptRoot "Versions.props" + $bootstrapSdkVersion = ([xml](Get-Content $propsFile)).SelectSingleNode("//PropertyGroup/BootstrapSdkVersion").InnerText + $buildToolCommand = "$bootstrapRoot\core\sdk\$bootstrapSdkVersion\MSBuild.dll" $buildToolFramework = "net" $env:DOTNET_ROOT="$bootstrapRoot\core"