Repository navigation
ci: ship an actual binary, replacing the release that never released #151
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
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,150 +1,174 @@ | ||||||||||||||
| # Binary releases for the Rust workspace. | ||||||||||||||
| # | ||||||||||||||
| # What this replaces: every job in the previous version of this file was gated, | ||||||||||||||
| # directly or transitively, on a `check-python-pkg` step testing for | ||||||||||||||
| # `python/Cargo.toml`. That check has reported `exists=false` for the whole life | ||||||||||||||
| # of the workflow, so `build-wheels`, `build-sdist`, `publish-pypi` and | ||||||||||||||
| # `github-release` were all skipped on every run -- while the workflow itself | ||||||||||||||
| # reported success. Both tags cut so far shipped nothing: | ||||||||||||||
| # | ||||||||||||||
| # $ gh release view v0.1.0 --json assets -> {"assets":[]} | ||||||||||||||
| # $ gh release view v0.2.0 --json assets -> {"assets":[]} | ||||||||||||||
| # | ||||||||||||||
| # There is no Python package to publish any more, so the PyPI path and its | ||||||||||||||
| # `id-token: write` permission are gone rather than repaired. | ||||||||||||||
| # | ||||||||||||||
| # Two triggers, deliberately different: | ||||||||||||||
| # - push of a `v*` tag -> build every target AND publish a GitHub release | ||||||||||||||
| # - workflow_dispatch -> build every target and stop | ||||||||||||||
| # | ||||||||||||||
| # The dispatch path exists so this workflow can be exercised without cutting a | ||||||||||||||
| # release. That matters: a release workflow that is only ever run by tagging is | ||||||||||||||
| # a workflow whose first real test is a release you cannot take back. | ||||||||||||||
| # | ||||||||||||||
| # Targets are built on native runners rather than cross-compiled, so no `cross` | ||||||||||||||
| # or linker configuration is involved. ARM Linux and both macOS architectures | ||||||||||||||
| # have first-party runners, and this repository is public, so they are free. | ||||||||||||||
|
|
||||||||||||||
| name: Release | ||||||||||||||
|
|
||||||||||||||
| on: | ||||||||||||||
| push: | ||||||||||||||
| tags: | ||||||||||||||
| - "v*" | ||||||||||||||
| workflow_dispatch: | ||||||||||||||
| inputs: | ||||||||||||||
| tag: | ||||||||||||||
| description: "Release tag (e.g. v0.1.0)" | ||||||||||||||
| required: true | ||||||||||||||
|
|
||||||||||||||
| permissions: | ||||||||||||||
| contents: write | ||||||||||||||
| id-token: write | ||||||||||||||
|
|
||||||||||||||
| concurrency: | ||||||||||||||
| group: release-${{ github.ref }} | ||||||||||||||
| cancel-in-progress: false | ||||||||||||||
|
|
||||||||||||||
| jobs: | ||||||||||||||
| check-python-pkg: | ||||||||||||||
| name: check Python package exists | ||||||||||||||
| runs-on: ubuntu-latest | ||||||||||||||
| outputs: | ||||||||||||||
| exists: ${{ steps.check.outputs.exists }} | ||||||||||||||
| steps: | ||||||||||||||
| - name: Check out repository | ||||||||||||||
| uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | ||||||||||||||
| with: | ||||||||||||||
| persist-credentials: false | ||||||||||||||
| - name: Check for python/Cargo.toml | ||||||||||||||
| id: check | ||||||||||||||
| run: | | ||||||||||||||
| if [ -f "python/Cargo.toml" ]; then | ||||||||||||||
| echo "exists=true" >> "$GITHUB_OUTPUT" | ||||||||||||||
| else | ||||||||||||||
| echo "exists=false" >> "$GITHUB_OUTPUT" | ||||||||||||||
| echo "::warning::python/Cargo.toml not found; skipping Python release steps." | ||||||||||||||
| fi | ||||||||||||||
| permissions: | ||||||||||||||
| contents: read | ||||||||||||||
|
|
||||||||||||||
| build-wheels: | ||||||||||||||
| name: wheel (${{ matrix.target }}) | ||||||||||||||
| needs: [check-python-pkg] | ||||||||||||||
| if: needs.check-python-pkg.outputs.exists == 'true' | ||||||||||||||
| jobs: | ||||||||||||||
| build: | ||||||||||||||
| name: build (${{ matrix.target }}) | ||||||||||||||
| runs-on: ${{ matrix.os }} | ||||||||||||||
| strategy: | ||||||||||||||
| fail-fast: false | ||||||||||||||
| matrix: | ||||||||||||||
| include: | ||||||||||||||
| - os: ubuntu-latest | ||||||||||||||
| target: x86_64 | ||||||||||||||
| - os: ubuntu-latest | ||||||||||||||
| target: aarch64 | ||||||||||||||
| target: x86_64-unknown-linux-gnu | ||||||||||||||
| - os: ubuntu-24.04-arm | ||||||||||||||
| target: aarch64-unknown-linux-gnu | ||||||||||||||
| # Intel macOS. `macos-13` was the usual label for this and is now | ||||||||||||||
| # retired; `macos-15-intel` is the current x86_64 image. | ||||||||||||||
| - os: macos-15-intel | ||||||||||||||
| target: x86_64-apple-darwin | ||||||||||||||
| - os: macos-latest | ||||||||||||||
| target: x86_64 | ||||||||||||||
| - os: macos-latest | ||||||||||||||
| target: aarch64 | ||||||||||||||
| target: aarch64-apple-darwin | ||||||||||||||
| - os: windows-latest | ||||||||||||||
| target: x64 | ||||||||||||||
| runs-on: ${{ matrix.os }} | ||||||||||||||
| target: x86_64-pc-windows-msvc | ||||||||||||||
| steps: | ||||||||||||||
| - name: Check out repository | ||||||||||||||
| uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | ||||||||||||||
| with: | ||||||||||||||
| persist-credentials: false | ||||||||||||||
|
|
||||||||||||||
| - name: Set up Python | ||||||||||||||
| uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 | ||||||||||||||
| with: | ||||||||||||||
| python-version: "3.14.7" | ||||||||||||||
|
|
||||||||||||||
| - name: Build wheel (maturin) | ||||||||||||||
| uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b # v1 | ||||||||||||||
| with: | ||||||||||||||
| target: ${{ matrix.target }} | ||||||||||||||
| args: --release --out dist -m python/Cargo.toml | ||||||||||||||
|
|
||||||||||||||
| - name: Upload wheel | ||||||||||||||
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 | ||||||||||||||
| with: | ||||||||||||||
| name: wheel-${{ matrix.os }}-${{ matrix.target }} | ||||||||||||||
| path: dist/*.whl | ||||||||||||||
|
|
||||||||||||||
| build-sdist: | ||||||||||||||
| name: source distribution | ||||||||||||||
| needs: [check-python-pkg] | ||||||||||||||
| if: needs.check-python-pkg.outputs.exists == 'true' | ||||||||||||||
| runs-on: ubuntu-latest | ||||||||||||||
| steps: | ||||||||||||||
| - name: Check out repository | ||||||||||||||
| uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | ||||||||||||||
| # Same pin as rust.yml. A release binary is built with the toolchain the | ||||||||||||||
| # workspace declares, not with whatever `stable` happens to be that day. | ||||||||||||||
| - name: Install Rust 1.97.1 | ||||||||||||||
| uses: dtolnay/rust-toolchain@4be7066ada62dd38de10e7b70166bc74ed198c30 # stable | ||||||||||||||
| with: | ||||||||||||||
| persist-credentials: false | ||||||||||||||
| toolchain: "1.97.1" | ||||||||||||||
|
|
||||||||||||||
| - name: Set up Python | ||||||||||||||
| uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 | ||||||||||||||
| - name: Cache Cargo artifacts | ||||||||||||||
| uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 | ||||||||||||||
| with: | ||||||||||||||
| python-version: "3.14.7" | ||||||||||||||
|
|
||||||||||||||
| - name: Build sdist | ||||||||||||||
| key: release-${{ matrix.target }} | ||||||||||||||
|
|
||||||||||||||
| # --locked: a release must build from the committed Cargo.lock, never from | ||||||||||||||
| # a lockfile silently updated on the runner. | ||||||||||||||
| - name: Build | ||||||||||||||
| run: cargo build --release --locked | ||||||||||||||
|
|
||||||||||||||
| # The binary name is read from cargo metadata rather than hardcoded, so | ||||||||||||||
| # this workflow survives the `wh` -> `writ` crate rename (#145) without an | ||||||||||||||
| # edit. Archives are uniformly .tar.gz, including Windows: tar ships with | ||||||||||||||
| # Windows 10 and later, and one format keeps the checksum step trivial. | ||||||||||||||
| # | ||||||||||||||
| # `jq` needs no install step. It is preinstalled on all three GitHub | ||||||||||||||
| # runner images, Windows included, and this step is `shell: bash` so the | ||||||||||||||
| # same script runs everywhere. Verified rather than assumed: dispatching | ||||||||||||||
| # this workflow on the branch built x86_64-pc-windows-msvc successfully | ||||||||||||||
| # and produced a 679,896-byte artifact. | ||||||||||||||
| - name: Package | ||||||||||||||
| shell: bash | ||||||||||||||
| env: | ||||||||||||||
| TARGET: ${{ matrix.target }} | ||||||||||||||
| run: | | ||||||||||||||
| pip install build | ||||||||||||||
| python -m build --sdist -o dist python/ | ||||||||||||||
| set -euo pipefail | ||||||||||||||
| bin=$(cargo metadata --no-deps --format-version 1 \ | ||||||||||||||
| | jq -r '[.packages[].targets[] | select(.kind[] == "bin") | .name] | first') | ||||||||||||||
| if [ -z "$bin" ] || [ "$bin" = "null" ]; then | ||||||||||||||
| echo "::error::no binary target found in cargo metadata" | ||||||||||||||
| exit 1 | ||||||||||||||
| fi | ||||||||||||||
| echo "Binary: $bin" | ||||||||||||||
|
|
||||||||||||||
| - name: Upload sdist | ||||||||||||||
| ext="" | ||||||||||||||
| if [ "$RUNNER_OS" = "Windows" ]; then | ||||||||||||||
| ext=".exe" | ||||||||||||||
| fi | ||||||||||||||
|
|
||||||||||||||
| # `set -e` would already abort on a missing file, but with `cp: cannot | ||||||||||||||
| # stat ...` buried in the log rather than surfaced on the run. Name the | ||||||||||||||
| # two files a rename could plausibly break, so the failure explains | ||||||||||||||
| # itself. | ||||||||||||||
| for required in README.md LICENSE; do | ||||||||||||||
| if [ ! -f "$required" ]; then | ||||||||||||||
| echo "::error::${required} not found at the repository root; the release archive expects it" | ||||||||||||||
| exit 1 | ||||||||||||||
| fi | ||||||||||||||
| done | ||||||||||||||
|
|
||||||||||||||
| staging="${bin}-${TARGET}" | ||||||||||||||
| mkdir -p "dist/${staging}" | ||||||||||||||
| cp "target/release/${bin}${ext}" "dist/${staging}/" | ||||||||||||||
| cp README.md LICENSE "dist/${staging}/" | ||||||||||||||
|
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. Add error handling for missing README.md or LICENSE files. The
Suggested change
|
||||||||||||||
| tar --create --gzip --file "dist/${staging}.tar.gz" -C dist "${staging}" | ||||||||||||||
| echo "Packaged dist/${staging}.tar.gz" | ||||||||||||||
|
|
||||||||||||||
| - name: Upload build artifact | ||||||||||||||
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 | ||||||||||||||
| with: | ||||||||||||||
| name: sdist | ||||||||||||||
| name: binary-${{ matrix.target }} | ||||||||||||||
| path: dist/*.tar.gz | ||||||||||||||
|
|
||||||||||||||
| publish-pypi: | ||||||||||||||
| name: publish to PyPI | ||||||||||||||
| needs: [build-wheels, build-sdist] | ||||||||||||||
| runs-on: ubuntu-latest | ||||||||||||||
| environment: | ||||||||||||||
| name: pypi | ||||||||||||||
| url: https://pypi.org/p/worktrees-hives | ||||||||||||||
| steps: | ||||||||||||||
| - name: Download all artifacts | ||||||||||||||
| uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 | ||||||||||||||
| with: | ||||||||||||||
| merge-multiple: true | ||||||||||||||
| path: dist/ | ||||||||||||||
|
|
||||||||||||||
| - name: Publish to PyPI | ||||||||||||||
| uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # release/v1 | ||||||||||||||
|
|
||||||||||||||
| github-release: | ||||||||||||||
| name: create GitHub release | ||||||||||||||
| needs: [publish-pypi] | ||||||||||||||
| if-no-files-found: error | ||||||||||||||
|
|
||||||||||||||
| release: | ||||||||||||||
| name: publish GitHub release | ||||||||||||||
| needs: [build] | ||||||||||||||
| # Tags publish; workflow_dispatch stops after `build`, leaving the archives | ||||||||||||||
| # downloadable from the run for inspection. | ||||||||||||||
| if: startsWith(github.ref, 'refs/tags/v') | ||||||||||||||
| runs-on: ubuntu-latest | ||||||||||||||
| permissions: | ||||||||||||||
| contents: write | ||||||||||||||
| steps: | ||||||||||||||
| - name: Check out repository | ||||||||||||||
| uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | ||||||||||||||
| with: | ||||||||||||||
| persist-credentials: false | ||||||||||||||
|
|
||||||||||||||
| - name: Download all artifacts | ||||||||||||||
| - name: Download all build artifacts | ||||||||||||||
| uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 | ||||||||||||||
| with: | ||||||||||||||
| merge-multiple: true | ||||||||||||||
| path: dist/ | ||||||||||||||
|
|
||||||||||||||
| # Generated here, on one runner, rather than per-target: macOS has | ||||||||||||||
| # `shasum` and Linux has `sha256sum`, and doing it once avoids that split | ||||||||||||||
| # while producing a single file a user can verify the whole release with. | ||||||||||||||
| - name: Generate checksums | ||||||||||||||
| run: | | ||||||||||||||
| set -euo pipefail | ||||||||||||||
| cd dist | ||||||||||||||
| ls -1 ./*.tar.gz | ||||||||||||||
| sha256sum ./*.tar.gz > SHA256SUMS | ||||||||||||||
| cat SHA256SUMS | ||||||||||||||
|
|
||||||||||||||
| - name: Create GitHub release | ||||||||||||||
| uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2 | ||||||||||||||
| with: | ||||||||||||||
| generate_release_notes: true | ||||||||||||||
| files: dist/* | ||||||||||||||
| fail_on_unmatched_files: true | ||||||||||||||
| files: | | ||||||||||||||
| dist/*.tar.gz | ||||||||||||||
| dist/SHA256SUMS | ||||||||||||||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
🛑 Dependency Missing: The packaging script relies on
jqto parse cargo metadata, butjqis not installed by default on GitHub's windows-latest runner. This will cause the Windows build to fail.