From e29fc2739680ccbed0e637767e9f7c13f8524e93 Mon Sep 17 00:00:00 2001 From: Kylian Bardini Date: Fri, 29 May 2026 11:34:56 +0200 Subject: [PATCH 1/3] =?UTF-8?q?=E2=9C=A8=20feat(build):=20cargo-binstall?= =?UTF-8?q?=20metadata=20(#27)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add `[package.metadata.binstall]` so `cargo binstall gwm` pulls the prebuilt archive from the GitHub Release instead of compiling git2 / vendored-libgit2 from source. `pkg-url` mirrors the release workflows' artefact naming (`gwm-v{version}-{target}.tar.gz`), `bin-dir` mirrors the in-archive layout (`gwm-v{version}-{target}/{bin}`), and an x86_64-pc-windows-msvc override points at the `.zip` artefact. Tested: tests/binstall_metadata_tests.rs parses the manifest and pins pkg-url / pkg-fmt / bin-dir / windows override against drift (adds `toml` as a dev-dependency for structural assertions). --- Cargo.toml | 19 ++++++++ tests/binstall_metadata_tests.rs | 77 ++++++++++++++++++++++++++++++++ 2 files changed, 96 insertions(+) create mode 100644 tests/binstall_metadata_tests.rs diff --git a/Cargo.toml b/Cargo.toml index 0847d86..2dd192a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,21 @@ readme = "README.md" keywords = ["git", "worktree", "tui", "cli", "ratatui"] categories = ["command-line-utilities", "development-tools"] +# cargo-binstall (#27): pull the prebuilt binary from the GitHub +# Release instead of compiling git2/vendored-libgit2 from source. +# Mirrors the release workflows' artefact naming — `gwm-v{version}- +# {target}.tar.gz` (`.zip` on windows) — and the in-archive layout +# `gwm-v{version}-{target}/{gwm|gwm.exe}`. `tests/binstall_metadata_tests.rs` +# pins this block against drift. +[package.metadata.binstall] +pkg-url = "{ repo }/releases/download/v{ version }/gwm-v{ version }-{ target }.tar.gz" +pkg-fmt = "tgz" +bin-dir = "gwm-v{ version }-{ target }/{ bin }" + +[package.metadata.binstall.overrides.x86_64-pc-windows-msvc] +pkg-url = "{ repo }/releases/download/v{ version }/gwm-v{ version }-{ target }.zip" +pkg-fmt = "zip" + [lib] name = "gwm" path = "src/lib.rs" @@ -62,6 +77,10 @@ libc = "0.2" assert_cmd = "2" criterion = "0.8" predicates = "3" +# Parse our own Cargo.toml in tests/binstall_metadata_tests.rs (#27) so +# the `[package.metadata.binstall]` contract is asserted structurally, +# not by string-matching. Same `toml` major as the runtime dependency. +toml = "1.1" [[bench]] name = "commit_graph" diff --git a/tests/binstall_metadata_tests.rs b/tests/binstall_metadata_tests.rs new file mode 100644 index 0000000..713b799 --- /dev/null +++ b/tests/binstall_metadata_tests.rs @@ -0,0 +1,77 @@ +//! Pin the `[package.metadata.binstall]` contract (issue #27). +//! +//! `cargo binstall gwm` reads this block to locate the prebuilt archive +//! on the GitHub Release instead of compiling from source. The block +//! must stay in lock-step with the release workflows' artefact naming +//! (`gwm-v{version}-{target}.{tar.gz|zip}`) and the in-archive layout +//! (`gwm-v{version}-{target}/{gwm|gwm.exe}`). A drift here ships a +//! `binstall` that 404s or extracts the wrong path, so the contract is +//! asserted structurally against the parsed manifest. + +use std::path::{Path, PathBuf}; + +fn manifest() -> toml::Value { + let path: PathBuf = Path::new(env!("CARGO_MANIFEST_DIR")).join("Cargo.toml"); + let raw = std::fs::read_to_string(&path).unwrap_or_else(|e| panic!("read {}: {e}", path.display())); + toml::from_str(&raw).expect("Cargo.toml is valid TOML") +} + +fn binstall() -> toml::Value { + manifest() + .get("package") + .and_then(|p| p.get("metadata")) + .and_then(|m| m.get("binstall")) + .cloned() + .expect("[package.metadata.binstall] block is present") +} + +#[test] +fn binstall_pkg_url_points_at_the_release_tarball() { + let b = binstall(); + let pkg_url = b.get("pkg-url").and_then(|v| v.as_str()).expect("pkg-url is a string"); + assert_eq!( + pkg_url, "{ repo }/releases/download/v{ version }/gwm-v{ version }-{ target }.tar.gz", + "pkg-url must match the release workflow's tarball naming" + ); +} + +#[test] +fn binstall_pkg_fmt_is_tgz() { + let b = binstall(); + let fmt = b.get("pkg-fmt").and_then(|v| v.as_str()).expect("pkg-fmt is a string"); + assert_eq!(fmt, "tgz", "the non-windows artefacts are gzipped tarballs"); +} + +#[test] +fn binstall_bin_dir_matches_in_archive_layout() { + let b = binstall(); + let bin_dir = b.get("bin-dir").and_then(|v| v.as_str()).expect("bin-dir is a string"); + // The packaging step stages the binary under + // `gwm-v{version}-{target}/` inside the archive; `{ bin }` resolves + // to `gwm` (or `gwm.exe` on windows). + assert_eq!(bin_dir, "gwm-v{ version }-{ target }/{ bin }"); +} + +#[test] +fn binstall_windows_override_uses_zip_artefact() { + let b = binstall(); + let win = b + .get("overrides") + .and_then(|o| o.get("x86_64-pc-windows-msvc")) + .expect("windows MSVC override is present"); + + let fmt = win + .get("pkg-fmt") + .and_then(|v| v.as_str()) + .expect("windows pkg-fmt is a string"); + assert_eq!(fmt, "zip", "the windows artefact is a .zip, not a tarball"); + + let pkg_url = win + .get("pkg-url") + .and_then(|v| v.as_str()) + .expect("windows pkg-url is a string"); + assert!( + pkg_url.ends_with("gwm-v{ version }-{ target }.zip"), + "windows pkg-url must target the .zip artefact, got: {pkg_url}" + ); +} From 32d46a63d51e82617275a802218103641dabc6b2 Mon Sep 17 00:00:00 2001 From: Kylian Bardini Date: Fri, 29 May 2026 11:34:56 +0200 Subject: [PATCH 2/3] =?UTF-8?q?=F0=9F=93=9D=20docs:=20document=20the=20car?= =?UTF-8?q?go-binstall=20install=20channel=20(#27)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit README install matrix gains a `cargo binstall gwm` row; the getting- started install doc gets a dedicated section explaining the no-compile path; CHANGELOG [Unreleased] notes the feature. --- CHANGELOG.md | 4 +++- README.md | 3 +++ docs/1.getting-started/1.install.md | 10 ++++++++++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cd08ca3..8bc9347 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -_No changes yet — entries land here as PRs merge into `dev`, then move to a per-RC file under `changelogs/pre-releases/` when the next RC is cut._ +### Added + +- **`cargo-binstall` support** ([#27](https://github.com/kbrdn1/gwm-cli/issues/27)). `[package.metadata.binstall]` in `Cargo.toml` lets `cargo binstall gwm` pull the prebuilt archive (`gwm-v{version}-{target}.tar.gz`, `.zip` on Windows) straight from the GitHub Release — no Rust toolchain or libgit2 compile at install time. Pinned against artefact-naming drift by `tests/binstall_metadata_tests.rs`. ## Past releases diff --git a/README.md b/README.md index 7f1cfb3..58197eb 100644 --- a/README.md +++ b/README.md @@ -14,10 +14,13 @@ Rust CLI + ratatui TUI to manage git worktrees across projects. Native `libgit2` | Channel | Command | |:---------------|:---------------------------------------------------------------------| | Cargo (source) | `cargo install --path .` | +| cargo-binstall | `cargo binstall gwm` | | Homebrew (macOS) | `brew tap kbrdn1/tap && brew install gwm` | | Nix flake | `nix profile install github:kbrdn1/gwm-cli` | | Prebuilt | (Linux / macOS / Windows) | +`cargo binstall gwm` grabs the prebuilt binary from the matching GitHub Release instead of compiling `git2`/vendored-libgit2 from source — no Rust toolchain needed at install time. + Full install matrix and verification steps: [`docs/getting-started/install.md`](docs/1.getting-started/1.install.md). ## the 30-second tour diff --git a/docs/1.getting-started/1.install.md b/docs/1.getting-started/1.install.md index f827af6..9596d95 100644 --- a/docs/1.getting-started/1.install.md +++ b/docs/1.getting-started/1.install.md @@ -17,6 +17,16 @@ cargo install --path . The binary lands in `~/.cargo/bin/gwm`. Requires a recent stable Rust toolchain (1.80+). +## via cargo-binstall + +```bash +cargo binstall gwm +``` + +[`cargo-binstall`](https://github.com/cargo-bins/cargo-binstall) reads the `[package.metadata.binstall]` block in `gwm`'s `Cargo.toml`, downloads the prebuilt archive matching your host triple from the GitHub Release, extracts it, and drops the binary in `~/.cargo/bin/`. No Rust toolchain or `git2`/libgit2 C compile is needed at install time — much faster than `cargo install` on first run. + +The metadata points at the same artefacts the release workflow publishes (`gwm-v{version}-{target}.tar.gz`, or `.zip` on Windows), so any tagged release is binstall-able. `tests/binstall_metadata_tests.rs` pins the block against drift. + ## via Homebrew (macOS) ```bash From 43cf74094d9ac58a3f8ea79d3e6047f019b86a01 Mon Sep 17 00:00:00 2001 From: Kylian Bardini Date: Fri, 29 May 2026 11:37:40 +0200 Subject: [PATCH 3/3] =?UTF-8?q?=F0=9F=93=9D=20docs(skill):=20note=20cargo?= =?UTF-8?q?=20binstall=20install=20path=20(#27)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- skills/SKILL.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/skills/SKILL.md b/skills/SKILL.md index 4fd326e..731ea86 100644 --- a/skills/SKILL.md +++ b/skills/SKILL.md @@ -53,6 +53,8 @@ cargo install --path . # → ~/.cargo/bin/gwm gwm --version ``` +No Rust toolchain at hand? `cargo binstall gwm` pulls the prebuilt binary from the matching GitHub Release (via `[package.metadata.binstall]`) and drops it in `~/.cargo/bin/` without compiling `git2`/vendored-libgit2 from source — much faster on first install. + Prebuilt releases (Linux x86_64/aarch64, macOS Intel/Apple Silicon, Windows): https://github.com/kbrdn1/gwm-cli/releases. A Homebrew formula ships under `packaging/homebrew/` and a Nix `flake.nix` is at the repo root. ## Default conventions