satkit 0.18.1

Satellite Toolkit
Documentation
name: Release

on:
  push:
    tags:
      - v*
    branches:
      - wheel*
  workflow_dispatch:

jobs:
  check_version:
    name: verify version
    runs-on: ubuntu-latest
    if: startsWith(github.ref, 'refs/tags/v')
    steps:
      - uses: actions/checkout@v5

      - name: Verify tag matches Cargo.toml, pyproject.toml, and python/Cargo.toml
        run: |
          TAG_VERSION="${GITHUB_REF_NAME#v}"

          # Root workspace crate
          CARGO_VERSION=$(cargo metadata --format-version 1 --no-deps | jq -r '.packages[] | select(.name == "satkit") | .version')

          # Python bindings crate (publish = false, but version must still be synced)
          PY_CARGO_VERSION=$(cargo metadata --format-version 1 --no-deps | jq -r '.packages[] | select(.name == "satkit-python") | .version')

          # pyproject.toml (the version shipped to PyPI)
          PYPROJECT_VERSION=$(grep -E '^version = "' pyproject.toml | head -1 | sed -E 's/version = "([^"]*)"/\1/')

          FAIL=0
          if [ "$TAG_VERSION" != "$CARGO_VERSION" ]; then
            echo "::error::Tag version ($TAG_VERSION) does not match Cargo.toml version ($CARGO_VERSION)"
            FAIL=1
          fi
          if [ "$TAG_VERSION" != "$PY_CARGO_VERSION" ]; then
            echo "::error::Tag version ($TAG_VERSION) does not match python/Cargo.toml version ($PY_CARGO_VERSION)"
            FAIL=1
          fi
          if [ "$TAG_VERSION" != "$PYPROJECT_VERSION" ]; then
            echo "::error::Tag version ($TAG_VERSION) does not match pyproject.toml version ($PYPROJECT_VERSION)"
            FAIL=1
          fi
          if [ "$FAIL" != "0" ]; then
            echo "::error::Version strings are out of sync. Use 'cargo release <version>' (which runs the pre-release-replacements in release.toml) instead of editing files by hand."
            exit 1
          fi
          echo "All version strings match tag ($TAG_VERSION)"

  test:
    name: test before release
    runs-on: ubuntu-latest

    env:
      SATKIT_DATA: astro-data
      SATKIT_TESTVEC_ROOT: satkit-testvecs

    steps:
      - uses: actions/checkout@v5
      - uses: dtolnay/rust-toolchain@stable

      - name: Set up Python
        uses: actions/setup-python@v6
        with:
          python-version: "3.13"

      - name: Cache Satkit Data
        id: cache-satkit-data
        uses: actions/cache@v4
        with:
          path: astro-data
          enableCrossOsArchive: true
          key: satkit-data-cache

      - name: Download Satkit Data
        if: steps.cache-satkit-data.outputs.cache-hit != 'true'
        run: |
          pip install requests
          python python/test/download_data.py astro-data

      - name: Cache Satkit Test Vectors
        id: cache-satkit-testvecs
        uses: actions/cache@v4
        with:
          path: satkit-testvecs
          enableCrossOsArchive: true
          key: satkit-testvecs-cache

      - name: Download Satkit Test Vectors
        if: steps.cache-satkit-testvecs.outputs.cache-hit != 'true'
        run: |
          pip install requests
          python python/test/download_testvecs.py satkit-testvecs

      - name: Rust tests
        run: cargo test --features chrono

      - name: Install satkit
        run: pip install -e ".[test]"

      - name: Python tests
        run: pytest python/test/

  publish_crate:
    name: publish to crates.io
    needs: [check_version, test]
    runs-on: ubuntu-latest
    environment: crates-io
    steps:
      - uses: actions/checkout@v5
      - uses: dtolnay/rust-toolchain@stable

      - name: Publish to crates.io
        env:
          CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
        run: cargo publish

  build_wheels:
    name: build wheels (${{ matrix.os }} ${{ matrix.python }})
    needs: test
    strategy:
      fail-fast: false
      matrix:
        # Each CPython version is its own job (no abi3 → 5 distinct wheels),
        # so the five versions build in parallel per architecture instead of
        # one serial cibuildwheel loop per runner.
        os:
          - macos-latest
          - ubuntu-latest
          - windows-latest
          - ubuntu-24.04-arm
        python:
          - cp310
          - cp311
          - cp312
          - cp313
          - cp314
        include:
          - os: ubuntu-latest
            platform: linux
          - os: ubuntu-24.04-arm
            platform: linux

    runs-on: ${{ matrix.os }}

    env:
      # Compiler-level cache (sccache) backed by the GitHub Actions cache.
      # Keyed on compiler inputs, so dependency crates (nalgebra, pyo3, …)
      # are reused across runs AND across the parallel per-version jobs —
      # this is what pays back the redundant dep compilation the matrix
      # split would otherwise introduce.
      RUSTC_WRAPPER: sccache
      SCCACHE_GHA_ENABLED: "true"
      # Incremental artifacts are not cacheable; disabling lets sccache hit.
      CARGO_INCREMENTAL: "0"
      # Build only this job's single Python version (overrides the full
      # build list in pyproject.toml's [tool.cibuildwheel]).
      CIBW_BUILD: ${{ matrix.python }}-*
      # Linux wheels build inside the manylinux container, which shares
      # neither the host filesystem nor host env. Forward the sccache
      # config + GHA cache credentials into the container…
      CIBW_ENVIRONMENT_PASS_LINUX: >-
        RUSTC_WRAPPER
        SCCACHE_GHA_ENABLED
        CARGO_INCREMENTAL
        ACTIONS_CACHE_URL
        ACTIONS_RESULTS_URL
        ACTIONS_RUNTIME_TOKEN
      # …and install the sccache binary inside that container (the host
      # install from sccache-action isn't visible in there). Pin to a
      # version that speaks the GHA cache service v2 (ACTIONS_RESULTS_URL);
      # older sccache only knows the retired v1 (ACTIONS_CACHE_URL) and
      # aborts with "environment variable not found" on current runners.
      CIBW_BEFORE_ALL_LINUX: >-
        curl -fsSL
        https://github.com/mozilla/sccache/releases/download/v0.15.0/sccache-v0.15.0-$(uname -m)-unknown-linux-musl.tar.gz
        | tar -xz -C /tmp &&
        install -m 755 /tmp/sccache-v0.15.0-$(uname -m)-unknown-linux-musl/sccache /usr/local/bin/sccache

    steps:
      - uses: actions/checkout@v5

      - name: Set up Python
        uses: actions/setup-python@v6

      - name: Set up Rust
        if: matrix.platform != 'linux'
        uses: dtolnay/rust-toolchain@stable

      # Installs sccache on the host and, importantly, exports ACTIONS_CACHE_URL /
      # ACTIONS_RESULTS_URL / ACTIONS_RUNTIME_TOKEN into the step env so the
      # CIBW_ENVIRONMENT_PASS_LINUX forwarding above has values to pass through.
      - name: Set up sccache
        uses: mozilla-actions/sccache-action@v0.0.9

      - name: Build wheels
        uses: pypa/cibuildwheel@v3.2.0
        with:
          package-dir: .
          output-dir: wheelhouse
          config-file: "{package}/pyproject.toml"

      - name: sccache stats
        if: always() && matrix.platform != 'linux'
        run: sccache --show-stats

      - name: Create sdist
        if: matrix.os == 'ubuntu-latest' && matrix.python == 'cp313'
        run: |
          pip install build
          python -m build --sdist . -o wheelhouse

      - name: Upload Artifacts
        uses: actions/upload-artifact@v4
        with:
          name: cibw-wheels-${{ matrix.os }}-${{ matrix.python }}
          path: ./wheelhouse/*

  publish_pypi:
    name: publish to PyPI
    needs: build_wheels
    runs-on: ubuntu-latest
    environment: PyPiPublish
    if: startsWith(github.ref, 'refs/tags/v')

    permissions:
      id-token: write

    steps:
      - name: Download All Artifacts
        uses: actions/download-artifact@v4
        with:
          path: dist
          pattern: cibw-wheels-*
          merge-multiple: true

      - name: Publish to PyPI
        uses: pypa/gh-action-pypi-publish@release/v1
        with:
          packages-dir: dist/