name: Publish
on:
push:
branches:
- main
- master
workflow_dispatch:
inputs:
publish_to:
description: "Where to publish"
required: true
default: "both"
type: choice
options: [both, pypi, crates]
test_mode:
description: "Use test repositories"
required: false
default: false
type: boolean
jobs:
check-version:
runs-on: ubuntu-latest
if: github.event.workflow_run.conclusion == 'success' || github.event_name == 'workflow_dispatch' || github.event_name == 'push'
outputs:
version: ${{ steps.version.outputs.version }}
should_publish: ${{ steps.check.outputs.should_publish }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 2
- name: Get version and check if changed
id: version
run: |
version=$(grep '^version[[:space:]]*=' Cargo.toml | head -1 | sed 's/.*"\([^"]*\)".*/\1/')
echo "version=$version" >> $GITHUB_OUTPUT
echo "Current version: $version"
- name: Check if should publish
id: check
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
echo "should_publish=true" >> $GITHUB_OUTPUT
elif git diff HEAD~1 HEAD --name-only | grep -q "Cargo.toml"; then
prev_version=$(git show HEAD~1:Cargo.toml | grep '^version[[:space:]]*=' | head -1 | sed 's/.*"\([^"]*\)".*/\1/')
if [ "${{ steps.version.outputs.version }}" != "$prev_version" ]; then
echo "should_publish=true" >> $GITHUB_OUTPUT
else
echo "should_publish=false" >> $GITHUB_OUTPUT
fi
else
echo "should_publish=false" >> $GITHUB_OUTPUT
fi
build-wheels:
runs-on: ${{ matrix.os }}
needs: [check-version]
timeout-minutes: 30
if: |
needs.check-version.outputs.should_publish == 'true' &&
(github.event_name == 'push' || github.event.inputs.publish_to == 'both' || github.event.inputs.publish_to == 'pypi' || github.event.inputs.publish_to == '')
strategy:
fail-fast: false
matrix:
include:
- os: macos-latest platform: macos
arch: arm64
python-version: "3.10"
- os: macos-latest
platform: macos
arch: arm64
python-version: "3.11"
- os: macos-latest
platform: macos
arch: arm64
python-version: "3.12"
- os: windows-latest
platform: windows
arch: x64
python-version: "3.10"
- os: windows-latest
platform: windows
arch: x64
python-version: "3.11"
- os: windows-latest
platform: windows
arch: x64
python-version: "3.12"
steps:
- uses: actions/checkout@v4
- name: Setup dependencies (Unix)
if: matrix.platform != 'windows'
uses: ./.github/actions/setup-dependencies
with:
python-version: ${{ matrix.python-version }}
os: ${{ matrix.os }}
- name: Setup Python (Windows)
if: matrix.platform == 'windows'
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install UV (Windows)
if: matrix.platform == 'windows'
run: pip install uv
- name: Create venv (Windows)
if: matrix.platform == 'windows'
run: uv venv --python ${{ matrix.python-version }}
- name: Set up Rust
uses: dtolnay/rust-toolchain@nightly
with:
targets: ${{ matrix.platform == 'linux' && 'x86_64-unknown-linux-gnu' || matrix.platform == 'macos' && matrix.arch == 'arm64' && 'aarch64-apple-darwin' || matrix.platform == 'windows' && 'x86_64-pc-windows-msvc' || 'x86_64-apple-darwin' }}
- name: Cache Rust dependencies
uses: Swatinem/rust-cache@v2
with:
key: build-wheels-${{ matrix.os }}-${{ matrix.python-version }}-${{ matrix.arch }}
- name: Install maturin (Unix)
if: matrix.platform != 'windows'
run: |
source .venv/bin/activate
uv pip install maturin>=1.7.4
- name: Install maturin (Windows)
if: matrix.platform == 'windows'
run: |
.venv\Scripts\activate
uv pip install maturin>=1.7.4
- name: Build wheels (Unix)
if: matrix.platform != 'windows'
run: |
source .venv/bin/activate
# Set target architecture for cross-compilation
if [ "${{ matrix.platform }}" = "macos" ] && [ "${{ matrix.arch }}" = "arm64" ]; then
export CARGO_BUILD_TARGET="aarch64-apple-darwin"
elif [ "${{ matrix.platform }}" = "macos" ] && [ "${{ matrix.arch }}" = "x86_64" ]; then
export CARGO_BUILD_TARGET="x86_64-apple-darwin"
fi
# Build wheel with all features on Unix (HDF5 included via platform dependencies)
maturin build --release --features python,polars,arrow --interpreter python${{ matrix.python-version }} --out dist
- name: Build wheels (Windows)
if: matrix.platform == 'windows'
run: |
.venv\Scripts\activate
# Windows builds exclude HDF5 due to build complexity
maturin build --release --features python,polars --interpreter python --out dist
env:
CARGO_BUILD_TARGET: x86_64-pc-windows-msvc
- name: Upload wheels as artifacts
uses: actions/upload-artifact@v4
with:
name: wheels-${{ matrix.platform }}-${{ matrix.arch }}-py${{ matrix.python-version }}
path: dist/*.whl
build-wheels-linux:
runs-on: ubuntu-latest
needs: [check-version]
timeout-minutes: 45
if: |
needs.check-version.outputs.should_publish == 'true' &&
(github.event_name == 'push' || github.event.inputs.publish_to == 'both' || github.event.inputs.publish_to == 'pypi' || github.event.inputs.publish_to == '')
strategy:
matrix:
python-version: ['3.10', '3.11', '3.12']
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Set up Rust
uses: dtolnay/rust-toolchain@nightly
- name: Cache Rust dependencies
uses: Swatinem/rust-cache@v2
with:
key: build-wheels-linux-${{ matrix.python-version }}
- name: Create HDF5 setup script
run: |
cat > setup-hdf5.sh << 'EOF'
#!/bin/bash
set -e
echo "=== Installing system dependencies ==="
yum install -y openssl-devel pkgconfig cmake3 wget tar make gcc-c++ zlib-devel
echo "=== Building HDF5 ==="
cd /tmp
wget -q https://github.com/HDFGroup/hdf5/archive/refs/tags/hdf5_1.14.4.3.tar.gz
tar -xzf hdf5_1.14.4.3.tar.gz
cd hdf5-hdf5_1.14.4.3
./configure \
--prefix=/usr/local/hdf5 \
--enable-build-mode=production \
--with-szlib \
--enable-shared \
--disable-static \
--disable-fortran \
--disable-cxx \
CFLAGS="-O2 -fPIC"
make -j $(nproc) && make install
echo "/usr/local/hdf5/lib" > /etc/ld.so.conf.d/hdf5.conf
ldconfig
# Make pkg-config file available
mkdir -p /usr/local/lib/pkgconfig
if [ -f "/usr/local/hdf5/lib/pkgconfig/hdf5.pc" ]; then
cp /usr/local/hdf5/lib/pkgconfig/hdf5.pc /usr/local/lib/pkgconfig/
fi
echo "=== HDF5 setup complete ==="
EOF
chmod +x setup-hdf5.sh
- name: Build wheels with maturin-action
uses: PyO3/maturin-action@v1
with:
command: build
args: --release --features python,polars,arrow --interpreter python${{ matrix.python-version }}
rust-toolchain: nightly
manylinux: 2014
before-script-linux: |
# Save current directory
ORIGINAL_DIR=$(pwd)
# Install system dependencies
yum install -y openssl-devel pkgconfig cmake3 wget tar make gcc-c++ zlib-devel
# Build HDF5 in temporary directory
cd /tmp
wget -q https://github.com/HDFGroup/hdf5/archive/refs/tags/hdf5_1.14.4.3.tar.gz
tar -xzf hdf5_1.14.4.3.tar.gz
cd hdf5-hdf5_1.14.4.3
./configure --prefix=/usr/local/hdf5 --enable-build-mode=production --with-szlib --enable-shared --disable-static --disable-fortran --disable-cxx CFLAGS="-O2 -fPIC"
make -j $(nproc) && make install
# Configure system for HDF5
echo "/usr/local/hdf5/lib" > /etc/ld.so.conf.d/hdf5.conf
ldconfig
mkdir -p /usr/local/lib/pkgconfig
[ -f "/usr/local/hdf5/lib/pkgconfig/hdf5.pc" ] && cp /usr/local/hdf5/lib/pkgconfig/hdf5.pc /usr/local/lib/pkgconfig/
# Return to project directory
cd "$ORIGINAL_DIR"
# Set environment variables for the build
export PKG_CONFIG_PATH="/usr/local/hdf5/lib/pkgconfig:/usr/local/lib/pkgconfig:$PKG_CONFIG_PATH"
export LD_LIBRARY_PATH="/usr/local/hdf5/lib:$LD_LIBRARY_PATH"
export HDF5_ROOT="/usr/local/hdf5"
export HDF5_DIR="/usr/local/hdf5"
env:
PKG_CONFIG_PATH: "/usr/local/hdf5/lib/pkgconfig:/usr/local/lib/pkgconfig"
LD_LIBRARY_PATH: "/usr/local/hdf5/lib"
HDF5_ROOT: "/usr/local/hdf5"
HDF5_DIR: "/usr/local/hdf5"
- name: Upload Linux wheels as artifacts
uses: actions/upload-artifact@v4
with:
name: wheels-linux-py${{ matrix.python-version }}
path: target/wheels/*.whl
publish-pypi:
runs-on: ubuntu-latest
needs: [check-version, build-wheels, build-wheels-linux]
if: |
needs.check-version.outputs.should_publish == 'true' &&
(github.event_name == 'push' || github.event.inputs.publish_to == 'both' || github.event.inputs.publish_to == 'pypi' || github.event.inputs.publish_to == '')
steps:
- uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Download all wheel artifacts
uses: actions/download-artifact@v4
with:
path: dist/
pattern: wheels-*
merge-multiple: true
- name: List built wheels
run: |
echo "Built wheels:"
ls -la dist/
echo
echo "Wheel details:"
for wheel in dist/*.whl; do
echo "$(basename "$wheel"): $(python -m zipfile -l "$wheel" | grep -E '\.(so|pyd)$' || echo 'No native extensions found')"
done
- name: Install twine
run: pip install twine
- name: Publish to PyPI
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
TEST_TWINE_PASSWORD: ${{ secrets.TEST_PYPI_API_TOKEN }}
run: |
if [ "${{ github.event.inputs.test_mode }}" = "true" ]; then
echo "TEST MODE: Uploading to TestPyPI"
twine upload --repository testpypi --username __token__ --password "$TEST_TWINE_PASSWORD" dist/*.whl --skip-existing --verbose
else
echo "Uploading to PyPI"
twine upload --username __token__ --password "$TWINE_PASSWORD" dist/*.whl --skip-existing --verbose
fi
publish-crates:
runs-on: ubuntu-latest
needs: [check-version]
if: |
needs.check-version.outputs.should_publish == 'true' &&
(github.event_name == 'push' || github.event.inputs.publish_to == 'both' || github.event.inputs.publish_to == 'crates' || github.event.inputs.publish_to == '')
steps:
- uses: actions/checkout@v4
- name: Setup dependencies
uses: ./.github/actions/setup-dependencies
with:
python-version: "3.11"
os: ubuntu-latest
- name: Set up Rust
uses: dtolnay/rust-toolchain@nightly
- name: Cache Rust dependencies
uses: Swatinem/rust-cache@v2
- name: Publish to crates.io
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CRATES_IO_API_TOKEN }}
run: |
if [ "${{ github.event.inputs.test_mode }}" = "true" ]; then
echo "TEST MODE: Dry-run publish"
cargo publish --dry-run
else
echo "Publishing version ${{ needs.check-version.outputs.version }}"
cargo publish --token $CARGO_REGISTRY_TOKEN
fi
create-release:
runs-on: ubuntu-latest
needs: [check-version, publish-crates, publish-pypi]
if: |
always() &&
needs.check-version.outputs.should_publish == 'true' &&
github.event.inputs.test_mode != 'true' &&
(needs.publish-crates.result == 'success' || needs.publish-crates.result == 'skipped') &&
(needs.publish-pypi.result == 'success' || needs.publish-pypi.result == 'skipped')
steps:
- uses: actions/checkout@v4
- name: Create Release
uses: softprops/action-gh-release@v2
with:
tag_name: v${{ needs.check-version.outputs.version }}
name: Release v${{ needs.check-version.outputs.version }}
body: |
## evlib v${{ needs.check-version.outputs.version }}
### Installation
```bash
pip install evlib==${{ needs.check-version.outputs.version }}
cargo add evlib@${{ needs.check-version.outputs.version }}
```
### Links
- [PyPI Package](https://pypi.org/project/evlib/${{ needs.check-version.outputs.version }}/)
- [crates.io Package](https://crates.io/crates/evlib/${{ needs.check-version.outputs.version }})
- [Documentation](https://tallamjr.github.io/evlib/)
deploy-docs:
runs-on: ubuntu-latest
needs: [check-version, publish-crates, publish-pypi]
if: |
always() &&
needs.check-version.outputs.should_publish == 'true' &&
github.event.inputs.test_mode != 'true' &&
(needs.publish-crates.result == 'success' || needs.publish-crates.result == 'skipped') &&
(needs.publish-pypi.result == 'success' || needs.publish-pypi.result == 'skipped')
permissions:
contents: write
pages: write
id-token: write
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup dependencies
uses: ./.github/actions/setup-dependencies
with:
python-version: "3.11"
os: ubuntu-latest
- name: Set up Rust
uses: dtolnay/rust-toolchain@nightly
- name: Install documentation dependencies
run: |
source .venv/bin/activate
uv pip install -e ".[dev]"
- name: Build evlib for documentation
run: |
source .venv/bin/activate
maturin develop --release --features python
- name: Configure Git user
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
- name: Deploy documentation
env:
VERSION: ${{ needs.check-version.outputs.version }}
run: |
source .venv/bin/activate
# Set version in mkdocs config
export EVLIB_VERSION="v$VERSION"
# Deploy using mike for versioning
mkdocs build
# Deploy to GitHub Pages using mike
mike deploy --push --update-aliases "$VERSION" latest
mike set-default --push latest