From fa9a3dc5ca7a4ca137910885585ebffbf1514399 Mon Sep 17 00:00:00 2001 From: "Kai Tao (from Dev Box)" Date: Thu, 30 Jul 2026 11:18:53 +0800 Subject: [PATCH 1/6] Configure Dependabot for WTA Rust dependencies Check the tools/wta Cargo dependency graph weekly, group routine minor and patch updates, and keep ACP SDK updates in dedicated pull requests. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: effeb503-7fb5-449e-905a-3f7ea0e084a1 --- .github/dependabot.yml | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000000..02229cbbe9 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,25 @@ +version: 2 + +updates: + - package-ecosystem: "cargo" + directory: "/tools/wta" + schedule: + interval: "weekly" + day: "monday" + time: "09:00" + timezone: "Asia/Shanghai" + open-pull-requests-limit: 5 + groups: + wta-rust-minor-and-patch: + applies-to: "version-updates" + patterns: + - "*" + exclude-patterns: + - "agent-client-protocol*" + update-types: + - "minor" + - "patch" + + # WTA statically links its Rust dependencies. After accepting an update, + # regenerate tools/wta/cgmanifest.json and the WTA block in NOTICE.md with + # build/scripts/Generate-WtaThirdPartyNotices.ps1. From fc2fb1646f5632f89218c233fc1c60fb3d4fb287 Mon Sep 17 00:00:00 2001 From: "Kai Tao (from Dev Box)" Date: Fri, 31 Jul 2026 10:53:39 +0800 Subject: [PATCH 2/6] Make WTA toolchain usable by Dependabot Keep Azure DevOps on the matching internal ms-prod channel while exposing the public Rust version to GitHub automation. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: fbe1d08b-e782-4b3e-b8d9-ccbde507a1a7 --- .github/instructions/rust-wta.instructions.md | 2 +- build/pipelines/templates-v2/job-build-project.yml | 14 +++++++++++--- build/scripts/Generate-WtaThirdPartyNotices.ps1 | 7 +++---- doc/specs/acp-1.0-conductor-migration.md | 4 ++-- tools/wta/Generate-Coverage.ps1 | 12 +++++------- tools/wta/rust-toolchain.toml | 8 +++----- 6 files changed, 25 insertions(+), 22 deletions(-) diff --git a/.github/instructions/rust-wta.instructions.md b/.github/instructions/rust-wta.instructions.md index 2078f33094..b589d24148 100644 --- a/.github/instructions/rust-wta.instructions.md +++ b/.github/instructions/rust-wta.instructions.md @@ -11,7 +11,7 @@ The repo contains multiple Rust crates (e.g. `installer/bootstrap/`); this file' ## Toolchain & Build -- **Toolchain is pinned.** `tools/wta/rust-toolchain.toml` pins the channel to `ms-prod-1.93` for CI reproducibility. Do not bump it casually. +- **Toolchain is pinned.** `tools/wta/rust-toolchain.toml` pins the public channel to `1.93` for local development and GitHub automation. Azure DevOps explicitly overrides it with the matching `ms-prod-1.93` channel. Do not bump either version casually or let them drift. - **Static CRT on Windows.** The repo-root `.cargo/config.toml` forces `+crt-static` rustflags for all Windows MSVC targets (`x86_64`, `i686`, `aarch64`). Avoid dependencies that break under static CRT. - **Two supported build invocations — don't mix them.** Both of these are valid for WTA local dev: diff --git a/build/pipelines/templates-v2/job-build-project.yml b/build/pipelines/templates-v2/job-build-project.yml index c92657e6e7..8779d0c935 100644 --- a/build/pipelines/templates-v2/job-build-project.yml +++ b/build/pipelines/templates-v2/job-build-project.yml @@ -114,6 +114,7 @@ jobs: BuildTargetParameter: ' ' SelectedSigningFragments: ' ' MSBuildCacheParameters: ' ' + WtaRustToolchain: ms-prod-1.93 # When building the unpackaged distribution, build it in portable mode if it's Canary-branded ${{ if eq(parameters.branding, 'Canary') }}: UnpackagedBuildArguments: -PortableMode @@ -192,8 +193,9 @@ jobs: # 2. Crates resolve through a project-level Azure Artifact Cargo feed # with crates.io upstream, configured in .cargo/config.toml (repo root, # generated by CI — see "Create Cargo registry config" step below). - # 3. The Rust version uses the `ms-prod-X.YY` channel, pinned in - # `tools/wta/rust-toolchain.toml`. + # 3. Public tooling uses the version in `tools/wta/rust-toolchain.toml`. + # ADO explicitly selects the matching `ms-prod-X.YY` channel through + # `WtaRustToolchain` so internal builds remain 1ES-compliant. # 4. Component Governance scanning of Cargo.lock is auto-injected by # 1ES PT (no extra task needed here). # @@ -212,7 +214,7 @@ jobs: - task: RustInstaller@1 displayName: Install Rust toolchain (MSRustup) inputs: - rustVersion: ms-prod-1.93 + rustVersion: $(WtaRustToolchain) toolchainFeed: https://pkgs.dev.azure.com/microsoft/_packaging/RustTools/nuget/v3/index.json # NOTE: we deliberately do NOT set `cratesIoFeedOverride` here. That # parameter injects an env-var registry override named `ms_crates_io` @@ -274,6 +276,8 @@ jobs: # phase. Fetching upfront keeps `cargo build --frozen` fully offline. - script: cargo fetch --manifest-path tools/wta/Cargo.toml --locked displayName: Fetch crates (offline-friendly) + env: + RUSTUP_TOOLCHAIN: $(WtaRustToolchain) - pwsh: |- $ErrorActionPreference = 'Stop' @@ -292,6 +296,8 @@ jobs: } if ($LASTEXITCODE -ne 0) { throw "cargo build failed with exit code $LASTEXITCODE" } displayName: Build wta (Cargo) + env: + RUSTUP_TOOLCHAIN: $(WtaRustToolchain) - pwsh: |- $ErrorActionPreference = 'Stop' @@ -306,6 +312,8 @@ jobs: if ($LASTEXITCODE -ne 0) { throw "cargo test failed with exit code $LASTEXITCODE" } displayName: Test wta (Cargo) condition: ne(variables['BuildPlatform'], 'arm64') + env: + RUSTUP_TOOLCHAIN: $(WtaRustToolchain) - template: .\steps-install-vcpkg.yml diff --git a/build/scripts/Generate-WtaThirdPartyNotices.ps1 b/build/scripts/Generate-WtaThirdPartyNotices.ps1 index 680a277f80..45356b47ca 100644 --- a/build/scripts/Generate-WtaThirdPartyNotices.ps1 +++ b/build/scripts/Generate-WtaThirdPartyNotices.ps1 @@ -35,10 +35,9 @@ left byte-identical. .NOTES - Requires `cargo` on PATH. The repo's rust-toolchain.toml pins a - custom channel for CI builds; for metadata-only invocations bypass - it by setting `$env:RUSTUP_TOOLCHAIN = 'stable'` before invoking - the script. + Requires `cargo` on PATH. The repo pins a public Rust version in + tools/wta/rust-toolchain.toml. Set `$env:RUSTUP_TOOLCHAIN = 'stable'` + before invoking the script only when intentionally overriding that pin. .EXAMPLE PS> $env:RUSTUP_TOOLCHAIN = 'stable' diff --git a/doc/specs/acp-1.0-conductor-migration.md b/doc/specs/acp-1.0-conductor-migration.md index f4d53395a2..e712a722fe 100644 --- a/doc/specs/acp-1.0-conductor-migration.md +++ b/doc/specs/acp-1.0-conductor-migration.md @@ -971,8 +971,8 @@ connection/auth state machine, the tab registry) outside this spec's scope. ## Implementation status (Phase 0) -Branch `dev//acp-1.0-phase0`. Build/test from the **worktree root** (not -`tools/wta/src` — that dir's `rust-toolchain.toml` pins an uninstalled channel): +Branch `dev//acp-1.0-phase0`. Build/test from the **worktree root** so +Cargo discovers the repo-root configuration: `cargo build --manifest-path tools/wta/Cargo.toml`. Baseline = 1017 tests. **Done — chunk 1 (committed):** Cargo bump 0.10→1.0; dropped stabilized features diff --git a/tools/wta/Generate-Coverage.ps1 b/tools/wta/Generate-Coverage.ps1 index d8306de17b..604265e13b 100644 --- a/tools/wta/Generate-Coverage.ps1 +++ b/tools/wta/Generate-Coverage.ps1 @@ -6,11 +6,9 @@ Runs `cargo llvm-cov` over `tools/wta` and emits an HTML report (and optionally lcov) plus a console summary. - Toolchain note: `tools/wta/rust-toolchain.toml` pins the MS-internal - `ms-prod-1.93` channel, which isn't installed on dev boxes. This script - forces `RUSTUP_TOOLCHAIN=stable` (the same bypass the third-party-notices - generator uses) and runs from the repo root with `--manifest-path`, so the - pin never kicks in. Stable has had source-based coverage since 1.60. + Toolchain note: this script intentionally uses the public stable channel + instead of the version pinned in `tools/wta/rust-toolchain.toml`. Stable + has had source-based coverage since 1.60. Prerequisites (auto-installed unless -NoInstall): * rustup component `llvm-tools-preview` (on the stable toolchain) @@ -53,7 +51,7 @@ if (-not $OutputDir) { } New-Item -ItemType Directory -Force -Path $OutputDir | Out-Null -# Bypass the ms-prod toolchain pin; use stable everywhere in this process. +# Use stable everywhere in this process for coverage tooling. $env:RUSTUP_TOOLCHAIN = 'stable' function Test-Command([string]$Probe) { @@ -69,7 +67,7 @@ function Test-Command([string]$Probe) { Write-Host "==> Coverage for WTA crate" -ForegroundColor Cyan Write-Host " repo: $RepoRoot" Write-Host " output: $OutputDir" -Write-Host " toolchain: stable (pin bypassed)" +Write-Host " toolchain: stable" # Build rule (tools/wta/AGENTS.md): kill any live wta.exe first, or the # instrumented build can fail with 'Access is denied' on the locked binary. diff --git a/tools/wta/rust-toolchain.toml b/tools/wta/rust-toolchain.toml index 94c765cffc..0fa0778d8a 100644 --- a/tools/wta/rust-toolchain.toml +++ b/tools/wta/rust-toolchain.toml @@ -1,6 +1,4 @@ -# Pin Rust version for CI reproducibility. -# The ms-prod channel is resolved by MSRustup (RustInstaller ADO task). -# Local dev with standard rustup ignores the ms-prod- prefix and uses -# the latest stable toolchain instead. +# Pin the public Rust version for local development and GitHub automation. +# Azure DevOps explicitly overrides this with the matching ms-prod toolchain. [toolchain] -channel = "ms-prod-1.93" +channel = "1.93" From aa0c8524da81cdaad6a9c89cb7d9d01a2f1d886b Mon Sep 17 00:00:00 2001 From: "Kai Tao (from Dev Box)" Date: Fri, 31 Jul 2026 12:39:23 +0800 Subject: [PATCH 3/6] Validate WTA dependency attribution Add an offline CI check for generated Rust notices and ensure local WTA builds consistently discover the pinned public toolchain. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: fbe1d08b-e782-4b3e-b8d9-ccbde507a1a7 --- .github/dependabot.yml | 6 +-- .github/instructions/rust-wta.instructions.md | 7 +-- .../references/validation.md | 10 +++-- AGENTS.md | 8 +++- CONTRIBUTING.md | 2 +- .../templates-v2/job-build-project.yml | 22 +++++++++ .../scripts/Generate-WtaThirdPartyNotices.ps1 | 45 ++++++++++++++++++- build/scripts/New-WtaLocalInstaller.ps1 | 43 ++++++++++-------- doc/building-installer.md | 18 +++++--- doc/quick-start-local-dev.md | 8 ++-- doc/specs/acp-1.0-conductor-migration.md | 6 +-- tools/wta/AGENTS.md | 12 +++-- 12 files changed, 140 insertions(+), 47 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 02229cbbe9..bd38bf3892 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -20,6 +20,6 @@ updates: - "minor" - "patch" - # WTA statically links its Rust dependencies. After accepting an update, - # regenerate tools/wta/cgmanifest.json and the WTA block in NOTICE.md with - # build/scripts/Generate-WtaThirdPartyNotices.ps1. + # WTA statically links its Rust dependencies. Before merging an update, + # regenerate and commit tools/wta/cgmanifest.json and the WTA block in + # NOTICE.md with build/scripts/Generate-WtaThirdPartyNotices.ps1. diff --git a/.github/instructions/rust-wta.instructions.md b/.github/instructions/rust-wta.instructions.md index b589d24148..d9f383b162 100644 --- a/.github/instructions/rust-wta.instructions.md +++ b/.github/instructions/rust-wta.instructions.md @@ -16,11 +16,12 @@ The repo contains multiple Rust crates (e.g. `installer/bootstrap/`); this file' - **Two supported build invocations — don't mix them.** Both of these are valid for WTA local dev: ```bash - cargo build --manifest-path tools/wta/Cargo.toml # host target (bare target/) - cargo build --target x86_64-pc-windows-msvc --manifest-path tools/wta/Cargo.toml # explicit target + cd tools/wta + cargo build # host target (bare target/) + cargo build --target x86_64-pc-windows-msvc # explicit target ``` - Pick one and stay with it within a single dev session. The `CascadiaPackage.wapproj` deploy step prefers `tools/wta/target/x86_64-pc-windows-msvc//wta.exe` over the bare `tools/wta/target//wta.exe`, so if you build once with `--target` and later iterate with plain `cargo build`, the wapproj will silently keep deploying the stale explicit-target binary. See `tools/wta/README.md` and `tools/wta/AGENTS.md` for the host-target workflow. + Run Cargo from `tools/wta` so rustup discovers `rust-toolchain.toml`. Pick one invocation and stay with it within a single dev session. The `CascadiaPackage.wapproj` deploy step prefers `tools/wta/target/x86_64-pc-windows-msvc//wta.exe` over the bare `tools/wta/target//wta.exe`, so if you build once with `--target` and later iterate with plain `cargo build`, the wapproj will silently keep deploying the stale explicit-target binary. See `tools/wta/README.md` and `tools/wta/AGENTS.md` for the host-target workflow. ## Localization diff --git a/.github/skills/add-acp-agent-support/references/validation.md b/.github/skills/add-acp-agent-support/references/validation.md index 189d4e43e1..f6723a9a36 100644 --- a/.github/skills/add-acp-agent-support/references/validation.md +++ b/.github/skills/add-acp-agent-support/references/validation.md @@ -43,10 +43,12 @@ Add or update tests for: - invalid or empty delegate executable rejection; - policy filtering or settings serialization when those paths changed. -Run the WTA suite from the repository root: +Run the WTA suite from its crate directory so Cargo uses the pinned toolchain: ```powershell -cargo test --manifest-path tools\wta\Cargo.toml +Push-Location tools\wta +cargo test +Pop-Location ``` Do not treat a successful build as a substitute for tests; WTA test-only code @@ -59,7 +61,9 @@ Resolve and stop only the specific live WTA process IDs before rebuilding: ```powershell Get-Process wta -ErrorAction SilentlyContinue | ForEach-Object { Stop-Process -Id $_.Id -Force } -cargo build --target x86_64-pc-windows-msvc --manifest-path tools\wta\Cargo.toml +Push-Location tools\wta +cargo build --target x86_64-pc-windows-msvc +Pop-Location ``` Always use the explicit target for a package-validation cycle because diff --git a/AGENTS.md b/AGENTS.md index 07fdda1e68..3fdc86cabb 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -330,7 +330,9 @@ There are two independent build systems. **Both must be built** before F5. # Kill stale WTA processes first taskkill //f //im wta.exe 2>/dev/null; true -cargo build --target x86_64-pc-windows-msvc --manifest-path tools/wta/Cargo.toml +pushd tools/wta +cargo build --target x86_64-pc-windows-msvc +popd # Output: tools/wta/target/x86_64-pc-windows-msvc/debug/wta.exe # # Always pass --target explicitly — the wapproj prefers @@ -379,7 +381,9 @@ flow. Static assets such as `wt-agent-hooks` are not `wta.exe`-only changes. ```bash # 1. Build WTA (always use --target — see note above) taskkill //f //im wta.exe 2>/dev/null; true -cargo build --target x86_64-pc-windows-msvc --manifest-path tools/wta/Cargo.toml +pushd tools/wta +cargo build --target x86_64-pc-windows-msvc +popd # 2. Build & run Terminal from VS # F5 in Visual Studio (CascadiaPackage project) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 83a2524702..ae6f831f62 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -164,7 +164,7 @@ Once you've discussed your proposed feature/fix/etc. with a team member, and you Testing is a key component in the development workflow. The C++ Terminal and Console code uses TAEF (the Test Authoring and Execution Framework); the Rust WTA crate uses standard `cargo test`. -If your changes affect existing test cases, or you're working on brand new features and also the accompanying test cases, see [TAEF](./doc/TAEF.md) for more information about how to validate the C++ side locally, and run `cargo test --manifest-path tools/wta/Cargo.toml` for the Rust side. +If your changes affect existing test cases, or you're working on brand new features and also the accompanying test cases, see [TAEF](./doc/TAEF.md) for more information about how to validate the C++ side locally. For the Rust side, run `cargo test` from `tools/wta` so Cargo uses the pinned toolchain. ### Code Review diff --git a/build/pipelines/templates-v2/job-build-project.yml b/build/pipelines/templates-v2/job-build-project.yml index 8779d0c935..62c50934e9 100644 --- a/build/pipelines/templates-v2/job-build-project.yml +++ b/build/pipelines/templates-v2/job-build-project.yml @@ -211,6 +211,17 @@ jobs: # the C++ build environment), so a misconfigured vcpkg/VS install still # cannot poison Rust. # ──────────────────────────────────────────────────────────────────────── + - pwsh: |- + $toolchainFile = Get-Content -Raw tools/wta/rust-toolchain.toml + if ($toolchainFile -notmatch '(?m)^\s*channel\s*=\s*"(?\d+\.\d+)"\s*$') { + throw 'tools/wta/rust-toolchain.toml must pin a public major.minor Rust channel.' + } + $expectedInternalChannel = "ms-prod-$($Matches.version)" + if ('$(WtaRustToolchain)' -ne $expectedInternalChannel) { + throw "WtaRustToolchain must be $expectedInternalChannel to match the public Rust pin." + } + displayName: Verify public and internal Rust pins match + - task: RustInstaller@1 displayName: Install Rust toolchain (MSRustup) inputs: @@ -279,6 +290,17 @@ jobs: env: RUSTUP_TOOLCHAIN: $(WtaRustToolchain) + - pwsh: |- + $ErrorActionPreference = 'Stop' + & .\build\scripts\Generate-WtaThirdPartyNotices.ps1 -Verify + if ($LASTEXITCODE -ne 0) { + throw "WTA third-party notice verification failed with exit code $LASTEXITCODE" + } + displayName: Verify WTA third-party notices + condition: and(succeeded(), eq(variables['BuildPlatform'], 'x64')) + env: + RUSTUP_TOOLCHAIN: $(WtaRustToolchain) + - pwsh: |- $ErrorActionPreference = 'Stop' $triple = switch ('$(BuildPlatform)') { diff --git a/build/scripts/Generate-WtaThirdPartyNotices.ps1 b/build/scripts/Generate-WtaThirdPartyNotices.ps1 index 45356b47ca..38f686e378 100644 --- a/build/scripts/Generate-WtaThirdPartyNotices.ps1 +++ b/build/scripts/Generate-WtaThirdPartyNotices.ps1 @@ -39,12 +39,19 @@ tools/wta/rust-toolchain.toml. Set `$env:RUSTUP_TOOLCHAIN = 'stable'` before invoking the script only when intentionally overriding that pin. +.PARAMETER Verify + Verify that cgmanifest.json and the generated NOTICE block contain exactly + the runtime Cargo dependency versions without rewriting either artifact or + contacting external license sources. + .EXAMPLE PS> $env:RUSTUP_TOOLCHAIN = 'stable' PS> .\build\scripts\Generate-WtaThirdPartyNotices.ps1 #> [CmdletBinding()] -param() +param( + [switch]$Verify +) $ErrorActionPreference = 'Stop' @@ -129,6 +136,42 @@ foreach ($pkgId in $seen) { $attributed = $attributed | Sort-Object name, version Write-Host "Crates to attribute: $($attributed.Count)" -ForegroundColor Cyan +if ($Verify) { + $expected = @($attributed | ForEach-Object { "$($_.name)@$($_.version)" } | Sort-Object) + + $cgManifest = Get-Content -Raw $cgManifestPath | ConvertFrom-Json -Depth 20 + $actualCg = @( + $cgManifest.Registrations | + ForEach-Object { "$($_.component.cargo.name)@$($_.component.cargo.version)" } | + Sort-Object + ) + + $notice = Get-Content -Raw $noticePath + $beginMarker = '' + $beginIndex = $notice.IndexOf($beginMarker, [StringComparison]::Ordinal) + $endIndex = $notice.IndexOf($endMarker, [StringComparison]::Ordinal) + if ($beginIndex -lt 0 -or $endIndex -le $beginIndex) { + throw 'NOTICE.md does not contain a valid WTA Rust dependency block.' + } + + $noticeBlock = $notice.Substring($beginIndex, $endIndex - $beginIndex) + $actualNotice = @( + [regex]::Matches($noticeBlock, '(?m)^- \*\*(?[^*]+)\*\* v(?\S+) --') | + ForEach-Object { "$($_.Groups['name'].Value)@$($_.Groups['version'].Value)" } | + Sort-Object + ) + + $cgDiff = @(Compare-Object $expected $actualCg) + $noticeDiff = @(Compare-Object $expected $actualNotice) + if ($cgDiff.Count -gt 0 -or $noticeDiff.Count -gt 0) { + throw 'WTA third-party artifacts are stale. Run build/scripts/Generate-WtaThirdPartyNotices.ps1 and commit NOTICE.md and tools/wta/cgmanifest.json.' + } + + Write-Host "Verified $($expected.Count) WTA Rust dependencies in cgmanifest.json and NOTICE.md." -ForegroundColor Green + return +} + # --------------------------------------------------------------------------- # 4. SPDX normalization + atomic-token decomposition. # - `X/Y` -> `X OR Y` (legacy cargo notation) diff --git a/build/scripts/New-WtaLocalInstaller.ps1 b/build/scripts/New-WtaLocalInstaller.ps1 index ea7f297817..2bfe683e13 100644 --- a/build/scripts/New-WtaLocalInstaller.ps1 +++ b/build/scripts/New-WtaLocalInstaller.ps1 @@ -87,14 +87,25 @@ function Find-CargoPath { } function Get-InstalledRustTargets { + param( + [Parameter(Mandatory = $true)] + [string]$ToolchainRoot + ) + $rustupPath = Join-Path $env:USERPROFILE '.cargo\bin\rustup.exe' if (-not (Test-Path $rustupPath -PathType Leaf)) { return @() } - $targets = & $rustupPath target list --installed - if ($LASTEXITCODE -ne 0) { - throw 'rustup target list --installed failed.' + Push-Location $ToolchainRoot + try { + $targets = & $rustupPath target list --installed + if ($LASTEXITCODE -ne 0) { + throw 'rustup target list --installed failed.' + } + } + finally { + Pop-Location } return @($targets | Where-Object { -not [string]::IsNullOrWhiteSpace($_) }) @@ -112,19 +123,14 @@ function Invoke-RustBuild { [string]$RustTarget, [Parameter(Mandatory = $true)] - [string]$RepoRoot + [string]$ToolchainRoot ) # Cargo's config discovery walks up from the current working directory, - # not from the manifest path. Pin CWD to the repo root so Cargo finds the - # repo-root .cargo/config.toml that supplies `+crt-static` — even when - # this script is launched from outside the repo. - # - # Important: do NOT push into the manifest's directory. tools/wta/ has its - # own rust-toolchain.toml, so letting rustup discover that file from CWD - # can change toolchain resolution compared to the repo-root configuration - # this script relies on for local builds. - Push-Location $RepoRoot + # not from the manifest path. Build both Rust binaries from tools/wta so + # rustup discovers the pinned public toolchain and Cargo walks up to the + # repo-root .cargo/config.toml that supplies `+crt-static`. + Push-Location $ToolchainRoot try { & $CargoPath build --manifest-path $ManifestPath --release --target $RustTarget if ($LASTEXITCODE -ne 0) { @@ -452,10 +458,11 @@ if ($BuildTerminal -and $installerVersion -ne $expectedManifestIdentity.Version) $cargoPath = Find-CargoPath $rustTarget = Get-RustTarget -PlatformName $Platform -$installedTargets = Get-InstalledRustTargets +$wtaRoot = Join-Path $repoRoot 'tools\wta' +$installedTargets = Get-InstalledRustTargets -ToolchainRoot $wtaRoot if ($installedTargets.Count -gt 0 -and $installedTargets -notcontains $rustTarget) { - throw "Rust target $rustTarget is not installed. Install it with rustup target add $rustTarget." + throw "Rust target $rustTarget is not installed for the WTA toolchain. Run rustup target add $rustTarget from tools/wta." } $timestamp = Get-Date -Format 'yyyyMMdd-HHmmss' @@ -498,8 +505,8 @@ if ($SkipWtaBuild) { $resolvedWtaExePath = Resolve-AbsolutePath -Path $WtaExePath } else { Write-Status "Building wta.exe for $rustTarget with a static CRT ..." - $manifestPath = Join-Path $repoRoot 'tools\wta\Cargo.toml' - Invoke-RustBuild -CargoPath $cargoPath -ManifestPath $manifestPath -RustTarget $rustTarget -RepoRoot $repoRoot + $manifestPath = Join-Path $wtaRoot 'Cargo.toml' + Invoke-RustBuild -CargoPath $cargoPath -ManifestPath $manifestPath -RustTarget $rustTarget -ToolchainRoot $wtaRoot $resolvedWtaExePath = Join-Path $repoRoot ("tools\wta\target\{0}\release\wta.exe" -f $rustTarget) } @@ -553,7 +560,7 @@ Copy-Item -Path $installerCmd -Destination (Join-Path $installerSourceRoot 'inst Copy-Item -Path $payloadZip -Destination (Join-Path $installerSourceRoot 'payload.zip') -Force Write-Status "Building installer bootstrap for $rustTarget ..." -Invoke-RustBuild -CargoPath $cargoPath -ManifestPath $installerBootstrapManifest -RustTarget $rustTarget -RepoRoot $repoRoot +Invoke-RustBuild -CargoPath $cargoPath -ManifestPath $installerBootstrapManifest -RustTarget $rustTarget -ToolchainRoot $wtaRoot $bootstrapExePath = Join-Path $repoRoot ("installer\bootstrap\target\{0}\release\intelligent-terminal-installer-bootstrap.exe" -f $rustTarget) if (-not (Test-Path $bootstrapExePath -PathType Leaf)) { throw "Installer bootstrap not found: $bootstrapExePath" diff --git a/doc/building-installer.md b/doc/building-installer.md index 4174a3a97d..9edc0ad5e3 100644 --- a/doc/building-installer.md +++ b/doc/building-installer.md @@ -23,9 +23,11 @@ intelligent-terminal---msix.zip - Visual Studio 2022 Enterprise with C++ desktop & UWP workloads - Windows 10 SDK (10.0.22621.0+) - Rust toolchain (`cargo`, `rustup`) with both targets: - ``` + ```powershell + Push-Location tools/wta rustup target add x86_64-pc-windows-msvc rustup target add aarch64-pc-windows-msvc + Pop-Location ``` --- @@ -37,7 +39,7 @@ Five lines, in order. Step details below. ```powershell # 0. Bump manifest + _sign_msix.cmd to the new version # 1. (skipped — cert is committed) -# 2. cargo build --release --target {x86_64,aarch64}-pc-windows-msvc --manifest-path tools/wta/Cargo.toml +# 2. From tools/wta: cargo build --release --target {x86_64,aarch64}-pc-windows-msvc # 3. .\_build_msix_x64.cmd AND THEN .\_build_msix_arm64.cmd # serial — see note # 4. .\_sign_msix.cmd # 5. powershell -File build\scripts\assemble-msix-zip.ps1 -Version 0.7.0.X -Arch x64 @@ -75,11 +77,15 @@ That script produces `CascadiaPackage_TemporaryKey.pfx` (gitignored) — you'd t ### Step 2: Build `wta.exe` ```powershell +Push-Location tools/wta + # x64 -cargo build --release --target x86_64-pc-windows-msvc --manifest-path tools/wta/Cargo.toml +cargo build --release --target x86_64-pc-windows-msvc # ARM64 (cross-compile) -cargo build --release --target aarch64-pc-windows-msvc --manifest-path tools/wta/Cargo.toml +cargo build --release --target aarch64-pc-windows-msvc + +Pop-Location ``` These two are independent — run them in parallel if you want. @@ -258,8 +264,8 @@ Options (pass to `install.cmd`): `/quiet`, `/nopath`, `/noshortcuts` | Goal | Command | |------|---------| | Generate dev cert (one-time / expired) | [`powershell -File build\scripts\New-DevSigningCert.ps1`](../build/scripts/New-DevSigningCert.ps1) | -| Build wta (x64) | `cargo build --release --target x86_64-pc-windows-msvc --manifest-path tools/wta/Cargo.toml` | -| Build wta (ARM64) | `cargo build --release --target aarch64-pc-windows-msvc --manifest-path tools/wta/Cargo.toml` | +| Build wta (x64) | From `tools/wta`: `cargo build --release --target x86_64-pc-windows-msvc` | +| Build wta (ARM64) | From `tools/wta`: `cargo build --release --target aarch64-pc-windows-msvc` | | Build MSIX (x64) | [`.\_build_msix_x64.cmd`](../_build_msix_x64.cmd) | | Build MSIX (ARM64) | [`.\_build_msix_arm64.cmd`](../_build_msix_arm64.cmd) | | Sign both MSIXs | [`.\_sign_msix.cmd`](../_sign_msix.cmd) | diff --git a/doc/quick-start-local-dev.md b/doc/quick-start-local-dev.md index b510f560bd..110c8440d3 100644 --- a/doc/quick-start-local-dev.md +++ b/doc/quick-start-local-dev.md @@ -27,7 +27,9 @@ the setup needed. For instance, ```powershell - cargo build --target x86_64-pc-windows-msvc --manifest-path tools/wta/Cargo.toml + Push-Location tools/wta + cargo build --target x86_64-pc-windows-msvc + Pop-Location ``` 2. In Visual Studio: - Set startup project: **`CascadiaPackage`** @@ -43,7 +45,7 @@ first build is slow; later ones are incremental. | Changed | Do this | |---------|---------| -| **Rust** (`tools/wta/`) | Rebuild via `cargo build`. For instance,
`cargo build --target x86_64-pc-windows-msvc --manifest-path tools/wta/Cargo.toml` | +| **Rust** (`tools/wta/`) | Rebuild from `tools/wta` so Cargo uses the pinned toolchain. For instance,
`Push-Location tools/wta; cargo build --target x86_64-pc-windows-msvc; Pop-Location` | | **C++** (`src/`) | Press **F5** in Visual Studio | `cargo build` is incremental (seconds for a small change). To see a WTA change inside the running @@ -56,7 +58,7 @@ Terminal (agent pane, autofix), press **F5** afterward so the new `wta.exe` is c | Side | Command | |------|---------| -| **Rust** | `cargo test --manifest-path tools/wta/Cargo.toml` | +| **Rust** | `Push-Location tools/wta; cargo test; Pop-Location` | | **C++** (TAEF) | `runut.cmd` (unit), `runft.cmd` (feature), `runuia.cmd` (UIA), from a dev environment | Run one C++ test with `te.exe /name:`. See [building.md](./building.md) for diff --git a/doc/specs/acp-1.0-conductor-migration.md b/doc/specs/acp-1.0-conductor-migration.md index e712a722fe..adc9f669a3 100644 --- a/doc/specs/acp-1.0-conductor-migration.md +++ b/doc/specs/acp-1.0-conductor-migration.md @@ -971,9 +971,9 @@ connection/auth state machine, the tab registry) outside this spec's scope. ## Implementation status (Phase 0) -Branch `dev//acp-1.0-phase0`. Build/test from the **worktree root** so -Cargo discovers the repo-root configuration: -`cargo build --manifest-path tools/wta/Cargo.toml`. Baseline = 1017 tests. +Branch `dev//acp-1.0-phase0`. Build/test from `tools/wta` so Cargo +discovers the pinned toolchain and repo-root configuration: +`cargo build`. Baseline = 1017 tests. **Done — chunk 1 (committed):** Cargo bump 0.10→1.0; dropped stabilized features `unstable_session_list`/`unstable_session_model`; ~538 schema-path moves diff --git a/tools/wta/AGENTS.md b/tools/wta/AGENTS.md index 1518e7a18b..07a63b5673 100644 --- a/tools/wta/AGENTS.md +++ b/tools/wta/AGENTS.md @@ -215,7 +215,9 @@ For normal local WTA development, always produce the binary at `tools/wta/target - Before running `cargo build` for WTA, kill any active `wta.exe` processes first. A live shared-host session can keep `target/debug/wta.exe` locked and make the build fail with `Access is denied`. - Preferred PowerShell sequence: - `Get-Process wta -ErrorAction SilentlyContinue | Stop-Process -Force` - - `cargo build --manifest-path tools/wta/Cargo.toml` + - `Push-Location tools/wta` + - `cargo build` + - `Pop-Location` - Do not switch to an alternate `--target-dir` just to work around a locked `wta.exe` unless that is explicitly the task. The default expectation is to refresh `tools/wta/target/debug/wta.exe`. ## Test Rule @@ -225,9 +227,11 @@ For any WTA change that is covered by — or should be covered by — unit tests the C++ F5 / `bcz` flow do **not** compile or run the `#[cfg(test)]` code, so a green build says nothing about the tests. -- Kill any live `wta.exe` first (same as the Build Rule), then run from the repo - root so the manifest's toolchain pin doesn't force the unavailable channel: - - `cargo test --manifest-path tools/wta/Cargo.toml` +- Kill any live `wta.exe` first (same as the Build Rule), then run from + `tools/wta` so rustup discovers the pinned public toolchain: + - `Push-Location tools/wta` + - `cargo test` + - `Pop-Location` - All tests must pass before you push. CI runs the same `cargo test` and fails the build on any failure (`build/pipelines/templates-v2/job-build-project.yml`), so a local run just From 4c3773d0b9b0a547b3679a1ac1482422b2e72782 Mon Sep 17 00:00:00 2001 From: "Kai Tao (from Dev Box)" Date: Fri, 31 Jul 2026 14:17:30 +0800 Subject: [PATCH 4/6] Add WTA dependency update skill Guide agents through regenerating, reviewing, and verifying Rust third-party attribution for Dependabot and Cargo dependency changes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: fbe1d08b-e782-4b3e-b8d9-ccbde507a1a7 --- .../wta-rust-dependency-update/SKILL.md | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 .github/skills/wta-rust-dependency-update/SKILL.md diff --git a/.github/skills/wta-rust-dependency-update/SKILL.md b/.github/skills/wta-rust-dependency-update/SKILL.md new file mode 100644 index 0000000000..b42eda73b7 --- /dev/null +++ b/.github/skills/wta-rust-dependency-update/SKILL.md @@ -0,0 +1,87 @@ +--- +name: wta-rust-dependency-update +description: 'Regenerate and verify WTA Rust third-party attribution after Cargo dependency changes. Use when handling Dependabot PRs for /tools/wta, editing tools/wta/Cargo.toml or Cargo.lock, changing dependency features, finishing or reviewing a WTA dependency PR, or fixing stale NOTICE.md or cgmanifest CI failures.' +--- + +# WTA Rust Dependency Update + +Keep WTA Cargo dependency changes synchronized with the generated Component +Governance manifest and third-party notices required for the statically linked +`wta.exe`. + +## When to Use This Skill + +- Complete or review a Dependabot PR for `/tools/wta`. +- Add, remove, or upgrade a dependency in `tools/wta/Cargo.toml`. +- Update `tools/wta/Cargo.lock` in a way that changes runtime dependencies. +- Change dependency features that add or remove transitive runtime crates. +- Fix the **Verify WTA third-party notices** CI failure. + +Do not run this workflow for Rust source-only changes that leave the dependency +graph unchanged. + +## Prerequisites + +- Run commands from the repository root. +- Use PowerShell 7 or later (`pwsh`), not Windows PowerShell 5.1. +- Read `tools/wta/AGENTS.md` and the WTA Rust instructions before changing + Cargo files. +- Preserve unrelated worktree changes; never reset or overwrite them. + +## Workflow + +1. Inspect the PR or worktree diff and confirm which Cargo manifest, lockfile, + or feature change triggered the dependency update. +2. Regenerate both attribution artifacts: + + ```powershell + pwsh -File .\build\scripts\Generate-WtaThirdPartyNotices.ps1 + ``` + +3. Review the dependency and generated changes together: + + ```powershell + git diff --check + git diff -- tools/wta/Cargo.toml tools/wta/Cargo.lock ` + tools/wta/cgmanifest.json NOTICE.md + ``` + +4. Run the same offline consistency check used by CI: + + ```powershell + pwsh -File .\build\scripts\Generate-WtaThirdPartyNotices.ps1 -Verify + ``` + +5. Finish according to the result: + - If generation produced changes, keep `NOTICE.md` and + `tools/wta/cgmanifest.json` in the same PR as the Cargo update. + - For a Dependabot branch, add a follow-up commit; do not rewrite the bot + commit unless the user explicitly requests it. + - If generation produced no changes, do not create an empty attribution + commit; report that `-Verify` passed. + - Commit or push only when requested. Include all generated attribution + changes together and describe them as generated output. + +## Completion Gate + +- The normal generator exits successfully. +- Generated changes contain only dependency attribution expected from the + Cargo update. +- `git diff --check` passes. +- `Generate-WtaThirdPartyNotices.ps1 -Verify` reports the same runtime crate + count in `cgmanifest.json` and `NOTICE.md`. +- No dependency PR is declared ready while generated artifacts are stale. + +## Gotchas + +- **Do not edit generated attribution text by hand.** Fix the Cargo metadata or + generator and rerun it. +- **Do not run only `-Verify` after a dependency change.** Verification detects + stale artifacts but does not regenerate them. +- **Do not assume every lockfile change needs an attribution diff.** Dev-only, + build-only, or platform-excluded dependency changes may leave runtime output + unchanged. +- **Do not omit `NOTICE.md` when `cgmanifest.json` changes, or vice versa.** + They are generated from the same runtime dependency graph. +- **Review major updates separately.** In particular, + `agent-client-protocol*` updates require focused ACP compatibility review. From 880fbade8075aede3a0ca4b0d5465ba497db2f7b Mon Sep 17 00:00:00 2001 From: "Kai Tao (from Dev Box)" Date: Fri, 31 Jul 2026 14:49:16 +0800 Subject: [PATCH 5/6] Harden WTA dependency update workflow Use the pinned toolchain for generation, make verification locked and offline, and separate review-only skill behavior from mutating completion steps. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: fbe1d08b-e782-4b3e-b8d9-ccbde507a1a7 --- .github/instructions/rust-wta.instructions.md | 1 - .../wta-rust-dependency-update/SKILL.md | 40 ++++++++++++++----- .../scripts/Generate-WtaThirdPartyNotices.ps1 | 31 +++++++++----- tools/wta/AGENTS.md | 1 - 4 files changed, 53 insertions(+), 20 deletions(-) diff --git a/.github/instructions/rust-wta.instructions.md b/.github/instructions/rust-wta.instructions.md index d9f383b162..60caa764aa 100644 --- a/.github/instructions/rust-wta.instructions.md +++ b/.github/instructions/rust-wta.instructions.md @@ -48,7 +48,6 @@ User-facing strings go through `t!(...)` (rust-i18n) — see `rust-localization. `tools/wta/cgmanifest.json` (Component Governance manifest) and the `` block in `/NOTICE.md` are **generated** from `cargo metadata`. Whenever you change the dependency graph — add/remove/upgrade a direct dep in `tools/wta/Cargo.toml`, run a `cargo update` that substantially shifts `Cargo.lock`, or flip a feature flag that pulls in/drops transitive crates — regenerate both and commit the diff alongside the Cargo change: ```powershell -$env:RUSTUP_TOOLCHAIN = 'stable' # bypass the rust-toolchain.toml pin pwsh -File .\build\scripts\Generate-WtaThirdPartyNotices.ps1 ``` diff --git a/.github/skills/wta-rust-dependency-update/SKILL.md b/.github/skills/wta-rust-dependency-update/SKILL.md index b42eda73b7..bbae0f8ea7 100644 --- a/.github/skills/wta-rust-dependency-update/SKILL.md +++ b/.github/skills/wta-rust-dependency-update/SKILL.md @@ -1,6 +1,6 @@ --- name: wta-rust-dependency-update -description: 'Regenerate and verify WTA Rust third-party attribution after Cargo dependency changes. Use when handling Dependabot PRs for /tools/wta, editing tools/wta/Cargo.toml or Cargo.lock, changing dependency features, finishing or reviewing a WTA dependency PR, or fixing stale NOTICE.md or cgmanifest CI failures.' +description: 'Regenerate, review, test, and verify WTA Rust dependency updates and third-party attribution. Use when handling Dependabot PRs for /tools/wta, editing tools/wta/Cargo.toml or Cargo.lock, changing dependency features, finishing or reviewing a WTA dependency PR, or fixing stale NOTICE.md or cgmanifest CI failures.' --- # WTA Rust Dependency Update @@ -32,17 +32,28 @@ graph unchanged. 1. Inspect the PR or worktree diff and confirm which Cargo manifest, lockfile, or feature change triggered the dependency update. -2. Regenerate both attribution artifacts: +2. Choose the safe mode: + - For a review-only request, do not run the mutating generator. Inspect the + existing diff and continue with the offline `-Verify` command in step 4. + - When completing or fixing the update, regenerate both attribution + artifacts: - ```powershell - pwsh -File .\build\scripts\Generate-WtaThirdPartyNotices.ps1 - ``` + ```powershell + pwsh -File .\build\scripts\Generate-WtaThirdPartyNotices.ps1 + ``` 3. Review the dependency and generated changes together: ```powershell - git diff --check - git diff -- tools/wta/Cargo.toml tools/wta/Cargo.lock ` + $baseRef = gh pr view --json baseRefName --jq .baseRefName 2>$null + if ($LASTEXITCODE -eq 0) { + git fetch origin $baseRef + $compareRef = git merge-base HEAD "origin/$baseRef" + } else { + $compareRef = 'HEAD' + } + git diff --check $compareRef -- + git diff $compareRef -- tools/wta/Cargo.toml tools/wta/Cargo.lock ` tools/wta/cgmanifest.json NOTICE.md ``` @@ -52,7 +63,17 @@ graph unchanged. pwsh -File .\build\scripts\Generate-WtaThirdPartyNotices.ps1 -Verify ``` -5. Finish according to the result: +5. Test and finish: + - Run the WTA test suite with the pinned toolchain: + + ```powershell + Get-Process wta -ErrorAction SilentlyContinue | + ForEach-Object { Stop-Process -Id $_.Id -Force } + Push-Location tools/wta + cargo test --locked + Pop-Location + ``` + - If generation produced changes, keep `NOTICE.md` and `tools/wta/cgmanifest.json` in the same PR as the Cargo update. - For a Dependabot branch, add a follow-up commit; do not rewrite the bot @@ -64,12 +85,13 @@ graph unchanged. ## Completion Gate -- The normal generator exits successfully. +- In update/completion mode, the normal generator exits successfully. - Generated changes contain only dependency attribution expected from the Cargo update. - `git diff --check` passes. - `Generate-WtaThirdPartyNotices.ps1 -Verify` reports the same runtime crate count in `cgmanifest.json` and `NOTICE.md`. +- `cargo test --locked` passes from `tools/wta`. - No dependency PR is declared ready while generated artifacts are stale. ## Gotchas diff --git a/build/scripts/Generate-WtaThirdPartyNotices.ps1 b/build/scripts/Generate-WtaThirdPartyNotices.ps1 index 38f686e378..52c5a75e3a 100644 --- a/build/scripts/Generate-WtaThirdPartyNotices.ps1 +++ b/build/scripts/Generate-WtaThirdPartyNotices.ps1 @@ -35,9 +35,9 @@ left byte-identical. .NOTES - Requires `cargo` on PATH. The repo pins a public Rust version in - tools/wta/rust-toolchain.toml. Set `$env:RUSTUP_TOOLCHAIN = 'stable'` - before invoking the script only when intentionally overriding that pin. + Requires `cargo` on PATH. Cargo runs from tools/wta so rustup discovers + the repository's pinned public toolchain. CI can explicitly override it + with RUSTUP_TOOLCHAIN. .PARAMETER Verify Verify that cgmanifest.json and the generated NOTICE block contain exactly @@ -45,7 +45,6 @@ contacting external license sources. .EXAMPLE - PS> $env:RUSTUP_TOOLCHAIN = 'stable' PS> .\build\scripts\Generate-WtaThirdPartyNotices.ps1 #> [CmdletBinding()] @@ -77,11 +76,25 @@ $targetTriple = 'x86_64-pc-windows-msvc' # needed in this script). # --------------------------------------------------------------------------- Write-Host "Running cargo metadata for $wtaRoot\Cargo.toml (--filter-platform $targetTriple)" -ForegroundColor Cyan -$cargoJson = & cargo metadata --format-version 1 ` - --filter-platform $targetTriple ` - --manifest-path (Join-Path $wtaRoot 'Cargo.toml') -if ($LASTEXITCODE -ne 0) { - throw "cargo metadata failed with exit code $LASTEXITCODE. Is cargo on PATH? Try `$env:RUSTUP_TOOLCHAIN='stable'." +$cargoArgs = @( + 'metadata' + '--format-version', '1' + '--filter-platform', $targetTriple + '--manifest-path', (Join-Path $wtaRoot 'Cargo.toml') +) +if ($Verify) { + $cargoArgs += @('--locked', '--offline') +} + +Push-Location $wtaRoot +try { + $cargoJson = & cargo @cargoArgs + if ($LASTEXITCODE -ne 0) { + throw "cargo metadata failed with exit code $LASTEXITCODE. Is cargo on PATH?" + } +} +finally { + Pop-Location } $meta = $cargoJson | ConvertFrom-Json -Depth 100 diff --git a/tools/wta/AGENTS.md b/tools/wta/AGENTS.md index 07a63b5673..726037c0ff 100644 --- a/tools/wta/AGENTS.md +++ b/tools/wta/AGENTS.md @@ -269,7 +269,6 @@ next to `Generate-ThirdPartyNotices.ps1` (the existing MD-to-HTML converter the build pipeline runs): ```powershell -$env:RUSTUP_TOOLCHAIN = 'stable' # bypass the repo's rust-toolchain.toml pin pwsh -File .\build\scripts\Generate-WtaThirdPartyNotices.ps1 ``` From 2eabd3d1cc4279f18325f274af63cccd40f5ea93 Mon Sep 17 00:00:00 2001 From: "Kai Tao (from Dev Box)" Date: Fri, 31 Jul 2026 15:21:53 +0800 Subject: [PATCH 6/6] Normalize generated license whitespace Strip trailing whitespace from every generated license-text line so dependency notice updates pass diff checks. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: fbe1d08b-e782-4b3e-b8d9-ccbde507a1a7 --- NOTICE.md | 2 +- build/scripts/Generate-WtaThirdPartyNotices.ps1 | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/NOTICE.md b/NOTICE.md index c7e77e2812..1d433fbe8e 100644 --- a/NOTICE.md +++ b/NOTICE.md @@ -1023,7 +1023,7 @@ Applies to 2 crate(s) (directly or via composite identifiers): moxcms v0.8.1, px _Canonical text reproduced from upstream `SPDX:BSD-3-Clause`:_ ``` -Copyright (c) . +Copyright (c) . Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/build/scripts/Generate-WtaThirdPartyNotices.ps1 b/build/scripts/Generate-WtaThirdPartyNotices.ps1 index 52c5a75e3a..73d39a9083 100644 --- a/build/scripts/Generate-WtaThirdPartyNotices.ps1 +++ b/build/scripts/Generate-WtaThirdPartyNotices.ps1 @@ -530,7 +530,8 @@ foreach ($atom in ($atomToCrates.Keys | Sort-Object)) { $out.Add("_Canonical text reproduced from upstream ``$($src.Name)``:_") $out.Add('') $out.Add('```') - $out.Add($src.Text.TrimEnd()) + $normalizedText = (($src.Text -split "`r?`n") | ForEach-Object { $_.TrimEnd() }) -join "`n" + $out.Add($normalizedText.TrimEnd()) $out.Add('```') } else { $out.Add('```')