diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c4dfe9f..cd1922b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,6 +38,48 @@ jobs: cargo test --all --offline sudo iptables -F OUTPUT || true + # Every platform a release ships a binary for is tested here. + # + # `check` above stays on Linux and owns the things that do not vary by platform — + # formatting, clippy, and the network-egress gate, which needs iptables. This job + # owns what does vary: path handling, the temporary directory, line endings, and + # whether the CLI actually runs. Releases shipped Windows and macOS binaries that no + # job had ever executed; a portability break would have reached users first. + platform: + name: test (${{ matrix.os }}) + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [windows-latest, macos-latest] + steps: + # Before checkout, deliberately: Windows runners convert LF to CRLF on the way + # in, which would mean testing fixtures no user's repository actually contains + # and shifting every byte offset the index records. Configuring git afterwards + # would be too late. + - name: Check out with the line endings as committed + if: runner.os == 'Windows' + run: git config --global core.autocrlf false + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 + + - name: Test + run: cargo test --all + + # The unit tests can pass while the binary cannot start. This is the same + # end-to-end path `bench-smoke` runs on Linux, reduced to what proves the CLI + # works on this platform at all. + - name: The CLI runs end to end + # bash, not the Windows default: PowerShell only propagates the *last* + # command's exit code, so a failing `init` here would pass the step silently. + shell: bash + run: | + set -euo pipefail + cargo run --release -p reify-cli -- -C fixtures/minierp init + cargo run --release -p reify-cli -- -C fixtures/minierp index + cargo run --release -p reify-cli -- -C fixtures/minierp --json context "strategic account discount" + deny: runs-on: ubuntu-latest steps: diff --git a/README.md b/README.md index a7f1afd..7fa0459 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,7 @@ Apache-2.0 SWE-bench retrieval 87.0% network calls: 0 + platforms: macOS, Linux, Windows

@@ -382,8 +383,15 @@ Three things that only break once you leave Latin script, each of which broke he curl -fsSL https://raw.githubusercontent.com/lambiengcode/reify/main/install.sh | sh ``` -Prebuilt binaries for macOS (Apple Silicon and Intel), Linux (x86_64 and aarch64) -and Windows (x86_64). +Prebuilt binaries for **macOS** (Apple Silicon and Intel), **Linux** (x86_64 and +aarch64) and **Windows** (x86_64). On Windows the line above works as written in Git +Bash, MSYS2 or WSL; from PowerShell, take the `x86_64-pc-windows-msvc` archive from +[the latest release](https://github.com/lambiengcode/reify/releases/latest), verify its +`.sha256`, and put `reify.exe` somewhere on your `PATH`. + +Every one of those platforms runs the full test suite in CI, and the CLI is exercised +end to end on each — a binary is not published for a platform nothing has executed. + Or build from source: ```bash diff --git a/README.vi.md b/README.vi.md index 3f68ff7..f4a742f 100644 --- a/README.vi.md +++ b/README.vi.md @@ -20,6 +20,7 @@ Apache-2.0 SWE-bench retrieval 87.0% network calls: 0 + platforms: macOS, Linux, Windows

@@ -403,7 +404,16 @@ Ba thứ chỉ vỡ khi bạn rời khỏi hệ chữ Latinh, và cả ba đều curl -fsSL https://raw.githubusercontent.com/lambiengcode/reify/main/install.sh | sh ``` -Có sẵn binary dựng trước cho macOS (Apple Silicon và Intel) và Linux (x86_64 và aarch64). +Có sẵn binary dựng trước cho **macOS** (Apple Silicon và Intel), **Linux** (x86_64 và +aarch64) và **Windows** (x86_64). Trên Windows, dòng lệnh trên chạy được nguyên vẹn +trong Git Bash, MSYS2 hoặc WSL; từ PowerShell, hãy tải archive `x86_64-pc-windows-msvc` +ở [bản phát hành mới nhất](https://github.com/lambiengcode/reify/releases/latest), kiểm +tra `.sha256` của nó, rồi đặt `reify.exe` vào một thư mục nằm trong `PATH`. + +Mọi nền tảng trong số đó đều chạy toàn bộ test suite trên CI, và CLI được chạy thử +đầu-cuối trên từng nền tảng — không phát hành binary cho một nền tảng mà chưa có gì +từng chạy trên đó. + Hoặc build từ mã nguồn: ```bash diff --git a/README.zh.md b/README.zh.md index 14382a1..d4134f7 100644 --- a/README.zh.md +++ b/README.zh.md @@ -20,6 +20,7 @@ Apache-2.0 SWE-bench retrieval 87.0% network calls: 0 + platforms: macOS, Linux, Windows

@@ -390,7 +391,14 @@ C/C++、Kotlin,外加 SQL。每一种都有一个测试断言它能产出容 curl -fsSL https://raw.githubusercontent.com/lambiengcode/reify/main/install.sh | sh ``` -提供 macOS(Apple Silicon 与 Intel)和 Linux(x86_64 与 aarch64)的预编译二进制。 +提供 **macOS**(Apple Silicon 与 Intel)、**Linux**(x86_64 与 aarch64)和 **Windows** +(x86_64)的预编译二进制。在 Windows 上,上面这行命令在 Git Bash、MSYS2 或 WSL 中可以 +原样运行;若使用 PowerShell,请从[最新发布](https://github.com/lambiengcode/reify/releases/latest) +下载 `x86_64-pc-windows-msvc` 压缩包,校验其 `.sha256`,再把 `reify.exe` 放到 `PATH` 中。 + +以上每个平台都会在 CI 上跑完整的测试套件,并逐一做端到端的 CLI 验证 —— 不会为一个从未 +被执行过的平台发布二进制。 + 也可以从源码构建: ```bash diff --git a/crates/reify/src/lockfile.rs b/crates/reify/src/lockfile.rs index d27d9c8..152e042 100644 --- a/crates/reify/src/lockfile.rs +++ b/crates/reify/src/lockfile.rs @@ -96,9 +96,11 @@ impl Drop for IndexLock { /// Is a process with this id running? /// -/// `kill(pid, 0)` is the portable POSIX existence check. On other platforms this -/// returns `false`, which errs toward reclaiming a lock rather than deadlocking a -/// repository — the safer failure for an advisory lock. +/// The lock is only as good as this answer. A liveness check that always says "no" +/// does not err on the safe side — it makes every lock look stale, so the lock stops +/// excluding anything and two indexers write the same store. That is what the +/// `not(unix)` stub used to do, and CI on Windows found it by failing to recognise +/// its own process as alive. #[cfg(unix)] fn process_is_alive(pid: u32) -> bool { // SAFETY: `kill` with signal 0 performs no action; it only reports whether the @@ -112,9 +114,57 @@ extern "C" { fn libc_kill(pid: i32, sig: i32) -> i32; } -#[cfg(not(unix))] +/// Win32's answer to `kill(pid, 0)`. +/// +/// Declared by hand rather than pulling in a Windows crate, for one question asked +/// once — the same reason `kill` is declared above rather than taking a libc +/// dependency. +#[cfg(windows)] +mod win32 { + pub type Handle = *mut core::ffi::c_void; + extern "system" { + pub fn OpenProcess(access: u32, inherit: i32, pid: u32) -> Handle; + pub fn WaitForSingleObject(handle: Handle, millis: u32) -> u32; + pub fn CloseHandle(handle: Handle) -> i32; + } +} + +#[cfg(windows)] +fn process_is_alive(pid: u32) -> bool { + /// The narrowest right to ask "does this exist"; granted across integrity levels + /// where `PROCESS_QUERY_INFORMATION` is not. + const QUERY_LIMITED_INFORMATION: u32 = 0x1000; + /// Required to wait on the handle at all. Omitting it does not make the wait + /// stricter — it makes it fail with `WAIT_FAILED`, which reads as "not running" + /// and silently restores the bug this function exists to fix. + const SYNCHRONIZE: u32 = 0x0010_0000; + /// The handle is not signalled, so the process has not exited. + const WAIT_TIMEOUT: u32 = 258; + + // SAFETY: `OpenProcess` returns null rather than an invalid handle on failure, and + // the handle is closed on every path that obtained one. + unsafe { + let handle = win32::OpenProcess(SYNCHRONIZE | QUERY_LIMITED_INFORMATION, 0, pid); + if handle.is_null() { + // No such process, or one this user may not query. Either way, treating + // the lock as reclaimable is the behaviour a dead owner should get. + return false; + } + // Waiting zero milliseconds asks the question without blocking. Preferred over + // `GetExitCodeProcess`, which reports the sentinel 259 for a running process + // and cannot distinguish it from one that genuinely exited with 259. + let state = win32::WaitForSingleObject(handle, 0); + win32::CloseHandle(handle); + state == WAIT_TIMEOUT + } +} + +/// Any other platform. Deliberately pessimistic: without a liveness check the lock +/// cannot be trusted, so it refuses to reclaim rather than silently allowing two +/// indexers to share a store. +#[cfg(not(any(unix, windows)))] fn process_is_alive(_pid: u32) -> bool { - false + true } #[cfg(test)] diff --git a/site/docs.html b/site/docs.html index 3944d1e..6ff273b 100644 --- a/site/docs.html +++ b/site/docs.html @@ -43,7 +43,14 @@

Install

-

Prebuilt binaries for macOS (Apple Silicon and Intel) and Linux (x86_64 and aarch64).

+

+ Prebuilt binaries for macOS (Apple Silicon and Intel), Linux (x86_64 and aarch64) + and Windows (x86_64). On Windows the command above works in Git Bash, MSYS2 or + WSL; from PowerShell, take the x86_64-pc-windows-msvc archive from + the latest release, verify its .sha256, and put + reify.exe on your PATH. Every platform runs the full + test suite in CI. +

 curl -fsSL https://raw.githubusercontent.com/lambiengcode/reify/main/install.sh | sh