Skip to content

fix(install): ship template-cli as raw wheel script; drop Python shim#7

Merged
zackees merged 1 commit into
mainfrom
fix/ship-cli-as-raw-wheel-script
Jun 22, 2026
Merged

fix(install): ship template-cli as raw wheel script; drop Python shim#7
zackees merged 1 commit into
mainfrom
fix/ship-cli-as-raw-wheel-script

Conversation

@zackees

@zackees zackees commented Jun 22, 2026

Copy link
Copy Markdown
Owner

Summary

The big one — same Windows stdout-ordering bug fbuild#747 just fixed, ported here.

Drop the [project.scripts] Python entry point + cli.py subprocess shim. ci/build_wheel.py now post-processes the maturin-built wheel to inject the cargo-built template-cli[.exe] at <name>-<ver>.data/scripts/ with a recomputed RECORD row + Unix +x bit. Pip drops files from .data/scripts/ straight into Scripts/bin verbatim — no Python wrapper for .exe files.

Also reverts no-build-isolation-package from PR #5 (kept the CARGO_TARGET_DIR pin from that PR). With maturin (vs. setuptools) the no-isolation trade-off doesn't work cleanly — uv reuses a build env lacking maturin and the preview extra-build-dependencies mechanism is too fragile.

Acceptance (all from #2)

  • unzip -l dist/*.whl shows template_python_rust_cmd-0.1.0.data/scripts/template-cli.exe, no _bin/.
  • file $(which template-cli) reports PE32+ executable, NOT a Zip-archive console-script stub.
  • cmd /c 'echo === BEFORE === & template-cli --version & echo === AFTER ===' prints === AFTER === AFTER the version line.

Breaking change

The template-python-rust-cmd command name is gone (it was the auto-generated console-script wrapper from [project.scripts]). Users should invoke template-cli directly. Action consumers (uses: zackees/template-python-rust-cmd@v1) already use command -v template-cli so they're unaffected.

Closes #2 (items 1 + 10).

🤖 Generated with Claude Code

…#2 items 1+10)

Previously the Python entry point `[project.scripts]
template-python-rust-cmd = "template_python_rust_cmd.cli:main"` created
a pip console-script `.exe` that subprocess-launched the staged
`_bin/template-cli`. On Windows that pattern races the next shell
prompt ahead of the child's stdout, because the Python shim returns
to cmd.exe before the spawned native binary finishes flushing. Same
class of bug fbuild fixed in FastLED/fbuild#747.

Drop the shim. ci/build_wheel.py now post-processes the maturin-built
wheel: opens the zip, injects the cargo-built `template-cli[.exe]` at
`<name>-<ver>.data/scripts/template-cli[.exe]`, recomputes the RECORD
row (sha256 + size), and stamps the Unix executable bit on the entry.
Files in `.data/scripts/` are pip's canonical raw-script install
location — pip drops them straight into the venv's `Scripts/` (Win) /
`bin/` (POSIX) directory verbatim, with no Python wrapper for `.exe`
files. Same mechanism cargo-dist and maturin's "bin" mode use.

Removed:
- `[project.scripts]` table from pyproject.toml.
- `src/template_python_rust_cmd/cli.py` (the racy subprocess shim).
- `src/template_python_rust_cmd/_bin/.gitkeep` + the gitignore lines
  that staged binary into the package data directory.
- `action/cleanup/action.yml` step that rm'd the (now-nonexistent)
  `_bin/` staging directory.

Verified end-to-end on Windows:
- Fresh maturin wheel produced via `uv run --no-project --script
  ci/build_wheel.py`.
- `unzip -l dist/*.whl` shows `template_python_rust_cmd-0.1.0.data/
  scripts/template-cli.exe` (1.2 MB) — no `_bin/` entries.
- `uv pip install` of that wheel into a fresh venv lands a `Scripts/
  template-cli.exe` that `file` reports as `PE32+ executable` (NOT a
  Zip-archive console-script stub).
- `cmd /c 'echo === BEFORE === & template-cli.exe --version & echo
  === AFTER ==='` prints `=== AFTER ===` AFTER the version output —
  acceptance criterion from issue #2 met.

The `no-build-isolation-package` setting added in PR #5 was reverted
in this PR: with maturin (vs. setuptools) the trade-off doesn't favor
us — uv reuses a stale build env that lacks maturin, and the preview
`extra-build-dependencies` mechanism is too fragile to ship in a
template. The CARGO_TARGET_DIR pin from that PR stays — it already
gives the cargo-incremental win without touching isolation.

Closes #2 (items 1 + 10).
Refs FastLED/fbuild#747.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@zackees zackees merged commit 761b1e6 into main Jun 22, 2026
@zackees zackees deleted the fix/ship-cli-as-raw-wheel-script branch June 22, 2026 06:19
zackees added a commit that referenced this pull request Jun 22, 2026
…oses #9) (#10)

Followup cleanup to #7 (drop Python CLI shim, ship raw wheel script).
The package-side refactor worked at the wheel-build level but left
stale source-tree references in two artifact-walking tools — same
failure shape as fbuild#748 (a packaging refactor that worked at the
wheel level but broke a tool walking the source tree a different way).

Stale references found by `git grep '_bin\|template_python_rust_cmd\.cli'`:

1. `tests/test_cli.py` — imported `template_python_rust_cmd.cli.
   packaged_binary_path` (module deleted in #7). Whole file failed to
   collect with ImportError; the suite was silently 1 file short.

2. `ci/gates/action_surface.py::_binary_path()` — listed
   `src/template_python_rust_cmd/_bin/template-cli[.exe]` as its FIRST
   lookup candidate. That directory was removed in #7. Fallback to
   target/release worked locally but the gate doesn't honor
   CARGO_TARGET_DIR (pinned to ~/.template-python-rust-cmd/cargo-target/
   wheel-build in #5), so it silently misses the binary on a
   wheel-build-driven runner.

3. `tests/README.md` — described `test_cli.py` as covering "the Python
   CLI shim's binary-discovery logic" (module gone).

Fix:

- `tests/test_cli.py` rewritten as a runtime contract check: probe
  PATH for `template-cli`, skip if absent (dev venv without install),
  otherwise assert it's invokable and produces non-empty --version
  output. Matches the new shipping mechanism (raw wheel script) which
  doesn't leave a source-tree probe target.

- `ci/gates/action_surface.py::_binary_path()` now searches, in order:
    1. $CARGO_TARGET_DIR/{release,debug}/ if set
    2. ROOT/target/{release,debug}/ for the local ./test flow
    3. PATH via shutil.which for pip/uv-installed binaries
  Updated the "no binary found" warning to match.

- `tests/README.md` describes what test_cli.py actually does now.

Verified:
- `uv run pytest tests/test_cli.py tests/test_version.py tests/test_bindings.py -v`
  → 2 pass, 2 skip (test_cli skips because dev venv has no installed
  template-cli — expected behavior).
- `./ci.sh test` → 34 pass, 2 skip, [test] ok.
- `./ci.sh action_surface` → finds the binary, gate passes.

Closes #9.

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
zackees added a commit that referenced this pull request Jun 22, 2026
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 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Adopt fbuild's perf + Windows-correctness lessons (meta)

1 participant