Skip to content

release

release #5

Workflow file for this run

# DO NOT RENAME — the basename `release.yml` is bound by PyPI/TestPyPI trusted publisher
# configuration. Renaming this file breaks publishing (which is the intended defence: a
# rename by an attacker also disables them).
name: release
on:
workflow_dispatch:
inputs:
dry-run:
type: boolean
default: false
description: >-
Publish only to TestPyPI.
This is used to test the workflow itself, do NOT install dimos from TestPyPI.
TestPyPI is highly vulnerable to supply chain attacks.
concurrency:
group: release-${{ github.ref }}
cancel-in-progress: false # Never cancel a release in flight
permissions: {}
jobs:
validate:
runs-on: ubuntu-latest
permissions:
contents: read # For checkout
outputs:
version: ${{ steps.read-version.outputs.version }}
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Install uv
uses: astral-sh/setup-uv@v6
with:
enable-cache: true
- name: Verify dispatched from a release/* branch
run: |
branch="${GITHUB_REF#refs/heads/}"
if [[ "$branch" != release/* ]]; then
echo "::error::release.yml must be dispatched from a release/* branch (got: $branch)"
exit 1
fi
- name: Read project version
id: read-version
run: |
version=$(uv version --short --frozen)
if [[ -z "$version" ]]; then
echo "::error::could not read project version via uv"
exit 1
fi
echo "version=$version" >> "$GITHUB_OUTPUT"
echo "Releasing version: $version"
- name: Verify version not already on PyPI
run: |
version="${{ steps.read-version.outputs.version }}"
http_code=$(curl -sS -o /dev/null -w '%{http_code}' \
"https://pypi.org/pypi/dimos/${version}/json" 2>/dev/null || true)
case "$http_code" in
200) echo "::error::dimos ${version} already exists on PyPI"; exit 1 ;;
404) echo "ok: ${version} not on PyPI" ;;
*) echo "::error::PyPI version check failed (HTTP code: '${http_code}')"; exit 2 ;;
esac
- name: Verify tag does not exist
run: |
if git ls-remote --tags origin "refs/tags/v${{ steps.read-version.outputs.version }}" | grep -q .; then
echo "::error::Tag v${{ steps.read-version.outputs.version }} already exists"
exit 1
fi
build-sdist:
needs: validate
runs-on: ubuntu-latest
permissions:
contents: read # For checkout
id-token: write # For attest-build-provenance's OIDC sigstore signing
attestations: write # For uploading the SLSA build-provenance attestation
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Install uv
uses: astral-sh/setup-uv@v6
with:
enable-cache: true
- name: Build sdist
run: uv build --sdist
- name: Attestation
uses: actions/attest-build-provenance@v2
with:
subject-path: 'dist/*.tar.gz'
- name: Upload sdist
uses: actions/upload-artifact@v4
with:
name: sdist
path: dist/*.tar.gz
build-wheels:
needs: validate
strategy:
fail-fast: false
matrix:
include:
- { os: ubuntu-latest, arch: x86_64 }
- { os: ubuntu-24.04-arm, arch: aarch64 }
- { os: macos-14, arch: arm64 }
runs-on: ${{ matrix.os }}
permissions:
contents: read # For checkout
id-token: write # For attest-build-provenance's OIDC sigstore signing
attestations: write # For uploading the SLSA build-provenance attestation
env:
CIBW_ENABLE: "cpython-freethreading"
CIBW_ARCHS: ${{ matrix.arch }}
# Skip musllinux (Alpine) — Pinocchio's build ecosystem (pin/coal/cmeel-*)
# is glibc-only; cmeel-assimp >= 6.0.5 has no musllinux wheels.
CIBW_SKIP: "*-musllinux_*"
CIBW_MANYLINUX_X86_64_IMAGE: manylinux_2_34 # open3d>=0.19 requires glibc 2.31+
CIBW_MANYLINUX_AARCH64_IMAGE: manylinux_2_34 # open3d>=0.19 requires glibc 2.31+
CIBW_TEST_COMMAND: "python -c 'from dimos.navigation.replanning_a_star.min_cost_astar_ext import min_cost_astar_cpp'"
MACOSX_DEPLOYMENT_TARGET: "11.0"
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Build wheels
uses: pypa/cibuildwheel@v3.4.1
- name: Attestation
uses: actions/attest-build-provenance@v2
with:
subject-path: 'wheelhouse/*.whl'
- name: Upload wheels
uses: actions/upload-artifact@v4
with:
name: wheels-${{ matrix.os }}-${{ matrix.arch }}
path: wheelhouse/*.whl
publish-testpypi:
needs: [validate, build-sdist, build-wheels]
runs-on: ubuntu-latest
environment:
name: testpypi
url: https://test.pypi.org/project/dimos/${{ needs.validate.outputs.version }}/
permissions:
id-token: write # For pypi-publish's OIDC trusted-publisher exchange (TestPyPI)
attestations: write # For PEP 740 attestations attached to each uploaded file
steps:
- name: Download wheels/sdist
uses: actions/download-artifact@v4
with:
path: dist
merge-multiple: true
- name: Install uv
uses: astral-sh/setup-uv@v6
- name: Install twine
run: uv tool install twine
- name: Validate distributions
run: twine check dist/*
- name: Publish to TestPyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
repository-url: https://test.pypi.org/legacy/
attestations: true
skip-existing: true
publish-pypi:
if: ${{ ! inputs.dry-run }}
needs: [validate, publish-testpypi]
runs-on: ubuntu-latest
environment:
name: pypi
url: https://pypi.org/project/dimos/${{ needs.validate.outputs.version }}/
permissions:
id-token: write # For pypi-publish's OIDC trusted-publisher exchange (PyPI)
attestations: write # For PEP 740 attestations attached to each uploaded file
steps:
- name: Download wheels/sdist
uses: actions/download-artifact@v4
with:
path: dist
merge-multiple: true
- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
attestations: true
tag-and-release:
if: ${{ ! inputs.dry-run }}
needs: [validate, publish-pypi]
runs-on: ubuntu-latest
environment: release-tag
permissions:
contents: read # Writes come from the release-bot App token
attestations: write # Release-asset attestations
steps:
- name: Mint release-bot token
uses: actions/create-github-app-token@v1
id: app-token
with:
app-id: ${{ vars.RELEASE_BOT_APP_ID }}
private-key: ${{ secrets.RELEASE_BOT_APP_PRIVATE_KEY }}
- name: Checkout (pinned to dispatch SHA)
uses: actions/checkout@v6
with:
token: ${{ steps.app-token.outputs.token }}
# Pin to dispatch SHA so commits pushed to the release branch
# after dispatch can't shift the tag onto unpublished commits.
ref: ${{ github.sha }}
fetch-depth: 0
- name: Import GPG signing key into runner-scoped GNUPGHOME
env:
GPG_KEY: ${{ secrets.RELEASE_BOT_GPG_PRIVATE_KEY }}
run: |
export GNUPGHOME="${RUNNER_TEMP}/gnupg"
mkdir -p "$GNUPGHOME" && chmod 700 "$GNUPGHOME"
echo "GNUPGHOME=$GNUPGHOME" >> "$GITHUB_ENV"
echo "$GPG_KEY" | gpg --batch --import
echo "trust-model always" >> "$GNUPGHOME/gpg.conf"
- name: Configure git identity and signing
env:
APP_ID: ${{ vars.RELEASE_BOT_APP_ID }}
KEY_ID: ${{ vars.RELEASE_BOT_GPG_KEY_ID }}
run: |
git config user.name "dimos-release-bot[bot]"
git config user.email "${APP_ID}+dimos-release-bot[bot]@users.noreply.github.com"
git config user.signingkey "$KEY_ID"
git config tag.gpgsign true
git config commit.gpgsign true
- name: Create signed annotated tag and push
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
VERSION: ${{ needs.validate.outputs.version }}
run: |
git tag -s -a "v$VERSION" -m "Release v$VERSION"
git push origin "v$VERSION"
- name: Download wheels/sdist
uses: actions/download-artifact@v4
with:
path: dist
merge-multiple: true
- name: Create GitHub Release
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
VERSION: ${{ needs.validate.outputs.version }}
run: |
gh release create "v$VERSION" --verify-tag --notes-from-tag dist/*
merge-back:
if: ${{ ! inputs.dry-run }}
needs: [validate, tag-and-release]
runs-on: ubuntu-latest
environment: release-tag
permissions:
contents: read # Writes come from the release-bot App token
steps:
- name: Mint release-bot token
uses: actions/create-github-app-token@v1
id: app-token
with:
app-id: ${{ vars.RELEASE_BOT_APP_ID }}
private-key: ${{ secrets.RELEASE_BOT_APP_PRIVATE_KEY }}
- name: Checkout
uses: actions/checkout@v6
with:
token: ${{ steps.app-token.outputs.token }}
ref: main
fetch-depth: 0
- name: Import GPG signing key into runner-scoped GNUPGHOME
env:
GPG_KEY: ${{ secrets.RELEASE_BOT_GPG_PRIVATE_KEY }}
run: |
export GNUPGHOME="${RUNNER_TEMP}/gnupg"
mkdir -p "$GNUPGHOME" && chmod 700 "$GNUPGHOME"
echo "GNUPGHOME=$GNUPGHOME" >> "$GITHUB_ENV"
echo "$GPG_KEY" | gpg --batch --import
echo "trust-model always" >> "$GNUPGHOME/gpg.conf"
- name: Configure git identity and signing
env:
APP_ID: ${{ vars.RELEASE_BOT_APP_ID }}
KEY_ID: ${{ vars.RELEASE_BOT_GPG_KEY_ID }}
run: |
git config user.name "dimos-release-bot[bot]"
git config user.email "${APP_ID}+dimos-release-bot[bot]@users.noreply.github.com"
git config user.signingkey "$KEY_ID"
git config commit.gpgsign true
- name: Push signed merge-back commit to main
env:
# Pin to dispatch SHA so commits pushed to the release branch
# after dispatch can't sneak into main via this merge.
RELEASE_SHA: ${{ github.sha }}
RELEASE_BRANCH: ${{ github.ref_name }}
run: |
git fetch origin "$RELEASE_SHA"
# -s ours: take only main's content, preserve two-parent topology so
# `git log --first-parent main` can reach the release tag via the merge commit.
git merge --no-ff -s ours --gpg-sign "$RELEASE_SHA" \
-m "Merge $RELEASE_BRANCH back to main"
git push origin main