Skip to content
171 changes: 157 additions & 14 deletions .github/workflows/build_wheels.yaml
Original file line number Diff line number Diff line change
@@ -1,26 +1,169 @@
name: Build and Publish
# Triggered by repository_dispatch from green-mbpt when a new tag is created.
# Updates version.py, updates HDF5 file version attributes, commits changes,
# creates a matching tag, and deploys the package to PyPI.

# NOTE: A direct / manual push for release is not allowed. Release can only be triggered
# via repository_dispatch from green-mbpt. This is to ensure that the version update and
# deployment steps are always executed when a new tag is created.

name: Update Version and Deploy

on:
push:
branches:
- main
tags:
- 'v*'
pull_request:
repository_dispatch:
types: [version-update]
workflow_dispatch:
inputs:
tag:
description: 'Tag to create (e.g. v0.3.3-test). Always a dry run — no commits, tags, or PyPI uploads.'
required: true

# Canceling in-progress runs is disabled to ensure that the deployment step is not
# accidentally canceled if multiple tags are created in quick succession.
# The concurrency group is still defined to prevent multiple runs for the same tag,
# but it won't cancel an already running deployment if a new tag is pushed while
# it's still running.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
group: ${{ github.workflow }}-${{ github.event.client_payload.tag || github.event.inputs.tag }}
cancel-in-progress: false # safer for PyPI deployments

jobs:
build_sdist:
update-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Generate token
id: generate-token
uses: actions/create-github-app-token@v1
with:
app-id: ${{ secrets.GREEN_TAG_APP_ID }}
private-key: ${{ secrets.GREEN_TAG_APP_PRIVATE_KEY }}
owner: Green-Phys

- name: Checkout green-grids
uses: actions/checkout@v4
with:
token: ${{ steps.generate-token.outputs.token }}

- name: Validate tag name
env:
TAG_NAME: ${{ github.event.client_payload.tag || github.event.inputs.tag }}
run: |
if [ -z "$TAG_NAME" ]; then
echo "Error: TAG_NAME is empty"
exit 1
fi
if ! echo "$TAG_NAME" | grep -qE '^v[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9._-]+)?$'; then
echo "Error: TAG_NAME '$TAG_NAME' does not match expected format vX.Y.Z or vX.Y.Z-suffix"
exit 1
fi
echo "TAG_NAME '$TAG_NAME' is valid"

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.x'

- name: Install h5py
run: python3 -m pip install h5py

- name: Update version.py
env:
TAG_NAME: ${{ github.event.client_payload.tag || github.event.inputs.tag }}
run: |
VERSION=${TAG_NAME#v}
sed -i "s/__version__ = '.*'/__version__ = '$VERSION'/" \
python/green_grids/version.py

- name: Update data.h5 version attributes
env:
TAG_NAME: ${{ github.event.client_payload.tag || github.event.inputs.tag }}
run: |
VERSION=${TAG_NAME#v}
GRIDS_VERSION="$VERSION" python3 - <<EOF
import h5py
import glob
import os

version = os.environ["GRIDS_VERSION"]
files = glob.glob("data/ir/*.h5") + \
glob.glob("data/cheb/*.h5")

for f in files:
print(f"Updating {f}")
with h5py.File(f, 'a') as h5f:
if '__grids_version__' in h5f.attrs:
del h5f.attrs['__grids_version__']
h5f.attrs['__grids_version__'] = version
EOF

- name: Commit and push
env:
TAG_NAME: ${{ github.event.client_payload.tag || github.event.inputs.tag }}
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
DRY_RUN: ${{ github.event_name == 'workflow_dispatch' }}
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add python/green_grids/version.py data/ir/*.h5 data/cheb/*.h5
if git diff --staged --quiet; then
echo "No changes to commit, version already up to date"
else
git commit -m "Release $TAG_NAME"
if [ "$DRY_RUN" = "true" ]; then
echo "Dry run: skipping push to ${DEFAULT_BRANCH}"
git diff HEAD~1
else
git push origin "HEAD:${DEFAULT_BRANCH}"
fi
fi

- name: Create and push tag
env:
TAG_NAME: ${{ github.event.client_payload.tag || github.event.inputs.tag }}
DRY_RUN: ${{ github.event_name == 'workflow_dispatch' }}
run: |
if git ls-remote --tags origin | grep -F -q "refs/tags/$TAG_NAME"; then
echo "Tag $TAG_NAME already exists on remote, skipping"
elif [ "$DRY_RUN" = "true" ]; then
echo "Dry run: skipping tag creation for $TAG_NAME"
else
git tag "$TAG_NAME"
git push origin "$TAG_NAME"
fi

build_distributions:
needs: [update-and-deploy]
if: github.event_name == 'repository_dispatch'
name: Build source distribution
runs-on: ubuntu-latest
steps:
- name: Generate token
id: generate-token
uses: actions/create-github-app-token@v1
with:
app-id: ${{ secrets.GREEN_TAG_APP_ID }}
private-key: ${{ secrets.GREEN_TAG_APP_PRIVATE_KEY }}
owner: Green-Phys

- name: Wait for tag to propagate
env:
TAG_NAME: ${{ github.event.client_payload.tag }}
run: |
for i in {1..5}; do
if git ls-remote --tags https://github.com/${{ github.repository }} | grep -F -q "refs/tags/$TAG_NAME"; then
echo "Tag $TAG_NAME found, proceeding"
exit 0
fi
echo "Attempt $i: tag not yet visible, waiting 10 seconds..."
sleep 10
done
echo "Tag $TAG_NAME not found after 5 attempts, failing"
exit 1

- uses: actions/checkout@v4
with:
ref: ${{ github.event.client_payload.tag }}
fetch-depth: 0
token: ${{ steps.generate-token.outputs.token }}

- uses: actions/setup-python@v5
with:
Expand All @@ -31,22 +174,22 @@ jobs:

- uses: actions/upload-artifact@v4
with:
name: sdist
name: distributions
path: dist/*
if-no-files-found: error

upload_pypi:
needs: [build_sdist]
needs: [build_distributions]
runs-on: ubuntu-latest
environment: release
permissions:
id-token: write
contents: read
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') || github.event_name == 'release'
if: github.event_name == 'repository_dispatch'
steps:
- uses: actions/download-artifact@v4
with:
pattern: sdist
pattern: distributions
path: dist
merge-multiple: true

Expand Down