Skip to content

release: Update CHANGELOG & bump version #47

release: Update CHANGELOG & bump version

release: Update CHANGELOG & bump version #47

Workflow file for this run

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/*