diff --git a/.github/workflows/README.md b/.github/workflows/README.md index 0cfc26ca..1ae052c2 100644 --- a/.github/workflows/README.md +++ b/.github/workflows/README.md @@ -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) | @@ -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. diff --git a/.github/workflows/build-bazel.yml b/.github/workflows/build-bazel.yml index d130485a..b8fb5138 100644 --- a/.github/workflows/build-bazel.yml +++ b/.github/workflows/build-bazel.yml @@ -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 + } diff --git a/docs/development/bazel-windows-debugging.md b/docs/development/bazel-windows-debugging.md new file mode 100644 index 00000000..9d02a50a --- /dev/null +++ b/docs/development/bazel-windows-debugging.md @@ -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==` +- `-Ldependency=` +- `--sysroot=` +- `--target=` + +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. diff --git a/lib/harper-ui/BUILD b/lib/harper-ui/BUILD index 9b896312..b88725eb 100644 --- a/lib/harper-ui/BUILD +++ b/lib/harper-ui/BUILD @@ -1,5 +1,5 @@ load("@rules_rust//rust:defs.bzl", "rust_library", "rust_binary") -load("@crates//:defs.bzl", "aliases", "all_crate_deps") +load("@crates//:defs.bzl", "aliases") exports_files(glob(["src/**/*.rs"])) @@ -7,10 +7,29 @@ rust_library( name = "harper_ui", crate_root = "src/lib.rs", srcs = glob(["src/**/*.rs"], exclude = ["src/main.rs", "src/bin/batch.rs"]), - aliases = aliases(), + aliases = aliases( + normal = True, + package_name = "lib/harper-ui", + ), deps = [ "//lib/harper-core:harper_core", - ] + all_crate_deps(normal = True), + "@crates//:arboard", + "@crates//:colored", + "@crates//:crossterm", + "@crates//:dotenvy", + "@crates//:image", + "@crates//:keyring", + "@crates//:ratatui", + "@crates//:reqwest", + "@crates//:rusqlite", + "@crates//:rustyline", + "@crates//:serde", + "@crates//:serde_json", + "@crates//:syntect", + "@crates//:tokio", + "@crates//:turul-mcp-client", + "@crates//:uuid", + ], proc_macro_deps = [ "@crates//:async-trait", ], @@ -21,18 +40,19 @@ rust_binary( name = "harper", crate_root = "src/main.rs", srcs = glob(["src/**/*.rs"]), - aliases = aliases(), + aliases = aliases( + normal = True, + package_name = "lib/harper-ui", + ), deps = [ ":harper_ui", "//lib/harper-core:harper_core", - "@crates//:tokio", - "@crates//:dotenvy", "@crates//:colored", - "@crates//:turul-mcp-client", + "@crates//:dotenvy", + "@crates//:keyring", "@crates//:rusqlite", - ] + all_crate_deps(normal = True), - proc_macro_deps = [ - "@crates//:async-trait", + "@crates//:tokio", + "@crates//:turul-mcp-client", ], crate_features = ["macros", "rt-multi-thread"], visibility = ["//visibility:public"], @@ -42,10 +62,13 @@ rust_binary( name = "harper_batch", crate_root = "src/bin/batch.rs", srcs = ["src/bin/batch.rs"], - aliases = aliases(), + aliases = aliases( + normal = True, + package_name = "lib/harper-ui", + ), deps = [ ":harper_ui", - "//lib/harper-core:harper_core", - ] + all_crate_deps(normal = True), + "@crates//:colored", + ], visibility = ["//visibility:public"], )