Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,15 @@ jobs:
shell: bash
run: ./ci.sh test

# Linux-only inside the gate: soldr's compile daemon cannot spawn
# on GHA macOS/Windows runners (zackees/soldr#1300); the gate
# self-skips there.
- name: backend-smoke
id: backend_smoke
continue-on-error: true
shell: bash
run: ./ci.sh backend_smoke

- name: action-yaml
id: action_yaml
continue-on-error: true
Expand All @@ -156,6 +165,7 @@ jobs:
[ "${{ steps.clippy.outcome }}" = "failure" ] && fail+=("clippy") || true
[ "${{ steps.ruff.outcome }}" = "failure" ] && fail+=("ruff") || true
[ "${{ steps.test.outcome }}" = "failure" ] && fail+=("test") || true
[ "${{ steps.backend_smoke.outcome }}" = "failure" ] && fail+=("backend_smoke") || true
[ "${{ steps.action_yaml.outcome }}" = "failure" ] && fail+=("action_yaml") || true
[ "${{ steps.action_surface.outcome }}" = "failure" ] && fail+=("action_surface") || true
if [ ${#fail[@]} -gt 0 ]; then
Expand Down
1 change: 1 addition & 0 deletions ci.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
"ruff",
"build",
"test",
"backend_smoke",
"action_yaml",
"action_surface",
]
Expand Down
8 changes: 3 additions & 5 deletions ci/build_wheel.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,13 @@
binary as a raw wheel script bypasses the Python launcher entirely.
See fbuild#747 / zackees/template-python-rust-cmd#2 (items 1 + 10).
"""

from __future__ import annotations

import base64
import hashlib
import json
import os
import shutil
import platform
import subprocess
import sys
Expand Down Expand Up @@ -289,9 +289,7 @@ def inject_cli_into_wheel(binary: Path) -> Path:
entries: dict[str, bytes] = {name: wf.read(name) for name in wf.namelist()}

if record_arcname not in entries:
raise SystemExit(
f"wheel has no {record_arcname}; cannot inject CLI script"
)
raise SystemExit(f"wheel has no {record_arcname}; cannot inject CLI script")

# Append a RECORD row for the new script. RECORD's own row keeps
# empty hash + size per spec — we preserve that.
Expand All @@ -311,7 +309,7 @@ def inject_cli_into_wheel(binary: Path) -> Path:
# pip installs land it +x. create_system=3 = Unix.
if name == script_arcname:
info.create_system = 3
info.external_attr = (0o755 << 16)
info.external_attr = 0o755 << 16
wf.writestr(info, data)
return wheel_path

Expand Down
52 changes: 52 additions & 0 deletions ci/gates/backend_smoke.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
"""PEP 517 backend smoke: `uv build --wheel` through the soldr backend.

The `[build-system]` in pyproject.toml routes wheel builds through the
soldr backend (which drives `maturin pep517` under a rustc-caching
wrapper). The `test` gate deliberately avoids that path (see its
docstring), so without this gate the backend swap would ship untested.

This gate builds one wheel via the real backend — exactly what a
downstream `pip install template-python-rust-cmd` (sdist) or
`uv build` does.

Linux-only: soldr's compile daemon cannot spawn on GHA macOS/Windows
runners (zackees/soldr#1300) — macOS fails with "embedded compile
dispatch failed after 30000ms budget: NotRunning" and Windows wedges
the step for ~an hour. On those platforms the gate skips (returns 0)
rather than burning an hour to report a known-upstream condition.
Re-enable everywhere once soldr#1300 is fixed and the pin in
pyproject.toml is bumped past the fixed release.
"""

from __future__ import annotations

import shutil
import subprocess
import sys
from pathlib import Path

ROOT = Path(__file__).resolve().parents[2]


def run() -> int:
if sys.platform != "linux":
print(
"backend_smoke: skipped on non-Linux (soldr compile daemon "
"cannot spawn on GHA macOS/Windows runners — "
"zackees/soldr#1300). The backend path is exercised on the "
"Linux lanes."
)
return 0
if shutil.which("uv") is None:
print("uv not on PATH; cannot run backend_smoke gate", file=sys.stderr)
return 1
proc = subprocess.run(
["uv", "build", "--wheel", "--out-dir", "dist/backend-smoke"],
cwd=ROOT,
check=False,
)
return proc.returncode


if __name__ == "__main__":
raise SystemExit(run())
3 changes: 2 additions & 1 deletion ci/gates/ruff.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
points at a real issue (unused imports, shadowed names, etc.).

Invoked via `uv run --no-project --with ruff` so we get a hermetic ruff
without triggering the maturin build the surrounding pyproject.toml
without triggering the wheel build (soldr PEP 517 backend driving
maturin) the surrounding pyproject.toml
otherwise demands. `--with ruff` provisions the dep at script-time even
when the script itself has no PEP 723 deps declared for it.

Expand Down
32 changes: 25 additions & 7 deletions ci/gates/test.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
"""Test gate: `cargo test --workspace` + `pytest`.

This is one of the named entry points that legitimately *needs* the
maturin wheel built (pytest imports `template_python_rust_cmd._native`).
We invoke it via plain `uv run` (project context kept on purpose) so
maturin develop materializes the extension module before pytest runs.
extension module built (pytest imports `template_python_rust_cmd._native`).
We sync the dev deps with `--no-install-project` and let a direct
`maturin develop` materialize the extension module before pytest runs —
deliberately NOT via the PEP 517 backend (soldr), whose compile daemon
cannot spawn on GHA macOS/Windows runners (zackees/soldr#1300). The
backend path is covered by the `backend_smoke` gate on Linux.

Reserve this opt-in to the build for named entry points — see [zccache#835
rule 5](https://github.com/zackees/zccache/issues/835). Other gates use
Expand Down Expand Up @@ -38,13 +41,28 @@ def run() -> int:
if rc != 0:
return rc

# `maturin develop` needs the project context, so we do NOT pass
# --no-project here. This is the documented opt-in to the full
# build (see [zccache#835] rule 5).
# Materialize the dev dependency group (maturin, pytest) WITHOUT
# installing the project itself. A plain `uv run` here would sync
# the project editable through the PEP 517 backend (soldr), and
# soldr's compile daemon cannot spawn on GHA macOS/Windows runners
# (zackees/soldr#1300): macOS dies with "embedded compile dispatch
# failed after 30000ms budget: NotRunning", Windows wedges the step
# for an hour. The extension module is instead built by the direct
# `maturin develop` call below, which does not route through the
# backend. The backend itself is exercised by the `backend_smoke`
# gate on Linux, where the daemon spawns fine.
rc = _run(["uv", "sync", "--no-install-project"])
if rc != 0:
return rc

# `maturin develop` builds + installs the extension module into the
# synced venv directly (no PEP 517 backend involved). `--no-sync`
# keeps uv from re-syncing (and re-triggering the backend build).
rc = _run(
[
"uv",
"run",
"--no-sync",
"maturin",
"develop",
"--uv",
Expand All @@ -55,7 +73,7 @@ def run() -> int:
if rc != 0:
return rc

rc = _run(["uv", "run", "pytest"])
rc = _run(["uv", "run", "--no-sync", "pytest"])
return rc


Expand Down
3 changes: 2 additions & 1 deletion ci/hooks/tool_guard.py
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,8 @@ def _check_segment(seg: str) -> tuple[str, str] | None:
"Use `./ci.sh <gate>` for lint/gate invocations, or run "
"your build through a named entry point (./test, ./build, "
"ci/build_wheel.py). Bare `uv run` walks up to pyproject.toml "
"and triggers the maturin wheel build before your script "
"and triggers the wheel build (soldr PEP 517 backend driving "
"maturin) before your script "
"starts. Add `--no-project --script` to skip discovery and "
"use the PEP 723 inline-deps path.",
)
Expand Down
4 changes: 2 additions & 2 deletions install
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ Verifies the toolchain shape this repo expects:
- The pinned Rust toolchain (rust-toolchain.toml) is resolvable.
- The dev dependency group is materialized.

Does NOT trigger a maturin build — that's reserved for `./test` /
`./build`. Use `./install` after a fresh clone to confirm the
Does NOT trigger a wheel build (soldr PEP 517 backend / maturin) —
that's reserved for `./test` / `./build`. Use `./install` after a fresh clone to confirm the
environment is sane.
"""

Expand Down
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[build-system]
requires = ["maturin>=1.7,<2"]
build-backend = "maturin"
requires = ["soldr>=0.7.98"]
build-backend = "soldr"

[project]
name = "template-python-rust-cmd"
Expand Down
1 change: 1 addition & 0 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
script) is fundamentally not visible from the source tree anymore —
there's no `_bin/` directory to look at. See #9.
"""

from __future__ import annotations

import os
Expand Down
Loading