release #5
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
| # 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 |