release: Update CHANGELOG & bump version #47
Workflow file for this run
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
| name: Publish Version Release | |
| on: | |
| push: | |
| tags: | |
| - v[0-9].[0-9]+.[0-9]+ | |
| jobs: | |
| # Validate that tag version matches package versions | |
| validate-version: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| version: ${{ steps.extract.outputs.version }} | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v7.0.0 | |
| - name: Extract version from tag | |
| id: extract | |
| run: | | |
| VERSION=${GITHUB_REF#refs/tags/v} | |
| echo "version=$VERSION" >> $GITHUB_OUTPUT | |
| echo "Extracted version: $VERSION" | |
| - name: Validate Cargo.toml version | |
| run: | | |
| CARGO_VERSION=$(grep '^version = ' Cargo.toml | head -1 | sed 's/version = "\(.*\)"/\1/') | |
| if [ "$CARGO_VERSION" != "${{ steps.extract.outputs.version }}" ]; then | |
| echo "ERROR: Tag version (${{ steps.extract.outputs.version }}) does not match Cargo.toml version ($CARGO_VERSION)" | |
| exit 1 | |
| fi | |
| echo "Cargo.toml version matches: $CARGO_VERSION" | |
| - name: Validate CHANGELOG entry for this version | |
| run: | | |
| VERSION="${{ steps.extract.outputs.version }}" | |
| if ! grep -q "^## \[${VERSION}\]" CHANGELOG.md; then | |
| echo "ERROR: CHANGELOG.md has no '## [${VERSION}]' entry." | |
| echo "Run 'just generate-changelog' locally before tagging, then commit and re-tag." | |
| exit 1 | |
| fi | |
| echo "CHANGELOG.md contains entry for v${VERSION}" | |
| - name: Validate pyproject.toml version | |
| run: | | |
| # pyproject.toml version is dynamic, synced to Cargo.toml, so just verify Cargo.toml is correct | |
| echo "Python version is dynamic and synced to Cargo.toml" | |
| test-rust: | |
| uses: ./.github/workflows/test_rust.yml | |
| needs: [validate-version] | |
| secrets: inherit | |
| test-python: | |
| uses: ./.github/workflows/test_python.yml | |
| needs: [validate-version] | |
| secrets: inherit | |
| test-examples: | |
| uses: ./.github/workflows/test_examples.yml | |
| needs: [validate-version] | |
| secrets: inherit | |
| # Integration (network) tests gate the release. They run in parallel with the | |
| # unit tests above; `prepare-release-notes` (and therefore every downstream | |
| # build/publish job, which all chain off it) waits for this to pass. Retries | |
| # inside integration_tests.yml absorb transient remote-host throttling. | |
| integration-tests: | |
| uses: ./.github/workflows/integration_tests.yml | |
| needs: [validate-version] | |
| secrets: inherit | |
| # Extract this release's notes from the CHANGELOG.md committed in the tag. | |
| # The maintainer runs `just generate-changelog` locally before tagging, so the | |
| # CHANGELOG entry is already part of the tagged commit. This job only reads | |
| # the file — it does not mutate or push anything, keeping the working tree | |
| # clean for downstream publish steps. | |
| prepare-release-notes: | |
| runs-on: ubuntu-latest | |
| needs: [validate-version, test-rust, test-python, test-examples, integration-tests] | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v7.0.0 | |
| - name: Extract release notes from CHANGELOG | |
| run: | | |
| VERSION=${{ needs.validate-version.outputs.version }} | |
| python3 scripts/extract_release_notes.py \ | |
| --version "$VERSION" \ | |
| --changelog CHANGELOG.md \ | |
| --output release_notes.md | |
| echo "----- release_notes.md -----" | |
| cat release_notes.md | |
| - name: Upload release notes artifact | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: release-notes | |
| path: release_notes.md | |
| # Generate type stubs once (platform-independent) for inclusion in all packages | |
| generate-stubs: | |
| needs: [prepare-release-notes] | |
| runs-on: ubuntu-latest | |
| env: | |
| RUST_TOOLCHAIN: stable | |
| PYTHON_VERSION: 3.12 | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v7.0.0 | |
| - name: Set up Rust | |
| uses: actions-rust-lang/setup-rust-toolchain@v1.16.1 | |
| with: | |
| toolchain: ${{ env.RUST_TOOLCHAIN }} | |
| - name: Set default Rust toolchain | |
| run: | | |
| rustup update ${{ env.RUST_TOOLCHAIN }} | |
| rustup default ${{ env.RUST_TOOLCHAIN }} | |
| - name: Install the latest version of uv | |
| uses: astral-sh/setup-uv@v8.2.0 | |
| with: | |
| enable-cache: true | |
| - name: Install Python | |
| run: uv python install ${{ env.PYTHON_VERSION }} | |
| - name: Create virtual environment | |
| run: uv venv -p ${{ env.PYTHON_VERSION }} | |
| - name: Install package and dependencies | |
| run: | | |
| uv sync --group dev | |
| uv pip install -e . | |
| - name: Generate stubs | |
| run: ./scripts/generate_stubs.sh | |
| - name: Upload stubs artifact | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: type-stubs | |
| path: brahe/_brahe.pyi | |
| python-build-source: | |
| needs: [prepare-release-notes, generate-stubs] | |
| runs-on: ubuntu-latest | |
| strategy: | |
| matrix: | |
| os: [ubuntu-latest] | |
| python-version: ['3.12'] | |
| rust-toolchain: [stable] | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v7.0.0 | |
| - name: Download type stubs | |
| uses: actions/download-artifact@v8 | |
| with: | |
| name: type-stubs | |
| path: brahe/ | |
| - name: Set up Rust | |
| uses: actions-rust-lang/setup-rust-toolchain@v1.16.1 | |
| with: | |
| toolchain: ${{ matrix.rust-toolchain }} | |
| - name: Set default Rust toolchain | |
| run: | | |
| rustup update ${{ matrix.rust-toolchain }} | |
| rustup default ${{ matrix.rust-toolchain }} | |
| - name: Install the latest version of uv | |
| uses: astral-sh/setup-uv@v8.2.0 | |
| with: | |
| enable-cache: true | |
| - name: Install Python ${{ matrix.python-version }} | |
| run: uv python install ${{ matrix.python-version }} | |
| - name: Create virtual environment | |
| run: uv venv -p ${{ matrix.python-version }} | |
| - name: Activate virtual environment | |
| run: source .venv/bin/activate | |
| - name: Install build | |
| run: uv pip install -U build | |
| - name: Build source distribution | |
| run: uv run python -m build --sdist | |
| - name: Upload artifact | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| path: dist/*.tar.gz | |
| name: brahe-${{ github.ref_name }}-${{ runner.os }}-source | |
| python-build-wheels: | |
| needs: [prepare-release-notes, generate-stubs] | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| matrix: | |
| os: [ubuntu-latest, ubuntu-24.04-arm, macos-latest, windows-latest] | |
| python-version: ['3.10', '3.11', '3.12', '3.13', '3.14'] | |
| rust-toolchain: [stable] | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v7.0.0 | |
| - name: Download type stubs | |
| uses: actions/download-artifact@v8 | |
| with: | |
| name: type-stubs | |
| path: brahe/ | |
| - name: Set up Rust | |
| uses: actions-rust-lang/setup-rust-toolchain@v1.16.1 | |
| with: | |
| toolchain: ${{ matrix.rust-toolchain }} | |
| - name: Set default Rust toolchain | |
| run: | | |
| rustup update ${{ matrix.rust-toolchain }} | |
| rustup default ${{ matrix.rust-toolchain }} | |
| - name: Install the latest version of uv | |
| uses: astral-sh/setup-uv@v8.2.0 | |
| with: | |
| enable-cache: true | |
| - name: Install Python ${{ matrix.python-version }} | |
| run: uv python install ${{ matrix.python-version }} | |
| - name: Build wheel with maturin | |
| uses: PyO3/maturin-action@v1 | |
| with: | |
| command: build | |
| rust-toolchain: ${{ matrix.rust-toolchain }} | |
| args: --release -i python${{ matrix.python-version }} | |
| - name: Upload artifact | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| path: target/wheels/*.whl | |
| name: brahe-${{ github.ref_name }}-python${{ matrix.python-version }}-${{ matrix.os }}-wheel | |
| release-python: | |
| needs: [python-build-source, python-build-wheels] | |
| runs-on: ubuntu-latest | |
| environment: release | |
| permissions: | |
| id-token: write | |
| steps: | |
| # Download all artifacts to dist folder | |
| - name: Download artifacts | |
| uses: actions/download-artifact@v8 | |
| - name: Copy artifacts to dist folder | |
| run: | | |
| mkdir dist | |
| cp -r ${{ github.workspace }}/*-source/* dist/ | |
| cp -r ${{ github.workspace }}/*-wheel/* dist/ | |
| - name: Upload package to PyPI | |
| uses: pypa/gh-action-pypi-publish@release/v1 | |
| release-rust: | |
| runs-on: ubuntu-latest | |
| needs: [python-build-source, python-build-wheels] | |
| environment: release | |
| permissions: | |
| id-token: write | |
| env: | |
| RUST_TOOLCHAIN: stable | |
| steps: | |
| - uses: actions/checkout@v7.0.0 | |
| - name: Set up Rust toolchain | |
| uses: actions-rust-lang/setup-rust-toolchain@v1.16.1 | |
| with: | |
| toolchain: ${{ env.RUST_TOOLCHAIN }} | |
| - name: Set default Rust toolchain | |
| run: | | |
| rustup update ${{ env.RUST_TOOLCHAIN }} | |
| rustup default ${{ env.RUST_TOOLCHAIN }} | |
| - name: Publish to crates.io | |
| run: cargo publish --token ${{ secrets.CARGO_REGISTRY_TOKEN }} | |
| update-docs: | |
| uses: ./.github/workflows/update_docs.yml | |
| needs: [prepare-release-notes, release-python, release-rust] | |
| with: | |
| version: ${{ github.ref_name }} | |
| # Create GitHub Release as draft with all artifacts | |
| create-github-release: | |
| runs-on: ubuntu-latest | |
| needs: [release-python, release-rust, update-docs, validate-version] | |
| permissions: | |
| contents: write | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v7.0.0 | |
| - name: Download all artifacts | |
| uses: actions/download-artifact@v8 | |
| - name: Prepare artifacts for release | |
| run: | | |
| mkdir -p release-artifacts | |
| cp -r *-source/* release-artifacts/ 2>/dev/null || true | |
| cp -r *-wheel/* release-artifacts/ 2>/dev/null || true | |
| ls -lah release-artifacts/ | |
| - name: Download release notes | |
| uses: actions/download-artifact@v8 | |
| with: | |
| name: release-notes | |
| - name: Publish GitHub Release | |
| uses: softprops/action-gh-release@v3 | |
| with: | |
| tag_name: ${{ github.ref_name }} | |
| name: Release ${{ github.ref_name }} | |
| body_path: release_notes.md | |
| draft: false | |
| prerelease: false | |
| files: release-artifacts/* | |
| # Update the "latest" release tag and artifacts | |
| update-latest-release: | |
| runs-on: ubuntu-latest | |
| needs: [create-github-release, validate-version] | |
| permissions: | |
| contents: write | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v7.0.0 | |
| with: | |
| # WORKFLOW_TOKEN has `workflow` scope; required because moving the | |
| # `latest` tag forward can sweep in commits that modified workflow | |
| # files, which the default GITHUB_TOKEN is not allowed to do. | |
| token: ${{ secrets.WORKFLOW_TOKEN }} | |
| - name: Download all artifacts | |
| uses: actions/download-artifact@v8 | |
| - name: Prepare and rename artifacts for latest release | |
| run: | | |
| mkdir -p release-artifacts | |
| # Copy and rename wheels to include "latest" instead of version | |
| for wheel in *-wheel/*.whl; do | |
| if [ -f "$wheel" ]; then | |
| filename=$(basename "$wheel") | |
| # Replace version pattern (e.g., 0.1.0, 1.2.3) with "latest" | |
| new_filename=$(echo "$filename" | sed -E 's/-[0-9]+\.[0-9]+\.[0-9]+(-|\.whl)/-latest\1/') | |
| cp -v "$wheel" "release-artifacts/$new_filename" | |
| fi | |
| done | |
| # Copy and rename source distribution to include "latest" instead of version | |
| for sdist in *-source/*.tar.gz; do | |
| if [ -f "$sdist" ]; then | |
| filename=$(basename "$sdist") | |
| # Replace version pattern with "latest" | |
| new_filename=$(echo "$filename" | sed -E 's/-[0-9]+\.[0-9]+\.[0-9]+\.tar\.gz/-latest.tar.gz/') | |
| cp -v "$sdist" "release-artifacts/$new_filename" | |
| fi | |
| done | |
| echo "Renamed artifacts for latest release:" | |
| ls -lah release-artifacts/ | |
| - name: Update latest tag | |
| run: | | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| git tag -fa latest -m "Latest stable release: ${{ github.ref_name }}" | |
| git push origin latest --force | |
| - name: Update latest release | |
| uses: softprops/action-gh-release@v3 | |
| with: | |
| tag_name: latest | |
| name: Latest Release (${{ github.ref_name }}) | |
| body: | | |
| This is the latest stable release of Brahe. | |
| Current version: **${{ github.ref_name }}** (v${{ needs.validate-version.outputs.version }}) | |
| For detailed release notes, see the [versioned release](${{ github.server_url }}/${{ github.repository }}/releases/tag/${{ github.ref_name }}). | |
| draft: false | |
| prerelease: true | |
| files: release-artifacts/* |