feat(template): split standalone-executable toggle into launcher/compiler/freezer - #43
Conversation
…iler/freezer
Replace the single `include_pycrucible` toggle with three independent,
combinable toggles mapped to distinct architectures: `include_launcher`
(PyCrucible uv-bootstrap launcher), `include_compiler` (Nuitka native
compiler), and `include_freezer` (PyInstaller offline freezer).
- copier.yml: three flat booleans (no `when:` gating)
- pyproject.toml.jinja: keywords, `tool`-group deps, renamed `[tool.pycrucible]` guard
- new conditional PyInstaller `.spec` file with entrypoint resolution
- mise.toml.jinja: split the single `{% raw %}` block (fixing a latent bug where
the PyCrucible task rendered unconditionally) into gated package/freeze/compile tasks
- release-please.yml.jinja: build-launcher/build-freezer/build-compiler matrix
jobs (fail-fast: false) with needs/download wiring
- docs: ADR-007, plus ADR-002/README/CLAUDE.md updates
See ADR-007 for the design rationale.
Reviewer's GuideSplits the previous single File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey - I've left some high level feedback:
- The entrypoint resolution logic for CLI/GUI/TUI/web/main is duplicated across the PyInstaller spec, Nuitka commands, and workflow; consider extracting this into a shared Jinja macro or variable to keep it consistent and easier to change.
- The Nuitka build command (flags and output conventions) is currently repeated in both the mise task and the GitHub Actions workflow; you could centralize this in a single template construct to avoid drift between local and CI builds.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The entrypoint resolution logic for CLI/GUI/TUI/web/__main__ is duplicated across the PyInstaller spec, Nuitka commands, and workflow; consider extracting this into a shared Jinja macro or variable to keep it consistent and easier to change.
- The Nuitka build command (flags and output conventions) is currently repeated in both the mise task and the GitHub Actions workflow; you could centralize this in a single template construct to avoid drift between local and CI builds.Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
The ADR covers three architectures (launcher/compiler/freezer), not just the PyCrucible/freezer angle, so the filename now matches the title.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 92bb8069a7
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…entrypoint Address PR review (Codex P2s + Sourcery): - Generalize __main__.py from CLI-only into the single runnable entrypoint that dispatches to whichever component is enabled (CLI/GUI/TUI/web/MCP/worker), each exposing a main() that actually invokes the app. Freezing/compiling a bare module like cli/app.py only defined `app` without calling it, so the binary exited without running anything. - Point the PyInstaller spec, Nuitka (mise + CI), and [tool.pycrucible] all at src/<pkg>/__main__.py, so the component-selection logic lives in one place instead of being duplicated across three files. - Un-ignore the committed PyInstaller spec in .gitignore (the default *.spec ignore would keep it uncommitted and break the release fresh-checkout build). - Document the entrypoint/gitignore decisions in ADR-007. Rendered output is ruff-clean across all component branches.
|
Thanks for the review — addressed in Codex P2 — Codex P2 — frozen/compiled binaries were no-ops ✅ The root cause was pointing the builders at modules like Sourcery — duplicated entrypoint resolution across spec/Nuitka/workflow ✅ Same change resolves this: the CLI/GUI/TUI/web/ Sourcery — Nuitka flags repeated in mise + CI — left as-is. With the entrypoint centralized, what remains duplicated is one short flag string ( Verification: rendered |
…/OS and harden executable toggles Give each build-launcher/freezer/compiler job a per-OS label/ext matrix and name binaries <pkg>-<tool>-<label> so download-artifact's merge-multiple and the release asset names never collide (previously a single toggle already dropped a platform across OSes, and Nuitka shipped a generic __main__.bin). Also propagate GUI/TUI exit codes via sys.exit, matrix-gate Nuitka's GUI window flags, fix the gh release upload glob to skip the dist/executables dir, redraw the README release-flow diagram, add executable-toggle CI scenarios with a unique-naming assertion, add a __main__ entrypoint test, and clarify ADR-007.
…PR CI
Add build-{launcher,freezer,compiler}-check jobs to ci.yml, gated on the same
toggles as the release builds, so each enabled executable is built on every
PR/push (per-OS, fail-fast: false). The test suite runs the source tree and
never the built binary, so these are the only packaging guard before release:
each verifies the binary was produced, smoke-runs it with --help for a CLI
project, and uploads a 7-day preview artifact for reviewers. They mirror the
release-please.yml build commands (and unique per-OS naming) but never publish,
and they gate the check aggregation job. Add include_cli to the template-ci
launcher scenario so the CLI smoke-run steps get actionlint-ed, and document the
jobs in CLAUDE.md and ADR-007.
… no-op entrypoint Address PR review findings on the launcher/compiler/freezer toggles: - release-please: add setup-python to build-freezer/build-compiler, give the launcher checkout fetch-depth: 0 (correct hatch-vcs version), drop the unused ext field from the launcher matrix, and guard the release-attach step against an empty dist/ (xargs would otherwise publish zero assets). - Nuitka: drop --macos-create-app-bundle (emits a .app directory that breaks the single-file verify/upload) and add --warn-implicit-exceptions / --warn-unusual-code so analysis gaps surface in the build log. - PyInstaller spec: default upx=False (not on CI runners; corrupts some macOS/Windows binaries) with an opt-in TODO. - __main__: no-component main() now raises SystemExit instead of a silent no-op, and documents the CLI>GUI>TUI>web>MCP>worker dispatch precedence. - tests: pin the dispatch wiring (app/_entry resolved) and assert the no-component case exits loudly. - template-ci: add a web+freezer render scenario and tighten the artifact-name assertion to require the matrix.label per-OS qualifier. - docs: sync ADR-007 and CLAUDE.md to the above.
The previous commit failed template-CI style checks (ruff, run with autofix):
- __main__.py no-component branch raised SystemExit with a string literal,
tripping EM101 (raw-string-in-exception) and DOC501 (raised exception
missing from docstring). Switch to sys.exit("...") — same loud non-zero
exit and message, but no `raise` statement, so neither rule fires.
- test_main.py asserted on main_module._entry, tripping SLF001
(private-member-access). Read it from the module namespace via
vars(main_module)["_entry"] instead.
- Fix the test's Jinja whitespace control so it renders PEP 8-clean
(no stray blank lines / import grouping) in every toggle combination,
rather than relying on the style env's autofix.
Verified with `ruff check --no-fix` and the entrypoint tests across the
no-component, CLI, GUI, and web renders.
basedpyright (reportImplicitStringConcatenation) rejected the two adjacent string literals in the no-component sys.exit(...). Collapse to a single short literal; drop the package-name prefix (the binary already identifies the app) so the line stays under the 88-char limit regardless of repo name.
Summary
Replaces the single
include_pycrucibletoggle with three independent, combinable toggles, each mapped to a distinct standalone-executable architecture:include_launcherinclude_compilerinclude_freezerThe previous toggle conflated a brand (PyCrucible) with a capability and covered only one of three ecosystem architectures. Architecture-named booleans let a project combine paths (e.g. a small launcher for connected users and an offline freezer for air-gapped distribution) and survive a future tool swap without renaming. See ADR-007.
Changes
copier.yml— three flat booleans (nowhen:gating)template/pyproject.toml.jinja— keywords,tool-group deps (pycrucible/nuitka/pyinstaller),[tool.pycrucible]guard renamed toinclude_launchertemplate/{% if include_freezer %}{{github_repo_name}}.spec{% endif %}.jinja— PyInstaller spec with entrypoint resolution (cli→gui→tui→web→__main__) andconsole=Falsefor GUItemplate/mise.toml.jinja— split the single{% raw %}block (fixes a latent bug where the PyCrucible task rendered unconditionally) into gatedpackage/freeze/compiletasks, all viauv run --locked --group tooltemplate/.github/workflows/release-please.yml.jinja—build-launcher/build-freezer/build-compilerjobs (fail-fast: false),needs:+ download wiringVerification
validate-pyproject✅ · mise + pyproject parse as valid TOML ✅ · workflow parses as valid YAML ✅ ·actionlintexit 0 ✅Breaking change
include_pycrucible: true→include_launcher: trueoncopier update(Copier does not auto-migrate renamed variables; documented in ADR-007).🤖 Generated with Claude Code
Summary by Sourcery
Split the previous single standalone-executable option into three independent launcher, compiler, and freezer toggles and wire them through configuration, build tooling, CI, and docs.
New Features:
Enhancements:
Documentation: