Skip to content

fix(scripts): link semver-checks baselines with rust-lld - #642

Open
Kateřina Churanová (kate-shine) wants to merge 1 commit into
microsoft:mainfrom
kate-shine:u/kchuranov/fix-semver-checks-max-path
Open

fix(scripts): link semver-checks baselines with rust-lld#642
Kateřina Churanová (kate-shine) wants to merge 1 commit into
microsoft:mainfrom
kate-shine:u/kchuranov/fix-semver-checks-max-path

Conversation

@kate-shine

@kate-shine Kateřina Churanová (kate-shine) commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Problem

On Windows, scripts/release-packages.ps1 and scripts/ci/semver-report.ps1 fail during the
cargo semver-checks baseline build:

LINK : fatal error LNK1104: cannot open file '...\deps\<crate>-<hash>.exe'

cargo-semver-checks nests its baseline registry checkout under the workspace target
directory, so intermediate artifact paths grow well past 260 characters. The MSVC link.exe
is not long-path aware — it fails regardless of the LongPathsEnabled registry setting
or a long-path-aware manifest on the calling process.

Fix

Link the baseline build with rust-lld, which prepends the \\?\ prefix internally and
handles those depths.

Measured on a dev machine with CARGO_TARGET_DIR at 239 characters:

Linker Result
link.exe (default) LNK1104: cannot open file ...\debug\deps\<crate>.exe
rust-lld linked successfully

How the override is applied

Invoke-SemverChecksCli sets CARGO_TARGET_<HOST_TRIPLE>_LINKER (e.g.
CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_LINKER) to rust-lld.exe for the duration of the call,
restoring or removing the variable in a finally.

That variable is just the environment spelling of [target.<triple>] linker = "..." in
.cargo/config.toml — the same knob, applied only where the problem is rather than to every
build in the repo. CI runs in short paths and does not have this problem, so switching its
linker would be blast radius for no gain.

An environment variable is used rather than a CLI flag because it is the only channel that
reaches the build that actually overflows MAX_PATH. cargo-semver-checks (0.47.0) exposes
no flag to forward cargo configuration, and as an external subcommand it receives
--config itself rather than letting cargo interpret it — while the failing build is the one
the tool spawns internally.

Two things keep the implementation small:

  • The bare name is enough. rustc puts its own linker directory on PATH when it
    invokes the linker, so rust-lld resolves without locating the sysroot. No flavor flag is
    needed either — rustc infers the link flavor from the rust-lld stem.
  • Only the linker is overridden. Deliberately not via RUSTFLAGS, which replaces
    target.<triple>.rustflags from .cargo/config.toml and would silently drop the
    repository's -C target-cpu=x86-64-v3. Verified with a verbose build that the linker
    override and the repo's target-cpu flag coexist.

Non-Windows platforms are unchanged; their default linkers already handle long paths.

Known limitation

This fixes the link step only. cl.exe / lib.exe invoked by cc-based build scripts
remain MAX_PATH-bound. In practice the semver-checks baseline builds have not hit that, but
it is a narrower guarantee than relocating the whole target directory would give.

Also in this PR

scripts/ci/semver-report.ps1 now routes its cargo semver-checks invocation through the
shared Invoke-SemverChecksCli instead of calling cargo directly. Besides picking up the
linker override, this fixes a latent bug where $LASTEXITCODE was read after an intervening
command and could be stale.

Tests

  • scripts/tests/Pester/unit/releasing/SemverChecksLinker.Tests.ps1: env-var naming for the
    host triple, that rust-lld is actually in the environment while cargo runs, and
    save/restore including the case where cargo throws.
  • PureFunctions.Tests.ps1: updated the LNK1104 hint assertion.
  • Full Pester suite green (431 passed, 0 failed, 1 platform skip).

Copilot AI lite review requested due to automatic review settings August 6, 2026 07:52

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.

🟡 Changes recommended

There’s a concrete path-handling bug in directory creation (-Path vs -LiteralPath) and the non-Windows override behavior is documented inconsistently with the implementation/tests.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.

Pull request overview

This PR addresses Windows MAX_PATH failures when running cargo semver-checks --baseline-rev during release/CI by scoping those invocations to a short scratch CARGO_TARGET_DIR (defaulting to %SystemDrive%\ox-semver) while keeping all other Cargo commands on the repo-local target/.

Changes:

  • Add Get-SemverChecksTargetDir and Invoke-SemverChecksCli to centralize target-dir scoping + env restoration for semver-checks runs.
  • Update both the release flow (Invoke-CrateSemverCheck) and CI semver report script to use the shared helper and capture exit codes reliably.
  • Add unit tests and documentation describing the Windows scratch-directory behavior and override knob.
File summaries
File Description
scripts/lib/releasing.ps1 Adds target-dir resolution and a shared semver-checks CLI wrapper; routes release semver checks through it.
scripts/ci/semver-report.ps1 Uses the shared semver-checks wrapper and consumes its captured output/exit code.
scripts/release-packages.ps1 Documents the Windows MAX_PATH issue and the short scratch target-dir mitigation.
scripts/tests/Pester/unit/releasing/SemverChecksTargetDir.Tests.ps1 Adds unit coverage for target-dir resolution behavior and overrides.
docs/releasing.md Documents the Windows baseline-build scratch directory behavior and override.
Review details
  • Files reviewed: 5/5 changed files
  • Comments generated: 3
  • Review effort level: Lite

We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.

Comment thread scripts/lib/releasing.ps1 Outdated
Comment thread scripts/release-packages.ps1 Outdated
Comment thread docs/releasing.md Outdated
@codecov

codecov Bot commented Aug 6, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.0%. Comparing base (2f653cc) to head (77948be).
⚠️ Report is 4 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff            @@
##             main     #642    +/-   ##
========================================
  Coverage   100.0%   100.0%            
========================================
  Files         532      543    +11     
  Lines       59937    60394   +457     
========================================
+ Hits        59937    60394   +457     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Kateřina Churanová (kate-shine) pushed a commit to kate-shine/oxidizer that referenced this pull request Aug 6, 2026
… override scope

Addresses review feedback on microsoft#642:

- Create the scratch directory with [System.IO.Directory]::CreateDirectory
  instead of New-Item. New-Item only offers the wildcard-expanding -Path (it
  has no -LiteralPath), which disagreed with the surrounding
  Test-Path -LiteralPath check for a user-supplied OXIDIZER_SEMVER_TARGET_DIR
  containing `[`, `]` or `*`. CreateDirectory is literal, creates intermediate
  directories and is a no-op when the directory exists, so the separate
  existence check is gone too.
- Correct the docs: OXIDIZER_SEMVER_TARGET_DIR is honored on every platform.
  Only the *default* is Windows-only. Both docs previously implied non-Windows
  ignored the override, contradicting the implementation and its tests.
- Document that the override must be absolute, since a relative path would be
  resolved against different working directories by this process and by the
  cargo child process.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings August 6, 2026 08:48

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.

🟡 Changes recommended

The implementation documents OXIDIZER_SEMVER_TARGET_DIR as “must be an absolute path” but does not validate/enforce that invariant, which can lead to unintended relative directory creation/use.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.

Review details

Suppressed comments (1)

scripts/lib/releasing.ps1:643

  • Get-SemverChecksTargetDir’s comments/docs state OXIDIZER_SEMVER_TARGET_DIR “must be an absolute path”, but the function currently returns the raw value with no validation. A relative value (or drive-relative like C:foo) would be accepted and could create the semver-checks scratch directory in an unintended location, depending on the caller’s working directory.
    if (-not [string]::IsNullOrWhiteSpace($env:OXIDIZER_SEMVER_TARGET_DIR)) {
        return $env:OXIDIZER_SEMVER_TARGET_DIR
    }
  • Files reviewed: 5/5 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.

Copilot AI review requested due to automatic review settings August 6, 2026 09:57

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.

🟡 Changes recommended

The new OXIDIZER_SEMVER_TARGET_DIR “absolute path” contract is documented but not enforced in code, and the legacy-scratch containment check can mis-detect nested target dirs due to path normalization/separator handling.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.

Review details

Suppressed comments (3)

scripts/lib/releasing.ps1:700

  • Clear-LegacySemverChecksScratch detects “TargetDir is repo target or nested under it” using a hard-coded "$normalizedRepoTarget\" prefix. If TargetDir uses / separators (valid on Windows) or comes from a different path normalization, this check can fail and the function may delete the live scratch directory unexpectedly.
    # When the override deliberately points back at the repository's own target
    # directory, this *is* the live scratch directory — leave it alone.
    $normalizedTarget = $TargetDir.TrimEnd('\', '/')
    $normalizedRepoTarget = $repoTarget.TrimEnd('\', '/')
    if ($normalizedTarget -eq $normalizedRepoTarget -or $normalizedTarget.StartsWith("$normalizedRepoTarget\", [StringComparison]::OrdinalIgnoreCase)) {
        return

scripts/lib/releasing.ps1:643

  • Get-SemverChecksTargetDir’s comment/docs say OXIDIZER_SEMVER_TARGET_DIR “must be an absolute path”, but the override is returned without validation. A relative (or drive-relative like C:foo) value can resolve differently between this script and the cargo child process, and may reintroduce hard-to-diagnose failures.
    if (-not [string]::IsNullOrWhiteSpace($env:OXIDIZER_SEMVER_TARGET_DIR)) {
        return $env:OXIDIZER_SEMVER_TARGET_DIR
    }

scripts/tests/Pester/unit/releasing/SemverChecksTargetDir.Tests.ps1:27

  • The scripts/docs state OXIDIZER_SEMVER_TARGET_DIR must be an absolute path; add a unit test to lock in that contract (e.g. a relative override should throw) so future refactors don’t silently accept ambiguous relative paths.
    It 'honors an explicit OXIDIZER_SEMVER_TARGET_DIR override on every platform' {
        $env:OXIDIZER_SEMVER_TARGET_DIR = 'D:\short'
        Get-SemverChecksTargetDir | Should -Be 'D:\short'
    }
  • Files reviewed: 5/5 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.

Copilot AI review requested due to automatic review settings August 6, 2026 11:23

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.

🟡 Changes recommended

OXIDIZER_SEMVER_TARGET_DIR is documented as requiring an absolute path but is not validated (and a unit test currently uses a Windows path on non-Windows), which can lead to incorrect target-dir handling and legacy-scratch cleanup decisions.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.

Review details

Suppressed comments (2)

scripts/lib/releasing.ps1:643

  • Get-SemverChecksTargetDir documents that OXIDIZER_SEMVER_TARGET_DIR “must be an absolute path”, but the override is currently returned verbatim with no validation. A relative (or drive-relative like C:foo) value can then be passed to CreateDirectory/CARGO_TARGET_DIR, which may resolve differently than intended and can also cause Clear-LegacySemverChecksScratch to mis-detect containment and delete the in-repo scratch unexpectedly.
    if (-not [string]::IsNullOrWhiteSpace($env:OXIDIZER_SEMVER_TARGET_DIR)) {
        return $env:OXIDIZER_SEMVER_TARGET_DIR
    }

scripts/tests/Pester/unit/releasing/SemverChecksTargetDir.Tests.ps1:26

  • This test uses a Windows-style override path ('D:\short') even when running on Linux/macOS. Since the docs state OXIDIZER_SEMVER_TARGET_DIR must be an absolute path, it’s clearer/safer for the test to use a platform-appropriate absolute path (e.g., based on GetTempPath()) so it doesn’t imply a Windows path is valid everywhere.
    It 'honors an explicit OXIDIZER_SEMVER_TARGET_DIR override on every platform' {
        $env:OXIDIZER_SEMVER_TARGET_DIR = 'D:\short'
        Get-SemverChecksTargetDir | Should -Be 'D:\short'
    }
  • Files reviewed: 5/5 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.

Copilot AI review requested due to automatic review settings August 6, 2026 13:35
@kate-shine

Copy link
Copy Markdown
Contributor Author

GHCP: Addressed the three suppressed findings from the last reviews. They had no inline threads to reply to, so recording the responses here.

1. Hardcoded separator in Clear-LegacySemverChecksScratch containment check — correct, and worse than described. The concern was raised for forward slashes on Windows; the same defect broke Linux outright, where Join-Path always produces /, so StartsWith("$normalizedRepoTarget\") could never match and the guard was dead code. script-tests (ubuntu-latest) caught it. Fixed in 320d8f9 with [System.IO.Path]::DirectorySeparatorChar plus a platform-appropriate string comparison (OrdinalIgnoreCase on Windows, Ordinal elsewhere). Added cases for the target dir being the scratch dir itself, and for a sibling like target-scratch that shares a string prefix without being contained.

2. OXIDIZER_SEMVER_TARGET_DIR documented as absolute but not validated — agreed, fixed in 1a426e0. Now rejected via IsPathFullyQualified, which catches both relative and drive-relative (C:foo) forms. Worth noting the two failure modes compound: a relative override does not merely create the scratch directory in an unexpected place, it also defeats the containment check in finding 1, which compares against an absolute repository path — so it would conclude that a same-directory override lies elsewhere and delete the live scratch directory.

3. Test used 'D:\short' on non-Windows — correct, and it became load-bearing once the validation above landed: D:\short is not fully qualified on Linux, so the test would have started failing there. Switched to a GetTempPath()-based absolute path, and added explicit coverage for the relative and drive-relative rejections.

Full unit suite: 343 passed, 0 failed. Both script-tests legs were green on 320d8f9.

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.

🟡 Changes recommended

semver-report does not pass its -RepoRoot parameter through to Invoke-SemverChecksCli, so the helper’s legacy scratch cleanup can target the wrong directory when the script is run from a different working directory.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.

Review details

Suppressed comments (2)

scripts/ci/semver-report.ps1:218

  • Invoke-SemverChecksCli’s RepoRoot parameter defaults to the current working directory, but semver-report supports running against an explicit -RepoRoot. If the script is invoked from a different directory (but with -RepoRoot set), the helper’s legacy scratch cleanup will target the wrong path and may leave <RepoRoot>/target/semver-checks behind, leading to package <name> is ambiguous once CARGO_TARGET_DIR is redirected.
        Write-Host "cargo semver-checks: $cargoName (on-disk v$onDisk) vs v$baselineVersion @ $shortSha..."
        $semverRun = Invoke-SemverChecksCli -PackageName $cargoName -BaselineSha $baselineSha
        $out = $semverRun.Output

scripts/lib/releasing.ps1:655

  • The thrown message for a non-absolute OXIDIZER_SEMVER_TARGET_DIR always suggests a Windows-style example path (D:\ox-semver). Since the override is explicitly supported on Linux/macOS too, the example is confusing there; use a platform-neutral example (or include both) so users on non-Windows know what format is expected.
        if (-not [System.IO.Path]::IsPathFullyQualified($override)) {
            throw "OXIDIZER_SEMVER_TARGET_DIR must be an absolute path, but is '$override'. Use a fully qualified path such as 'D:\ox-semver'."
        }
  • Files reviewed: 5/5 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.

Copilot AI review requested due to automatic review settings August 6, 2026 14:02
@kate-shine

Copy link
Copy Markdown
Contributor Author

GHCP: Both suppressed findings addressed in f5d8cd8.

1. semver-report not passing -RepoRoot through — the change is right, but the reported failure is not reachable today. The call site at semver-report.ps1:217 sits inside Push-Location $RepoRoot (line 121) … Pop-Location (line 282), so $PWD.ProviderPath already equals $RepoRoot and the cleanup targets the correct directory. The real problem is the coupling: correctness depends on a Push-Location about a hundred lines earlier, so moving or removing it would silently break the cleanup with no local signal. Passing it explicitly costs nothing and makes the call site self-contained. Applied at both call sites — Invoke-CrateSemverCheck (releasing.ps1:835) had exactly the same latent coupling, which the review didn't flag. Also softened the parameter comment, which asserted the default "is correct for them" and would have discouraged precisely this fix.

2. Windows-only example in the error message — agreed, straightforwardly wrong for a knob documented as working on every platform. The example is now chosen per platform (/var/tmp/ox-semver off Windows).

Full unit suite: 343 passed, 0 failed.

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.

🟡 Changes recommended

The legacy-scratch containment guard in Clear-LegacySemverChecksScratch is vulnerable to path normalization differences (e.g. ..\ / .) and can delete the live scratch directory when the override points back into the repo target via a non-canonical path.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.

Review details

Suppressed comments (1)

scripts/lib/releasing.ps1:723

  • Clear-LegacySemverChecksScratch uses raw string comparisons for the containment guard, so a fully-qualified TargetDir that points at the repo target via normalization (e.g. C:\repo\target\..\target or C:\repo\target\.) won’t match and will incorrectly delete the live <repo>/target/semver-checks scratch dir. Normalize both paths (e.g. via [System.IO.Path]::GetFullPath) before TrimEnd/StartsWith comparisons.
    $normalizedTarget = if ($IsWindows) { $TargetDir.Replace('/', '\') } else { $TargetDir }
    $normalizedRepoTarget = if ($IsWindows) { $repoTarget.Replace('/', '\') } else { $repoTarget }
    $normalizedTarget = $normalizedTarget.TrimEnd($separator)
    $normalizedRepoTarget = $normalizedRepoTarget.TrimEnd($separator)
  • Files reviewed: 5/5 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Clear-LegacySemverChecksScratch compares lexical paths without canonicalizing dot segments. For example, OXIDIZER_SEMVER_TARGET_DIR=C:\repo\target\..\semver-out is fully qualified but is classified as being inside target, even though it resolves to a sibling. Cleanup can then be skipped, leaving stale semver-check clones that cause duplicate/ambiguous-package failures, or the wrong cache can be rebuilt. Please normalize both paths with [IO.Path]::GetFullPath() before the platform-specific equality/child comparison, and add Pester coverage for . and .. segments.

@kate-shine

Copy link
Copy Markdown
Contributor Author

GHCP: Fixed in 5cd6709. Clear-LegacySemverChecksScratch now canonicalizes both the configured target directory and the repository target directory with [System.IO.Path]::GetFullPath() before the platform-specific equality/child comparison. Added Pester coverage proving target/./custom is retained while target/../semver-out is treated as a sibling and triggers legacy cleanup.

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

Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.

@kate-shine

Copy link
Copy Markdown
Contributor Author

GHCP: Follow-up simplification in 8b02915: replaced the platform-specific canonical-prefix comparison with Path.GetRelativePath. The guard now protects only the repository target itself or a configured target inside the legacy scratch being deleted; another target nested under repo/target no longer incorrectly suppresses stale-scratch cleanup. Added coverage for both cases.

Copilot AI review requested due to automatic review settings August 7, 2026 08:48
Copilot AI review requested due to automatic review settings August 19, 2026 11:42
@kate-shine
Kateřina Churanová (kate-shine) force-pushed the u/kchuranov/fix-semver-checks-max-path branch from 8e392b7 to 00c7bf1 Compare August 19, 2026 11:42

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

Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.

Suppressed comments (1)

scripts/lib/releasing.ps1:983

  • Get-SemverChecksLinkerEnvName relies on $Matches[1] from a -match executed inside the Where-Object scriptblock. In PowerShell, $Matches is scoped to where -match runs, so $Matches[1] here can be $null or stale, producing an incorrect CARGO_TARGET_*_LINKER name and disabling the intended rust-lld override.
    $PSNativeCommandUseErrorActionPreference = $false
    $hostLine = & rustc -vV 2>$null | Where-Object { $_ -match '^host:\s*(\S+)' }
    if ($LASTEXITCODE -ne 0 -or -not $hostLine) {
        return $null
    }

@kate-shine
Kateřina Churanová (kate-shine) force-pushed the u/kchuranov/fix-semver-checks-max-path branch from 00c7bf1 to 9673096 Compare August 19, 2026 12:01
Copilot AI review requested due to automatic review settings August 19, 2026 12:01

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

Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.

Suppressed comments (1)

scripts/lib/releasing.ps1:983

  • Get-SemverChecksLinkerEnvName relies on $Matches[1] coming from the -match inside the Where-Object pipeline. $Matches is scope-local to that script block, so $Matches[1] here can be $null/stale and can throw (ToUpperInvariant() on null) or generate the wrong Cargo env-var name. Re-run the match in the function scope (or use [regex]::Match) before reading $Matches.
    $triple = $Matches[1].ToUpperInvariant() -replace '[^A-Z0-9]', '_'

Copilot AI review requested due to automatic review settings August 19, 2026 12:50
@kate-shine
Kateřina Churanová (kate-shine) force-pushed the u/kchuranov/fix-semver-checks-max-path branch from 9673096 to 2147d0e Compare August 19, 2026 12:50
@kate-shine

Copy link
Copy Markdown
Contributor Author

GHCP: Fixed in 2147d0e.

The finding was half right, and worth acting on either way. Empirically $Matches does propagate out of the Where-Object block on PowerShell 7.6.5 — that is why the existing test passed rather than throwing — so it was not broken as described. But relying on implicit leakage of an automatic variable is fragile, and there is a subtler hazard nearby: a failed -match leaves $Matches untouched rather than clearing it, so the early-return guard was the only thing standing between a missing host: line and a stale triple.

Switched to [regex]::Match and read the capture off the returned Match object, so nothing depends on automatic-variable scoping and the failure case cannot inherit an earlier value.

Added two tests that pin the parse: one mocks rustc reporting an aarch64 host and asserts the resulting variable name, which proves the triple is read from the host: line rather than picked up from the ambient machine; the other asserts output with no host: line returns nothing instead of a stale or malformed name.

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

Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.

Suppressed comments (2)

scripts/lib/releasing.ps1:1142

  • The Windows path-length hint says a MAX_PATH-bound tool was reached "despite the rust-lld override", but ConvertFrom-SemverChecksOutput can be called even when no linker override was applied (e.g., if Get-SemverChecksLinkerEnvName returned $null or the caller didn’t use Invoke-SemverChecksCli). Reword to avoid stating the override definitely happened.
    $pathHint = if ($IsWindows) {
        ' If the output contains LNK1104 or a path-length error, a MAX_PATH-bound tool was reached despite the rust-lld override; shorten the repository path.'
    } else {
        ''
    }
    throw "cargo semver-checks did not produce a parseable result for '$PackageName' (exit $ExitCode). This usually means the tool is missing or the crate/baseline failed to build.$pathHint Output:`n$Output"

scripts/lib/releasing.ps1:980

  • Get-SemverChecksLinkerEnvName checks rustc success via $LASTEXITCODE, but $LASTEXITCODE is only updated for native executables. In unit tests (and any environment where rustc is a PowerShell function/shim), this can read a stale exit code and incorrectly return $null. Prefer checking $? immediately after invoking rustc.
    $PSNativeCommandUseErrorActionPreference = $false
    $version = & rustc -vV 2>$null | Out-String
    if ($LASTEXITCODE -ne 0) {
        return $null
    }

cargo-semver-checks nests its baseline registry checkout under the
workspace target directory, so on Windows the intermediate artifact
paths grow past 260 characters and the MSVC link.exe fails with
LNK1104. link.exe is not long-path aware, so neither LongPathsEnabled
nor a long-path-aware manifest on the calling process helps.

Point the baseline build at rust-lld instead; LLVM prepends the \\?\
prefix internally and links successfully at those depths.
Invoke-SemverChecksCli scopes CARGO_TARGET_<HOST_TRIPLE>_LINKER around
the cargo call and restores or removes it afterwards. That variable is
the environment spelling of [target.<triple>] linker in
.cargo/config.toml, applied only where the problem is rather than to
every build in the repository.

The bare linker name suffices because rustc puts its own linker
directory on PATH when it invokes the linker, so there is no need to
locate the sysroot. The override deliberately does not go through
RUSTFLAGS, which would replace target.<triple>.rustflags from
.cargo/config.toml and silently drop the repository's -C target-cpu
setting.

Non-Windows platforms are unchanged, as their default linkers already
handle long paths.

Also route scripts/ci/semver-report.ps1 through Invoke-SemverChecksCli
so it picks up the same handling, which additionally fixes a stale
$LASTEXITCODE read after an intervening command.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@kate-shine
Kateřina Churanová (kate-shine) force-pushed the u/kchuranov/fix-semver-checks-max-path branch from 2147d0e to 77948be Compare August 19, 2026 14:48
Copilot AI review requested due to automatic review settings August 19, 2026 14:48
@kate-shine

Copy link
Copy Markdown
Contributor Author

GHCP: Addressed both points from the latest review in 77948be.

1. $LASTEXITCODE after rustc — real defect, confirmed by experiment.
I tested it rather than assuming: with a PowerShell-function shim for rustc, $LASTEXITCODE retains its previous value (set it to 7, call the shim, it is still 7), so the guard rejected a perfectly good host: line. My earlier aarch64 test was only passing because $LASTEXITCODE happened to be 0 at that point.

I did not switch to $?, which has its own pitfalls with the 2>$null redirection. The exit-code check was simply redundant: the host: regex is the real success signal, and a missing or failing rustc produces nothing that can match. So the guard is gone, and the invocation is wrapped in try/catch to also cover rustc not being installed (verified that a command-resolution failure is catchable).

Two regression tests pin this. I verified they are genuine by temporarily reverting the fix: both fail on the old code and pass on the new.

2. $pathHint wording. Correct — ConvertFrom-SemverChecksOutput is reachable when no override was applied (Get-SemverChecksLinkerEnvName returns $null, or a caller bypasses Invoke-SemverChecksCli), so asserting "despite the rust-lld override" could be false. Reworded to state only what is known.

Linker tests 10 passed / 1 skipped, PureFunctions 118/118, just spellcheck clean; full Pester suite running.

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

Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.

Comment thread scripts/lib/releasing.ps1
[Parameter(Mandatory = $true)][string]$RepoRoot
)

$linkerVar = Get-SemverChecksLinkerEnvName

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖: Get-SemverChecksLinkerEnvName runs before Push-Location $RepoRoot, but rustup resolves directory-specific toolchain overrides from the current working directory. If this release entry point is invoked from a directory whose override has a different host triple (for example GNU outside the repo and MSVC inside it), this computes the wrong CARGO_TARGET_<TRIPLE>_LINKER; Cargo then runs in RepoRoot, ignores that variable, and the baseline still uses its default linker — i.e. the fix silently doesn't apply.

Please perform the rustc probe / environment setup after entering RepoRoot (while retaining the restoration finally), so the probe and semver-checks see the same active toolchain.

Comment thread scripts/lib/releasing.ps1
[CmdletBinding()]
param()

if (-not $IsWindows) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖: Non-blocking — the gate here is $IsWindows, but the justification (and the comment three lines above) is MSVC-specific: link.exe is what is not long-path aware. On a *-pc-windows-gnu host this still fires and sets the linker to rust-lld.exe — and rustc's flavor inference maps stem rust-lld to (Cc::No, Lld::Yes), so on a gnu target that switches linking from the gcc driver to lld invoked directly: a materially different link path that this fix doesn't need and that nothing here has exercised.

The triple is already parsed a few lines down, so the narrower gate is nearly free: compute the triple first, then return $null unless it ends in -msvc. That also makes the function say exactly what the comment says.

Comment thread scripts/lib/releasing.ps1
# leaves untouched when it fails and so can hold a stale value.
$hostMatch = [regex]::Match($version, '(?m)^host:\s*(\S+)')
if (-not $hostMatch.Success) {
return $null

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖: Non-blocking — failing open here is the right policy (a rustc probe must not break releases), but the skip is completely silent: on Windows, if rustc -vV is missing, throws, or prints no host: line, the override this whole PR exists for is quietly not applied, the baseline build fails with the original LNK1104, and the only thing the user sees is ConvertFrom-SemverChecksOutput's new hint telling them to shorten the repository path — advice for a problem the tooling believes it already fixed, with no indication that the intended remedy never ran.

A single Write-Warning on this Windows-and-no-triple path ("could not determine the host triple from rustc -vV; leaving the toolchain default linker in place") makes the two failure modes distinguishable without changing the fail-open behaviour.

Comment thread scripts/lib/releasing.ps1
$path = "Env:\$linkerVar"
$hadPrevious = Test-Path $path
$previous = if ($hadPrevious) { (Get-Item $path).Value } else { $null }
Set-Item -Path $path -Value 'rust-lld.exe'

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖: Non-blocking — worth deciding explicitly: this overwrites a pre-existing CARGO_TARGET_<HOST>_LINKER unconditionally, so a developer who has deliberately configured their own linker for the host target (a wrapper, a different lld, a custom driver) has that choice silently discarded for the duration of the semver run — restored afterwards, but ignored while it matters. The stated premise is "MSVC link.exe can't do long paths"; if the user has already told cargo not to use link.exe, the premise doesn't hold and we're overriding for nothing.

$hadPrevious is already computed just above, so honouring an explicit setting is a two-line change (skip the Set-Item when $hadPrevious, ideally with a note on stdout). If stomping it is the intended answer, a one-line comment saying so would keep the next reader from re-asking.

Comment thread scripts/lib/releasing.ps1
try {
# A required version bump produces an expected non-zero exit code.
$PSNativeCommandUseErrorActionPreference = $false
$output = & cargo semver-checks --package $PackageName --baseline-rev $BaselineSha --all-features --color never 2>&1 | Out-String

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖: Non-blocking — could we add a Windows-only integration test around this real invocation? Every new test mocks cargo, so they prove the environment-variable lifecycle but not the load-bearing claim of the PR: that cargo-semver-checks forwards this per-target linker setting to its inner baseline build, and that rustc resolves the bare rust-lld.exe there. If either assumption is wrong, the whole change is a no-op and the suite stays green.

A focused test under scripts/tests/Pester/integration/ (or a dedicated Windows workflow step) could install the pinned cargo-semver-checks version, create a tiny versioned crate under a deliberately deep path, call Invoke-SemverChecksCli, and assert a parseable result with no LNK1104.

Comment thread scripts/lib/releasing.ps1
return ConvertFrom-SemverChecksOutput -Output $result.Output -ExitCode $result.ExitCode -PackageName $PackageName
}

# Parses `cargo semver-checks` combined output into a change type. Pure (no I/O)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖: Non-blocking — this header still says the function is "Pure (no I/O) so it can be unit-tested against captured tool output", but after this change its output also depends on $IsWindows, so the thrown message is no longer a function of the arguments alone — which is exactly why the new case in PureFunctions.Tests.ps1 has to carry -Skip:(-not $IsWindows). Worth a word in the comment (e.g. noting the message carries a platform-conditional hint) so the stated contract matches the code.

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.

5 participants