From d4cc3f60e8091f295a7305c9115886a7350c2da0 Mon Sep 17 00:00:00 2001 From: zackees Date: Mon, 22 Jun 2026 00:14:49 -0700 Subject: [PATCH] docs: scrub stale cli.py / _bin/ references after #7 (closes #11) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Followup to #7 (raw wheel script) and #9/#10 (test + gate cleanup). #9 fixed the tools that broke at runtime; this PR fixes the docs that broke at the human-reader level. Same failure shape as fbuild#748: a packaging refactor was correct at the wheel-build path but downstream readers walking artifacts a different way still saw the dead shape. Files touched (doc-only; no behavioral change): - README.md: tree diagram no longer shows cli.py/_bin/. Packaging section describes the actual raw-wheel-script mechanism. - CLAUDE.md: agent-routing doc now describes the post-#7 wheel layout + the pinned CARGO_TARGET_DIR build pipeline. No more cli.py shim invariants. - ENHANCE.md: invariants updated — "no Python shim, raw wheel script, re-adding [project.scripts] would re-introduce the Windows os.execv race" replaces the old shim invariants. - crates/template-cli/README.md: describes the inject_cli_into_wheel step instead of the deleted _bin/ staging step. - docs/ARCHITECTURE.md: package contents accurately reflect what's actually in src/template_python_rust_cmd/ today (no cli.py listed). - docs/RELEASE.md: build_wheel.py described as cargo → maturin → inject into .data/scripts/, not stage into _bin/. Verified: - `grep -rn '_bin\|template_python_rust_cmd\.cli\|cli\.py' README.md CLAUDE.md ENHANCE.md docs/ crates/template-cli/README.md` returns only historical-context references explicitly explaining the migration (acceptance-criterion-allowed). - `./ci.sh test` → 34 pass, 2 skip. - `./ci.sh action_yaml` → ok. - `./ci.sh action_surface` → ok. Closes #11. Refs #7, #9, fbuild#748. Co-Authored-By: Claude Opus 4.7 --- CLAUDE.md | 19 +++++++++++-------- ENHANCE.md | 12 +++++++----- README.md | 18 ++++++++++++------ crates/template-cli/README.md | 20 +++++++++++++------- docs/ARCHITECTURE.md | 12 ++++++++++-- docs/RELEASE.md | 13 ++++++++----- 6 files changed, 61 insertions(+), 33 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index ce76bf7..9ed570f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -74,17 +74,20 @@ is about to run, write it as a hook. - Grow `template-core` first; expose through `template-cli` and `template-py`. Don't let them diverge on core behavior. -- The wheel exposes `template_python_rust_cmd._native` (PyO3) AND a - packaged `template-cli` binary under - `src/template_python_rust_cmd/_bin/`. `ci/build_wheel.py` enforces - both are present. +- The wheel exposes `template_python_rust_cmd._native` (PyO3) AND + ships the cargo-built `template-cli[.exe]` as a raw wheel script at + `template_python_rust_cmd-.data/scripts/`. Pip extracts that + directly into the venv's `Scripts/` / `bin/` on install — no Python + shim sits in front of the binary. `ci/build_wheel.py::verify_artifacts()` + enforces both deliverables are present in the wheel. - When changing user-visible commands, update [README.md](./README.md), [UPDATE.md](./UPDATE.md), [docs/ARCHITECTURE.md](./docs/ARCHITECTURE.md), and any affected `ci/gates/.py`. -- The release pipeline stages the compiled `template-cli` binary into - `src/template_python_rust_cmd/_bin/` during the build, verifies the - resulting wheel contains both native deliverables, and removes the - staged binary afterward so the worktree stays clean. +- The release pipeline builds `template-cli` via cargo into the pinned + `CARGO_TARGET_DIR`, then `ci/build_wheel.py` post-processes the + maturin wheel to inject the binary at `.data/scripts/` with a fresh + RECORD row. There is no `_bin/` staging step under the package source + tree anymore; see #7 for the rationale (Windows `os.execv` race). ## Where to ask questions diff --git a/ENHANCE.md b/ENHANCE.md index bcb53e6..25c7b0f 100644 --- a/ENHANCE.md +++ b/ENHANCE.md @@ -34,12 +34,14 @@ When growing the scaffold, the load-bearing decisions are: ## Invariants to preserve - `template-cli` and `template-py` never diverge on core behavior. -- The Python CLI shim in `src/template_python_rust_cmd/cli.py` stays - thin: it locates the packaged binary and delegates. No business - logic. +- There is no Python CLI shim. `template-cli[.exe]` ships as a raw + wheel script at `template_python_rust_cmd-.data/scripts/`; pip + drops it straight into the venv's `Scripts/` / `bin/` on install with + no Python wrapper. Adding `[project.scripts]` back would re-introduce + the Windows `os.execv` race fixed in #7 — don't. - The wheel always contains both deliverables (PyO3 extension AND - staged native binary). `ci/build_wheel.py::verify_artifacts()` - enforces this — don't bypass it. + the raw `template-cli[.exe]` wheel script). `ci/build_wheel.py:: + verify_artifacts()` enforces this — don't bypass it. - The composite `action.yml` only references subcommands that exist in `template-cli --help`. `ci/gates/action_surface.py` checks this. diff --git a/README.md b/README.md index 2214ecd..63b8d4e 100644 --- a/README.md +++ b/README.md @@ -42,9 +42,11 @@ the rules and where they're implemented are also covered in ├── src/template_python_rust_cmd/ │ ├── __init__.py # package version + public imports │ ├── _native.pyi # typing stub for the PyO3 surface -│ ├── bindings.py # Python wrapper around the extension -│ ├── cli.py # Python entry that delegates to the native binary -│ └── _bin/ # packaged native executable location (gitignored) +│ └── bindings.py # Python wrapper around the extension +│ # `template-cli[.exe]` is NOT under the package — it's injected into +│ # the wheel's -.data/scripts/ directory by ci/build_wheel.py +│ # and pip drops it straight into the venv's Scripts/ (Win) or bin/ +│ # (POSIX) on install. See src/template_python_rust_cmd/README.md. ├── tests/ # pytest fixtures + gate contract tests └── docs/ ├── ARCHITECTURE.md @@ -91,9 +93,13 @@ gate produce noise instead of signal. See [zccache#835 rule 7](https://github.co The wheel contains: - the PyO3 extension module at `template_python_rust_cmd._native`, and -- the packaged native executable used by the `template-python-rust-cmd` - console script, staged into `src/template_python_rust_cmd/_bin/` - during build and removed afterward (gitignored). +- the cargo-built `template-cli[.exe]` binary at + `template_python_rust_cmd-.data/scripts/` — pip extracts this + straight into the venv's `Scripts/` (Windows) or `bin/` (POSIX) + directory on install, with no Python wrapper in front of it. See + [#7](https://github.com/zackees/template-python-rust-cmd/pull/7) for + why we avoid `[project.scripts]` (Windows `os.execv` is emulated and + races the shell prompt ahead of the child's stdout). `./build_wheel.py` orchestrates the maturin build, verifies the wheel contains both deliverables, and cleans up. `./publish.py` is the diff --git a/crates/template-cli/README.md b/crates/template-cli/README.md index b1db1b7..5e37884 100644 --- a/crates/template-cli/README.md +++ b/crates/template-cli/README.md @@ -1,8 +1,13 @@ # `template-cli` The bare Rust binary shipped with the Python package. Built into -`target/release/template-cli{,.exe}`, then staged into -`src/template_python_rust_cmd/_bin/` by `ci/build_wheel.py`. +`target/release/template-cli{,.exe}` (or `$CARGO_TARGET_DIR/release/` +when the wheel-build path runs — see `ci/build_wheel.py` for the pin), +then **injected directly into the wheel** at +`template_python_rust_cmd-.data/scripts/` by +`ci/build_wheel.py::inject_cli_into_wheel()`. Pip extracts the binary +straight into the venv's `Scripts/` (Win) or `bin/` (POSIX) on install +— no Python launcher in front of it. See #7 / #2 for why. ## Responsibilities @@ -31,11 +36,12 @@ you remove one, the cleanup step is the same in reverse. ## Why the binary is packaged into the wheel A Python user doing `pip install template-python-rust-cmd` gets the -`template-cli` binary on PATH (via the entry-point script). They -don't need to install Rust, and they don't need a separate -distribution channel for the binary. The wheel is one artifact for -both deliverables; `ci/build_wheel.py` enforces that the staged -binary is present. +`template-cli` binary on PATH (the wheel ships it as a raw script in +`.data/scripts/`; pip handles the extraction). They don't need to +install Rust, and they don't need a separate distribution channel for +the binary. The wheel is one artifact for both deliverables; +`ci/build_wheel.py::verify_artifacts()` enforces that the script entry +is present alongside the PyO3 extension. ## Cross-compilation diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 5278354..f8677d4 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -6,7 +6,10 @@ Ship one Python wheel that exposes: - a Python import surface backed by `PyO3` (`template_python_rust_cmd._native`) - a command surface backed by a compiled Rust executable (`template-cli`, - packaged at `template_python_rust_cmd._bin/`) + shipped as a raw wheel script at + `template_python_rust_cmd-.data/scripts/` — pip extracts it + straight into the venv's `Scripts/`/`bin/` on install, no Python + launcher; see #7 for the Windows `os.execv` race that motivated this) A consumer installs a single distribution and gets both deliverables. A maintainer maintains one workspace and one CI matrix to feed both. @@ -63,9 +66,14 @@ Five load-bearing pieces: - package version and re-exports (`__init__.py`) - Python wrapper around the extension (`bindings.py`) -- CLI shim that finds and execs the packaged native binary (`cli.py`) - typing stub for the PyO3 surface (`_native.pyi`) +The `template-cli` binary is intentionally NOT under the package — it +ships as a raw wheel script (see Goal above). A Python caller that +wants to invoke the CLI from code should do +`subprocess.run([shutil.which("template-cli"), ...])`, not import +anything from this package. + ## Hook / Gate Split | Concern | Home | diff --git a/docs/RELEASE.md b/docs/RELEASE.md index 7014a37..b63e769 100644 --- a/docs/RELEASE.md +++ b/docs/RELEASE.md @@ -14,11 +14,14 @@ The canonical release sequence and the publish-script contract. ./ci.sh all uv run python ci/build_wheel.py ``` - `build_wheel.py` stages the compiled CLI binary into - `src/template_python_rust_cmd/_bin/`, drives maturin to produce - wheel + sdist, asserts both the PyO3 extension module and the - staged binary are present in the wheel, then removes the staged - binary. + `build_wheel.py` builds the CLI via cargo into the pinned + `CARGO_TARGET_DIR`, drives maturin to produce the wheel + sdist + (PyO3 extension only), then post-processes the wheel to inject the + cargo-built `template-cli[.exe]` at + `template_python_rust_cmd-.data/scripts/` with a fresh RECORD + row. `verify_artifacts()` asserts both the PyO3 extension module + and the raw `template-cli` wheel script are present. There is no + `_bin/` staging step under the package source tree — see #7. 4. Verify wheel and sdist by hand: `uv run --with twine twine check dist/*`. 5. Set `_ENABLED = True` in `ci/publish.py` (deliberately not a CLI