Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ This directory contains all automation that runs in GitHub Actions for the Harpe
| --- | --- | --- | --- |
| Apply Rulesets | `apply-rulesets.yml` | Applies branch ruleset definitions from `.github/rulesets/*.json` to GitHub. Edit `main-branch-protection.json` to change rules; the workflow syncs them on push. | Push to `main` touching rulesets, manual dispatch |
| Auto Merge | `auto-merge.yml` | Three jobs via `libnudget/auto-merge@v1`: `auto-merge` enables GitHub's built-in auto-merge (waits for `CI (ubuntu-latest)` + 1 review); `auto-merge-now` merges immediately via `BYPASS_TOKEN` bypassing ruleset checks; `cancel-auto-merge` disables queued auto-merge when the `auto-merge` label is removed. | `labeled`/`unlabeled`/PR events |
| Bazel CI | `build-bazel.yml` | Builds `:harper_bin` with Bazel on Linux and macOS, plus a scoped Windows smoke build/test for `harper-core` (including lockfile repinning fallback). | Push/PR to `main`, Bazel branches |
| Bazel CI | `build-bazel.yml` | Builds `:harper_bin` with Bazel on Linux and macOS, plus a scoped Windows smoke lane that builds the UI binary, runs `//lib/harper-core:harper_core_test`, resolves the built `harper.exe` via `bazel cquery` and executes `--version`, and dumps `harper_ui` params on failure. See `docs/development/bazel-windows-debugging.md` for the failure-analysis cookbook. | Push/PR to `main`, Bazel branches |
| Bazel Smoke | `bazel-smoke.yml` | Daily `bazel test //...` to catch dependency drift outside PRs. | Daily cron, manual dispatch |
| Rust Benchmarks | `benchmarks.yml` | Runs `cargo bench` nightly and stores results as artifacts. | Daily cron, manual dispatch |
| Integration Tests | `integration.yml` | Executes `cargo test -- --include-ignored` against real services (requires secrets). | PRs touching app code, manual dispatch (with environment input) |
Expand Down Expand Up @@ -66,6 +66,7 @@ Current rules:

- **Action not found:** Verify the action path and version exist (e.g., `bazel-contrib/setup-bazel@0.15.0`). GitHub's error usually means the tag or repository is missing.
- **Cache warnings:** Archived actions (such as the old Bazel setup) may emit 400s from the cache API. Migrating to an actively maintained action usually resolves this.
- **Windows Bazel smoke failures:** Use `docs/development/bazel-windows-debugging.md` to interpret the params dump, extern existence checks, and direct `rustc` smoke compile.
- **`auto-merge-now` fails with ruleset violation:** Ensure `BYPASS_TOKEN` secret is set to a PAT from an org admin account with `repo` scope. The `GITHUB_TOKEN` cannot bypass rulesets.
- **Sandbox permissions (Codex/CI reproductions):** Some local sandbox sessions can mark `.git/refs/heads` with macOS provenance flags, blocking branch creation. If you see "Operation not permitted" writing inside `.git`, create/push branches from a fresh session or your host machine; the issue is environmental, not workflow-related.

Expand Down
121 changes: 118 additions & 3 deletions .github/workflows/build-bazel.yml
Original file line number Diff line number Diff line change
Expand Up @@ -81,15 +81,130 @@ jobs:
Remove-Item -Recurse -Force "$env:TEMP\\*bazel*" -ErrorAction SilentlyContinue
Remove-Item -Force "cargo-bazel-lock.json" -ErrorAction SilentlyContinue
- name: Build with Bazel
id: build_bazel
shell: pwsh
run: |
$env:CARGO_BAZEL_REPIN = "true"
bazel build :harper_bin
bazel build :harper_bin --verbose_failures -s
continue-on-error: true
- name: Dump harper_ui Bazel params on failure
if: steps.build_bazel.outcome == 'failure'
shell: pwsh
run: |
$params = Get-ChildItem -Path bazel-out -Recurse -Filter "libharper_ui-*.params" -ErrorAction SilentlyContinue | Select-Object -First 1
if (-not $params) {
Write-Host "No harper_ui params file found under bazel-out."
exit 1
}

Write-Host "harper_ui params file: $($params.FullName)"
$lines = Get-Content $params.FullName
$lines

Write-Host ""
Write-Host "Extern artifact existence:"
$missing = 0
foreach ($line in $lines) {
if ($line -notmatch '^--extern=([^=]+)=(.+)$') {
continue
}
$crate = $matches[1]
$relativePath = $matches[2]
$artifactPath = Join-Path $PWD $relativePath
$exists = Test-Path $artifactPath
Write-Host ("{0}: {1} -> {2}" -f $crate, $exists, $artifactPath)
if (-not $exists) {
$missing += 1
}
}

Write-Host ""
Write-Host "Dependency search path existence:"
foreach ($line in $lines) {
if ($line -notmatch '^-Ldependency=(.+)$') {
continue
}
$relativePath = $matches[1]
$depPath = Join-Path $PWD $relativePath
$exists = Test-Path $depPath
Write-Host ("{0} -> {1}" -f $exists, $depPath)
}

if ($missing -gt 0) {
Write-Host ""
Write-Host "Missing extern artifacts: $missing"
}

$serdeExtern = ($lines | Where-Object { $_ -match '^--extern=serde=(.+)$' } | Select-Object -First 1)
$sysrootLine = ($lines | Where-Object { $_ -match '^--sysroot=(.+)$' } | Select-Object -First 1)
$targetLine = ($lines | Where-Object { $_ -match '^--target=(.+)$' } | Select-Object -First 1)
$depLines = $lines | Where-Object { $_ -match '^-Ldependency=(.+)$' }
$rustc = Get-ChildItem -Path bazel-out -Recurse -Filter "rustc.exe" -ErrorAction SilentlyContinue | Select-Object -First 1

if ($serdeExtern -and $sysrootLine -and $targetLine -and $rustc) {
$null = $serdeExtern -match '^--extern=serde=(.+)$'
$serdePath = Join-Path $PWD $matches[1]
$serdeDir = Split-Path -Parent $serdePath
$null = $sysrootLine -match '^--sysroot=(.+)$'
$sysrootPath = Join-Path $PWD $matches[1]
$null = $targetLine -match '^--target=(.+)$'
$targetTriple = $matches[1]
$smokeDir = Join-Path $PWD "bazel-out/windows-rustc-smoke"
New-Item -ItemType Directory -Force -Path $smokeDir | Out-Null
$smokeSource = Join-Path $smokeDir "smoke.rs"
Set-Content -Path $smokeSource -Value @(
'use serde as _;',
'',
'fn main() {}'
)

$rustcArgs = @(
$smokeSource,
"--crate-name=windows_rustc_smoke",
"--crate-type=bin",
"--edition=2021",
"--target=$targetTriple",
"--sysroot=$sysrootPath",
"--extern=serde=$serdePath",
"-Ldependency=$serdeDir",
"--out-dir=$smokeDir"
)

foreach ($depLine in $depLines) {
$null = $depLine -match '^-Ldependency=(.+)$'
$depPath = Join-Path $PWD $matches[1]
$rustcArgs += "-Ldependency=$depPath"
}

$rustcArgsFile = Join-Path $smokeDir "smoke.rustc.params"
Set-Content -Path $rustcArgsFile -Value $rustcArgs

Write-Host ""
Write-Host "Direct rustc smoke command:"
Write-Host "$($rustc.FullName) @$rustcArgsFile"
& $rustc.FullName "@$rustcArgsFile"
Write-Host "Direct rustc smoke exit code: $LASTEXITCODE"
} else {
Write-Host ""
Write-Host "Skipping direct rustc smoke compile; required params or rustc.exe path were not available."
}

exit 1
- name: Test harper-core with Bazel
shell: pwsh
run: |
$env:CARGO_BAZEL_REPIN = "true"
bazel test //lib/harper-core:harper_core_test
bazel test //lib/harper-core:harper_core_test --verbose_failures -s
- name: Test Bazel build
shell: pwsh
run: bazel run :harper_bin -- --version
run: |
$executionRoot = (bazel info execution_root).Trim()
$harperExeRelative = [string](
bazel cquery --output=files //:harper_bin |
Select-Object -First 1
).Trim()
$harperExe = Join-Path $executionRoot $harperExeRelative
$proc = Start-Process -FilePath $harperExe -ArgumentList "--version" -NoNewWindow -Wait -PassThru
if ($proc.ExitCode -ne 0) {
exit $proc.ExitCode
}
202 changes: 202 additions & 0 deletions docs/development/bazel-windows-debugging.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
# Bazel Windows Smoke Debugging

Use this guide when `.github/workflows/build-bazel.yml` fails in the `build-windows-smoke` job.

## Scope

The Windows lane is intentionally narrower than Linux and macOS:

- build `//:harper_bin`
- test `//lib/harper-core:harper_core_test`
- run the built `harper.exe --version`

It is a cross-platform smoke lane, not the full Bazel matrix.

## Failure Order

Read the workflow in this order:

1. `Build with Bazel`
2. `Dump harper_ui Bazel params on failure`
3. `Test harper-core with Bazel`
4. `Test Bazel build`

The params dump is keyed off the actual `Build with Bazel` step outcome. If the UI build fails, the dump step is the source of truth.

## Diagnostics Already in the Workflow

The Windows build step runs Bazel with:

- `--verbose_failures`
- `-s`

If `//lib/harper-ui:harper_ui` fails, the workflow also:

- dumps `libharper_ui-*.params`
- checks whether each `--extern=...` artifact exists
- checks whether each `-Ldependency=...` directory exists
- runs a direct `rustc` smoke compile against the dumped `serde` extern

These diagnostics are meant to separate:

- missing Bazel artifacts
- broken dependency search paths
- rustc extern-loading failures
- full-target-only compile failures

## How to Read the Params Dump

The important lines are:

- `--extern=<crate>=<path>`
- `-Ldependency=<path>`
- `--sysroot=<path>`
- `--target=<triple>`

Interpret them as follows:

- if one or more `--extern` files are missing, the issue is Bazel artifact materialization or target wiring
- if one or more `-Ldependency` directories are missing, the issue is dependency propagation
- if all externs and dependency directories exist, the failure is deeper than simple BUILD wiring

## How to Read the Direct `rustc` Smoke Compile

The smoke compile reuses:

- the dumped target triple
- the dumped sysroot
- one dumped extern (`serde`)
- all dumped `-Ldependency` directories

Interpret the result as follows:

- if the direct smoke compile fails with `can't find crate`, rustc cannot load externs correctly in that Windows action context
- if the direct smoke compile succeeds, the failure is specific to the full `harper_ui` compile shape rather than basic extern loading

## Current Minimal Repro

The current Windows smoke lane has already reduced the failure to a minimal direct `rustc` invocation:

- source file:
- `use serde as _;`
- `fn main() {}`
- target:
- `x86_64-pc-windows-msvc`
- sysroot:
- taken from the dumped `harper_ui` params file
- extern:
- `--extern=serde=.../libserde-*.rlib`
- dependency search paths:
- all dumped `-Ldependency=...` entries
- invocation form:
- `rustc.exe @smoke.rustc.params`

Observed result on Windows:

- all referenced files and directories exist
- direct smoke compile still fails with:
- `error[E0463]: can't find crate for 'serde'`

This matters because it removes `harper_ui`-specific compile shape from the equation. The failure reproduces with a single externed crate.

## Upstream Issue Evidence

If this needs to be escalated to `rules_rust` or a Rust Windows toolchain issue, carry these facts:

- the full `harper_ui` params file contains the expected `--extern` entries
- every checked extern artifact exists on disk
- every checked `-Ldependency` directory exists on disk
- a direct `rustc.exe @response-file` smoke compile using only `serde` still fails with `E0463`
- adding the extern crate's parent directory as another `-Ldependency=...` path does not change the result

That is enough to show the problem is deeper than:

- missing BUILD deps
- missing crate-universe outputs
- obvious path nonexistence
- `harper_ui` target complexity

## Known Failure Classes

### Missing BUILD deps

Typical symptoms:

- `E0463` for first-party or third-party crates
- dumped params file does not contain the expected `--extern` entries

Expected fix area:

- `lib/harper-ui/BUILD`

### Missing artifacts despite correct params

Typical symptoms:

- dumped params file contains `--extern`
- artifact existence check reports `False`

Expected fix area:

- Bazel artifact generation
- crate-universe integration
- `cargo-bazel-lock.json` drift

### Externs exist but `rustc` still reports `E0463`

Typical symptoms:

- params file contains expected `--extern`
- existence checks are all `True`
- direct smoke compile fails similarly

Expected fix area:

- Windows-specific `rules_rust` behavior
- proc-macro loading
- rustc path resolution under the Bazel action context

### Full `harper_ui` compile fails but direct smoke compile passes

Typical symptoms:

- direct smoke compile succeeds
- `//lib/harper-ui:harper_ui` still fails

Expected fix area:

- target-specific compile shape
- proc-macro interaction
- larger dependency graph behavior

## Local Reproduction

Start with the narrowest useful commands:

- `CARGO_BAZEL_REPIN=1 bazel build :harper_bin`
- `bazel aquery --include_commandline 'mnemonic("Rustc", //lib/harper-ui:harper_ui)'`

Use `aquery` to compare the `harper_ui` Rustc action across platforms:

- wrapper mode
- params file path
- `--extern` set
- `-Ldependency` set
- `--sysroot`
- target triple

## Files To Update Together

When changing the Windows smoke behavior, keep these in sync:

- `.github/workflows/build-bazel.yml`
- `.github/workflows/README.md`
- this document: `docs/development/bazel-windows-debugging.md`

If the change affects generated Bazel crate wiring, also check:

- `cargo-bazel-lock.json`

## Rule of Thumb

Do not keep guessing in `lib/harper-ui/BUILD` once the params dump already proves the externs are present. At that point, add a diagnostic that narrows the Windows rustc behavior further.
Loading
Loading