petekstatic 0.1.13

petekStatic — the GEOMODEL layer: structural framework, grid construction, property modelling, volumetrics and static uncertainty, consolidated into one crate.
Documentation
name: release

on:
  push:
    tags: ["v*"]
  workflow_dispatch:
    inputs:
      ref:
        description: "Branch, tag, or SHA to release. Defaults to the workflow ref."
        required: false
        type: string
      version:
        description: "Expected package version. Must match Cargo metadata."
        required: false
        type: string

permissions:
  contents: read

concurrency:
  group: release-${{ github.event_name == 'workflow_dispatch' && inputs.ref || github.ref }}
  cancel-in-progress: false

jobs:
  prepare:
    name: resolve release
    runs-on: ubuntu-latest
    outputs:
      version: ${{ steps.release.outputs.version }}
      tag: ${{ steps.release.outputs.tag }}
      started_at: ${{ steps.release.outputs.started_at }}
    steps:
      - uses: actions/checkout@v7
        with:
          ref: ${{ github.event_name == 'workflow_dispatch' && inputs.ref || github.ref }}
          fetch-depth: 0
      - name: Resolve release tag
        id: release
        shell: bash
        run: |
          set -euo pipefail
          VER=$(cargo metadata --no-deps --format-version 1 \
            | jq -r '.packages[] | select(.name=="petekstatic") | .version')
          TAG="v$VER"
          EXPECTED="${{ github.event_name == 'workflow_dispatch' && inputs.version || '' }}"
          if [ -n "$EXPECTED" ] && [ "$EXPECTED" != "$VER" ]; then
            echo "expected version $EXPECTED does not match Cargo version $VER" >&2
            exit 1
          fi
          if [ "${{ github.event_name }}" = "push" ] && [ "$GITHUB_REF_NAME" != "$TAG" ]; then
            echo "tag $GITHUB_REF_NAME does not match Cargo version $VER" >&2
            exit 1
          fi
          if [ "${{ github.event_name }}" = "workflow_dispatch" ] \
            && git ls-remote --exit-code --tags origin "$TAG" >/dev/null 2>&1; then
            git fetch --force origin "+refs/tags/$TAG:refs/tags/$TAG"
            EXISTING_COMMIT=$(git rev-list -n 1 "$TAG")
            CURRENT_COMMIT=$(git rev-parse HEAD)
            if [ "$EXISTING_COMMIT" != "$CURRENT_COMMIT" ]; then
              echo "tag $TAG already exists on $EXISTING_COMMIT, not $CURRENT_COMMIT" >&2
              exit 1
            fi
          fi
          echo "version=$VER" >> "$GITHUB_OUTPUT"
          echo "tag=$TAG" >> "$GITHUB_OUTPUT"
          echo "started_at=$(date +%s)" >> "$GITHUB_OUTPUT"

  gates:
    name: gates (fmt / clippy / test)
    needs: prepare
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
        with:
          ref: ${{ github.event_name == 'workflow_dispatch' && inputs.ref || github.ref }}
      - uses: dtolnay/rust-toolchain@stable
        with:
          components: clippy, rustfmt
      - uses: Swatinem/rust-cache@v2.9.1
      - name: fmt
        run: cargo fmt --all --check
      - name: clippy (warnings = errors)
        run: cargo clippy --workspace --all-targets --all-features -- -D warnings
      - name: test
        run: cargo test --workspace --all-features

  create-tag:
    name: create release tag
    needs: [prepare, gates]
    runs-on: ubuntu-latest
    permissions:
      contents: write
    steps:
      - uses: actions/checkout@v7
        with:
          ref: ${{ github.event_name == 'workflow_dispatch' && inputs.ref || github.ref }}
          fetch-depth: 0
      - name: Create or reuse release tag
        shell: bash
        run: |
          set -euo pipefail
          TAG="${{ needs.prepare.outputs.tag }}"
          if [ "${{ github.event_name }}" != "workflow_dispatch" ]; then
            echo "$TAG supplied by tag push"
            exit 0
          fi
          if git ls-remote --exit-code --tags origin "$TAG" >/dev/null 2>&1; then
            git fetch --force origin "+refs/tags/$TAG:refs/tags/$TAG"
            EXISTING_COMMIT=$(git rev-list -n 1 "$TAG")
            CURRENT_COMMIT=$(git rev-parse HEAD)
            if [ "$EXISTING_COMMIT" != "$CURRENT_COMMIT" ]; then
              echo "tag $TAG already exists on $EXISTING_COMMIT, not $CURRENT_COMMIT" >&2
              exit 1
            fi
            echo "$TAG already exists on this commit"
            exit 0
          fi
          git config user.name "github-actions[bot]"
          git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
          git tag "$TAG"
          git push origin "refs/tags/$TAG"

  build-wheels:
    name: wheels (${{ matrix.os }}-${{ matrix.target }})
    needs: prepare
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        include:
          - { os: ubuntu-latest, target: x86_64 }
          - { os: ubuntu-latest, target: aarch64 }
          - { os: macos-latest, target: aarch64 }
          - { os: macos-latest, target: x86_64 }
          - { os: windows-latest, target: x64 }
    steps:
      - uses: actions/checkout@v7
        with:
          ref: ${{ github.event_name == 'workflow_dispatch' && inputs.ref || github.ref }}
      - uses: actions/setup-python@v6
        with: { python-version: "3.x" }
      - name: Build wheels
        uses: PyO3/maturin-action@v1.51.0
        with:
          target: ${{ matrix.target }}
          args: --release --out dist
          manylinux: auto
          sccache: "true"
      - uses: actions/upload-artifact@v7
        with:
          name: wheels-${{ matrix.os }}-${{ matrix.target }}
          path: dist

  sdist:
    name: build sdist
    needs: prepare
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
        with:
          ref: ${{ github.event_name == 'workflow_dispatch' && inputs.ref || github.ref }}
      - name: Build sdist
        uses: PyO3/maturin-action@v1.51.0
        with:
          command: sdist
          args: --out dist
      - uses: actions/upload-artifact@v7
        with:
          name: wheels-sdist
          path: dist

  publish-crate:
    name: crates.io
    needs: [prepare, create-tag]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
        with:
          ref: ${{ needs.prepare.outputs.tag }}
      - uses: dtolnay/rust-toolchain@stable
      - name: Publish to crates.io (skip if this version is already published)
        run: |
          VER=$(cargo metadata --no-deps --format-version 1 \
            | jq -r '.packages[] | select(.name=="petekstatic") | .version')
          if curl -fsS -H "User-Agent: petekstatic-ci" \
               "https://crates.io/api/v1/crates/petekstatic/$VER" \
               | jq -e '.version.num' >/dev/null 2>&1; then
            echo "petekstatic $VER already on crates.io; skipping publish"
          else
            cargo publish -p petekstatic --token ${{ secrets.CARGO_REGISTRY_TOKEN }}
          fi

  publish-pypi:
    name: publish to PyPI (trusted publishing)
    needs: [create-tag, build-wheels, sdist]
    runs-on: ubuntu-latest
    environment: pypi
    permissions:
      id-token: write
    steps:
      - uses: actions/download-artifact@v8
        with: { pattern: wheels-*, merge-multiple: true, path: dist }
      - id: publish_1
        continue-on-error: true
        uses: pypa/gh-action-pypi-publish@release/v1.14
        with:
          skip-existing: true
      - if: steps.publish_1.outcome == 'failure'
        run: sleep 10
      - id: publish_2
        if: steps.publish_1.outcome == 'failure'
        continue-on-error: true
        uses: pypa/gh-action-pypi-publish@release/v1.14
        with:
          skip-existing: true
      - if: steps.publish_1.outcome == 'failure' && steps.publish_2.outcome == 'failure'
        run: sleep 30
      - id: publish_3
        if: steps.publish_1.outcome == 'failure' && steps.publish_2.outcome == 'failure'
        continue-on-error: true
        uses: pypa/gh-action-pypi-publish@release/v1.14
        with:
          skip-existing: true
      - name: Require a successful publish attempt
        if: always()
        run: |
          test "${{ steps.publish_1.outcome }}" = success \
            || test "${{ steps.publish_2.outcome }}" = success \
            || test "${{ steps.publish_3.outcome }}" = success

  verify-pypi:
    name: verify PyPI availability
    needs: [prepare, publish-pypi]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
        with:
          ref: ${{ needs.prepare.outputs.tag }}
      - uses: actions/setup-python@v6
        with:
          python-version: "3.12"
      - name: Wait until pip can install this release
        run: python scripts/wait_for_pypi.py petekstatic "${{ needs.prepare.outputs.version }}"

      - name: Report trigger-to-install time
        run: |
          elapsed=$(( $(date +%s) - ${{ needs.prepare.outputs.started_at }} ))
          echo "### Registry availability" >> "$GITHUB_STEP_SUMMARY"
          echo "Trigger to installable wheel: $((elapsed / 60))m $((elapsed % 60))s" >> "$GITHUB_STEP_SUMMARY"

  github-release:
    name: GitHub Release
    needs: [prepare, publish-crate, verify-pypi]
    runs-on: ubuntu-latest
    permissions:
      contents: write
    steps:
      - uses: softprops/action-gh-release@v3
        with:
          tag_name: ${{ needs.prepare.outputs.tag }}
          generate_release_notes: true