-
Notifications
You must be signed in to change notification settings - Fork 0
ci: per-platform PyInstaller build + release workflow (#33) #63
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
79def36
0360681
cd8d0c9
9814f2f
edd39c6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,220 @@ | ||
| # Per-platform PyInstaller build + GitHub Release upload. | ||
| # | ||
| # Triggered on v* tags (and manually via workflow_dispatch for fork | ||
| # validation). Builds a single-file ``wbox`` binary for each of the four | ||
| # supported targets, computes its SHA-256, and uploads the binaries plus | ||
| # a combined manifest as assets on the GitHub Release for the tag. | ||
| # | ||
| # The PyPI publish job lives in ci.yml and is intentionally not touched | ||
| # here — both workflows fire on ``v*`` tags but operate independently. | ||
| # | ||
| # ---------------------------------------------------------------------- | ||
| # Manifest contract (LOCKED — downstream consumers: issue #42 install.sh | ||
| # rewrite, issue #43 install.ps1 parity) | ||
| # ---------------------------------------------------------------------- | ||
| # Manifest filename: SHA256SUMS.txt | ||
| # Format: standard sha256sum-compatible. One record per line: | ||
| # <hex-sha256><two-spaces><filename> | ||
| # Sort order: ASCII-sorted by filename (stable across runs) | ||
| # Binary filenames: | ||
| # wbox-linux-x64 | ||
| # wbox-macos-arm64 | ||
| # wbox-macos-x64 | ||
| # wbox-windows-x64.exe | ||
| # Manifest assets are attached to the same GitHub Release as the four | ||
| # binaries. Install scripts can fetch SHA256SUMS.txt and a single binary | ||
| # from the same release URL prefix and verify the binary against the | ||
| # matching line. | ||
| # ---------------------------------------------------------------------- | ||
|
|
||
| name: Release Binaries | ||
|
|
||
| on: | ||
| push: | ||
| tags: ["v*"] | ||
| workflow_dispatch: {} | ||
|
|
||
| permissions: | ||
| contents: read | ||
|
|
||
| jobs: | ||
| build: | ||
| name: Build ${{ matrix.target }} | ||
| runs-on: ${{ matrix.os }} | ||
| strategy: | ||
| fail-fast: false | ||
| matrix: | ||
| include: | ||
| - os: ubuntu-latest | ||
| target: linux-x64 | ||
| binary: wbox-linux-x64 | ||
| - os: macos-15-intel # Intel runner (x86_64) — macos-13 was retired Dec 2025 | ||
| target: macos-x64 | ||
| binary: wbox-macos-x64 | ||
| - os: macos-14 # Apple Silicon runner (arm64) | ||
| target: macos-arm64 | ||
| binary: wbox-macos-arm64 | ||
| - os: windows-latest | ||
| target: windows-x64 | ||
| binary: wbox-windows-x64.exe | ||
| steps: | ||
| - name: Checkout | ||
| uses: actions/checkout@v4 | ||
|
|
||
| - name: Set up Python | ||
| uses: actions/setup-python@v5 | ||
| with: | ||
| python-version: "3.12" | ||
|
|
||
| - name: Install runtime + build deps | ||
| run: | | ||
| python -m pip install --upgrade pip | ||
| pip install . | ||
| pip install pyinstaller | ||
|
|
||
| - name: Build single-file binary (Unix) | ||
| if: runner.os != 'Windows' | ||
| shell: bash | ||
| run: | | ||
| pyinstaller \ | ||
| --onefile \ | ||
| --name "${{ matrix.binary }}" \ | ||
| --noconfirm \ | ||
| --collect-data wealthbox_tools \ | ||
| --copy-metadata wealthbox-cli \ | ||
| scripts/wbox_entry.py | ||
|
Comment on lines
+83
to
+85
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
For these PyInstaller builds, the smoke test invokes Useful? React with 👍 / 👎. |
||
| test -f "dist/${{ matrix.binary }}" | ||
| chmod +x "dist/${{ matrix.binary }}" | ||
|
|
||
| - name: Build single-file binary (Windows) | ||
| if: runner.os == 'Windows' | ||
| shell: pwsh | ||
| run: | | ||
| pyinstaller ` | ||
| --onefile ` | ||
| --name "${{ matrix.binary }}" ` | ||
| --noconfirm ` | ||
| --collect-data wealthbox_tools ` | ||
| --copy-metadata wealthbox-cli ` | ||
| scripts/wbox_entry.py | ||
| if (-not (Test-Path "dist/${{ matrix.binary }}")) { | ||
| throw "expected dist/${{ matrix.binary }} to exist" | ||
| } | ||
|
|
||
| - name: Smoke-test binary | ||
| # `--version` only proves the entry point runs. `skills install` | ||
| # exercises bundled package data — it reads | ||
| # wealthbox_tools/skills/wealthbox-crm via importlib.resources — so | ||
| # this fails loudly if --collect-data wealthbox_tools didn't bundle | ||
| # the skill template. Runs against the fresh runner HOME with | ||
| # --no-bootstrap to skip the post-install confirm prompt. | ||
| run: | | ||
| ./dist/${{ matrix.binary }} --version | ||
| ./dist/${{ matrix.binary }} skills install --platform claude-code-user --no-bootstrap | ||
|
|
||
| - name: Compute SHA-256 (Unix) | ||
| if: runner.os != 'Windows' | ||
| shell: bash | ||
| working-directory: dist | ||
| run: | | ||
| if command -v sha256sum >/dev/null 2>&1; then | ||
| sha256sum "${{ matrix.binary }}" > "${{ matrix.binary }}.sha256" | ||
| else | ||
| # macOS: shasum -a 256 emits sha256sum-compatible "<hash> <name>" | ||
| shasum -a 256 "${{ matrix.binary }}" > "${{ matrix.binary }}.sha256" | ||
| fi | ||
| cat "${{ matrix.binary }}.sha256" | ||
|
|
||
| - name: Compute SHA-256 (Windows) | ||
| if: runner.os == 'Windows' | ||
| shell: pwsh | ||
| working-directory: dist | ||
| run: | | ||
| $hash = (Get-FileHash -Algorithm SHA256 "${{ matrix.binary }}").Hash.ToLower() | ||
| # sha256sum format: "<hex> <filename>" (two spaces, LF line ending) | ||
| $line = "$hash ${{ matrix.binary }}`n" | ||
| [System.IO.File]::WriteAllText( | ||
| (Join-Path $PWD "${{ matrix.binary }}.sha256"), | ||
| $line | ||
| ) | ||
| Get-Content "${{ matrix.binary }}.sha256" | ||
|
|
||
| - name: Upload build artifact | ||
| uses: actions/upload-artifact@v4 | ||
| with: | ||
| name: wbox-${{ matrix.target }} | ||
| path: | | ||
| dist/${{ matrix.binary }} | ||
| dist/${{ matrix.binary }}.sha256 | ||
| if-no-files-found: error | ||
| retention-days: 7 | ||
|
|
||
| manifest-and-release: | ||
| name: Combine manifest and upload to release | ||
| needs: [build] | ||
| runs-on: ubuntu-latest | ||
| permissions: | ||
| contents: write | ||
| steps: | ||
| - name: Download all build artifacts | ||
| uses: actions/download-artifact@v4 | ||
| with: | ||
| path: artifacts | ||
|
|
||
| - name: Stage release files | ||
| shell: bash | ||
| run: | | ||
| mkdir -p release | ||
| # Each artifact directory contains one binary + one .sha256 file. | ||
| find artifacts -type f \( -name 'wbox-*' -a ! -name '*.sha256' \) -exec cp -v {} release/ \; | ||
| find artifacts -type f -name '*.sha256' -exec cp -v {} release/ \; | ||
| ls -la release | ||
|
|
||
| - name: Build combined SHA256SUMS.txt | ||
| shell: bash | ||
| working-directory: release | ||
| run: | | ||
| # Concatenate per-binary sums into a single sha256sum-compatible | ||
| # manifest, ASCII-sorted by filename for stability. | ||
| # Each .sha256 file is already in "<hex> <filename>" form. | ||
| cat *.sha256 | LC_ALL=C sort -k 2 > SHA256SUMS.txt | ||
| echo "----- SHA256SUMS.txt -----" | ||
| cat SHA256SUMS.txt | ||
| echo "--------------------------" | ||
| # Verify the manifest against the staged binaries as a sanity check. | ||
| sha256sum -c SHA256SUMS.txt | ||
|
|
||
| - name: Upload combined manifest artifact | ||
| uses: actions/upload-artifact@v4 | ||
| with: | ||
| name: SHA256SUMS | ||
| path: release/SHA256SUMS.txt | ||
| if-no-files-found: error | ||
|
|
||
| - name: Upload assets to GitHub Release | ||
| if: startsWith(github.ref, 'refs/tags/') | ||
| env: | ||
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
| TAG: ${{ github.ref_name }} | ||
| GH_REPO: ${{ github.repository }} | ||
| shell: bash | ||
| working-directory: release | ||
| run: | | ||
| # Ensure a release exists for the tag. If the PyPI publish job in | ||
| # ci.yml or a manual release-create created it first, --clobber on | ||
| # upload handles re-runs cleanly. | ||
| if ! gh release view "$TAG" >/dev/null 2>&1; then | ||
| gh release create "$TAG" \ | ||
| --title "$TAG" \ | ||
| --notes "Automated release for $TAG. Binaries built by release-binaries.yml; PyPI wheel published by ci.yml." | ||
| fi | ||
| # Upload all four binaries + the combined manifest. Per-platform | ||
| # .sha256 sidecars are intentionally not uploaded — SHA256SUMS.txt | ||
| # is the single source of truth for installers. | ||
| gh release upload "$TAG" \ | ||
| wbox-linux-x64 \ | ||
| wbox-macos-arm64 \ | ||
| wbox-macos-x64 \ | ||
| wbox-windows-x64.exe \ | ||
| SHA256SUMS.txt \ | ||
| --clobber | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| """PyInstaller entry shim for the wbox CLI. | ||
|
|
||
| PyInstaller's --onefile mode prefers a script file over a module:function | ||
| console-script reference. This shim simply imports the Typer app defined | ||
| at ``wealthbox_tools.cli.main:app`` and calls it, so the resulting binary | ||
| behaves identically to the ``wbox`` console script registered in | ||
| ``pyproject.toml``. | ||
| """ | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| from wealthbox_tools.cli.main import app | ||
|
|
||
|
|
||
| def main() -> None: | ||
| app() | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| main() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For the released binaries, commands such as
wbox skills installandwbox skills upgradeloadwealthbox_tools/skills/wealthbox-crmviaimportlib.resources, but this PyInstaller invocation only includes the Python entry script/modules. PyInstaller’s documented options require adding non-code assets explicitly (--add-data) or collecting package data (--collect-data, “Collect all data from the specified package or module”), so the--versionsmoke test can pass while the binary omits the skill template and later fails when users run those commands. Please add the bundled skill data to both PyInstaller invocations and smoke-test a resource-backed command.Useful? React with 👍 / 👎.