Skip to content
Latchkey

PR and Merge builds (on main branch) workflow (AndrewAnnex/SpiceyPy)

The PR and Merge builds (on main branch) workflow from AndrewAnnex/SpiceyPy, explained and optimized by Latchkey.

C

CI health: C - fair

Run this on Latchkey for self-healing, caching, and up to 58% lower cost.

Grade your own workflow free or run it on Latchkey →
Source: AndrewAnnex/SpiceyPy.github/workflows/ci-build.ymlLicense MITView source

What it does

This is the PR and Merge builds (on main branch) workflow from the AndrewAnnex/SpiceyPy repository, a real project running GitHub Actions. It is shown here with attribution under its MIT license.

Below, Latchkey shows a faster, safer version produced by its optimization engine.

The workflow

workflow (.yml)
name: PR and Merge builds (on main branch)

on:
  pull_request:
    branches: [ main ]
  push:
    branches: [ main ]
  schedule:
    - cron: "0 4 * * 0"
env:
  # Base URL for the CSPICE release mirror (archived + hashed NAIF toolkits)
  MIRROR_BASE: https://raw.githubusercontent.com/AndrewAnnex/cspice-release-mirror/refs/heads/main/artifacts
  # bump to invalidate the cached CSPICE mirror archive
  CSPICE_CACHE: 0
  CMAKE_BUILD_PARALLEL_LEVEL: 2

jobs:
  test-pyodide:
    name: Test SpiceyPy Pyodide build
    runs-on: 'ubuntu-latest'
    permissions:
      id-token: write
      contents: read
    steps:
      - name: Checkout 🌢️ πŸ₯§
        uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
        with:
          ref: ${{ github.event.pull_request.head.sha }}
      - name: Set up uv 🐍 πŸ“¦ with Python 3.14
        uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
        with:
          python-version: "3.14"
          activate-environment: true
          enable-cache: true
      # Pyodide/emscripten uses the 32-bit Linux package (matches the
      # PC_Linux_GCC_32bit toolkit cspice-cmake-spiceypy downloads). The build
      # runs directly on the host, so CSPICE_SRC is a host path and is inherited
      # from $GITHUB_ENV by the (non-container) cibuildwheel build.
      - name: Cache CSPICE mirror archive
        uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
        with:
          path: cspice-src-cache/N67_lin_x86_cspice.tar.gz
          key: cspice-mirror-N67_lin_x86_cspice.tar.gz-${{ env.CSPICE_CACHE }}
      - name: Provision CSPICE source from mirror
        shell: bash
        run: |
          set -euo pipefail
          mkdir -p cspice-src-cache
          archive="cspice-src-cache/N67_lin_x86_cspice.tar.gz"
          if [ ! -f "$archive" ]; then
            echo "Cache miss; downloading N67_lin_x86_cspice.tar.gz from mirror"
            curl -fsSL "${MIRROR_BASE}/N67_lin_x86_cspice.tar.gz" -o "$archive"
          else
            echo "Cache hit; using cached $archive"
          fi
          curl -fsSL "${MIRROR_BASE}/N67_lin_x86_cspice.tar.gz.sha256" -o "$archive.sha256"
          expected="$(tr -d '[:space:]' < "$archive.sha256")"
          actual="$(cmake -E sha256sum "$archive" | awk '{print $1}')"
          if [ "$expected" != "$actual" ]; then
            echo "::error::SHA256 mismatch for N67_lin_x86_cspice.tar.gz"
            exit 1
          fi
          echo "Checksum OK"
          rm -rf cspice-src-cache/cspice
          ( cd cspice-src-cache && cmake -E tar xf "N67_lin_x86_cspice.tar.gz" )
          test -d cspice-src-cache/cspice/src/cspice
          echo "CSPICE_SRC=$(pwd)/cspice-src-cache/cspice" >> "$GITHUB_ENV"
          echo "Provisioned CSPICE_SRC for Pyodide"
      - name: Build Pyodide wheel for SpiceyPy 🌢️ πŸ₯§
        uses: pypa/cibuildwheel@294735312765b09d24a2fbec22660ce817587d55 # v4.1.0
        env:
          CIBW_PLATFORM: pyodide
          CIBW_ARCHS: "wasm32"
          CMAKE_BUILD_PARALLEL_LEVEL: 2
          SKBUILD_BUILD_VERBOSE: "true"
  test-conda:
    name: Test SpiceyPy Conda install
    runs-on: 'ubuntu-latest'
    permissions:
      id-token: write
      contents: read
    steps:
      - name: Checkout 🌢️ πŸ₯§
        uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
        with:
          ref: ${{ github.event.pull_request.head.sha }}
      - uses: mamba-org/setup-micromamba@d7c9bd84e824b79d2af72a2d4196c7f4300d3476 # v3.0.0
        with:
          environment-name: test-env
          init-shell: >-
            bash
          cache-environment: true
          create-args: >-
            python=3.13
            cspice
            cython
            numpy
            pip
      - name: Install dependencies
        shell: bash -el {0}
        run: |
          python -m pip install --user --group devwheel
      - name: Build SpiceyPy 
        shell: bash -el {0}
        env:
          SYSTEM_VERSION_COMPAT: 0
        run: 
          CMAKE_PREFIX_PATH=$CONDA_PREFIX python -m build
      - name: Install SpiceyPy 🌢️ πŸ₯§
        shell: bash -el {0}
        env:
          SYSTEM_VERSION_COMPAT: 0
        run: 
          python -m pip install dist/*.whl
      - name: Test πŸ§ͺ with coverage πŸ“ˆ
        shell: bash -el {0}
        run: |
          python -m coverage run --source spiceypy -m pytest --pyargs spiceypy --benchmark-disable
          # look for .coverage file
          ls -lahtr
      - name: Upload πŸ†™ coverage πŸ“ˆ report to codecov 
        uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
        with:
          fail_ci_if_error: false
          verbose: true
          use_oidc: true
  test:
    name: Test SpiceyPy 🌢️ πŸ₯§
    runs-on: ${{ matrix.os }}
    permissions:
      id-token: write
      contents: read
    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu-22.04-arm, ubuntu-latest, macos-15-intel, macos-15, windows-latest]
        python-version: ['3.12', '3.13', '3.14']
        # Attach the matching mirror archive per OS (CSPICE C source is
        # architecture-independent, so aarch64 Linux reuses the x86_64 archive).
        include:
          - { os: ubuntu-22.04-arm, archive: N67_lin_x64_cspice.tar.gz }
          - { os: ubuntu-latest,    archive: N67_lin_x64_cspice.tar.gz }
          - { os: macos-15-intel,   archive: N67_mac_x64_cspice.tar.gz }
          - { os: macos-15,         archive: N67_mac_arm_cspice.tar.gz }
          - { os: windows-latest,   archive: N67_win_x64_cspice.zip }
    steps:
      - name: Setup windows msvc
        if: ${{ matrix.os == 'windows-latest'}}
        uses: ilammy/msvc-dev-cmd@0b201ec74fa43914dc39ae48a89fd1d8cb592756 # v1.13.0
      - name: Checkout 🌢️ πŸ₯§
        uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
        with:
          ref: ${{ github.event.pull_request.head.sha }}
      - name: Set up uv 🐍 πŸ“¦ with Python ${{ matrix.python-version }}
        uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
        with:
          python-version: ${{ matrix.python-version }}
          activate-environment: true
          enable-cache: true
      - name: Display Python 🐍 version
        run: python --version
      - name: Install dependencies
        run: |
          uv pip install --group devwheel
      # Provide CSPICE from the mirror so the build reuses a cached, hash-verified
      # archive instead of downloading from NAIF. uv build runs directly on the
      # runner (no container), so CSPICE_SRC is a host path on every OS. The
      # cspice-cmake-spiceypy CMake copies + patches this tree, never mutating it.
      - name: Cache CSPICE mirror archive
        uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
        with:
          path: cspice-src-cache/${{ matrix.archive }}
          key: cspice-mirror-${{ matrix.archive }}-${{ env.CSPICE_CACHE }}
      - name: Provision CSPICE source from mirror
        shell: bash
        run: |
          set -euo pipefail
          mkdir -p cspice-src-cache
          archive="cspice-src-cache/${{ matrix.archive }}"
          if [ ! -f "$archive" ]; then
            echo "Cache miss; downloading ${{ matrix.archive }} from mirror"
            curl -fsSL "${MIRROR_BASE}/${{ matrix.archive }}" -o "$archive"
          else
            echo "Cache hit; using cached $archive"
          fi
          # Always verify against the mirror's sha256 sidecar (also checks cache integrity)
          curl -fsSL "${MIRROR_BASE}/${{ matrix.archive }}.sha256" -o "$archive.sha256"
          expected="$(tr -d '[:space:]' < "$archive.sha256")"
          actual="$(cmake -E sha256sum "$archive" | awk '{print $1}')"
          if [ "$expected" != "$actual" ]; then
            echo "::error::SHA256 mismatch for ${{ matrix.archive }}"
            exit 1
          fi
          echo "Checksum OK"
          rm -rf cspice-src-cache/cspice
          ( cd cspice-src-cache && cmake -E tar xf "${{ matrix.archive }}" )
          test -d cspice-src-cache/cspice/src/cspice
          test -d cspice-src-cache/cspice/include
          if [ "${{ runner.os }}" = "Windows" ]; then
            echo "CSPICE_SRC=$(cygpath -m "$(pwd)/cspice-src-cache/cspice")" >> "$GITHUB_ENV"
          else
            echo "CSPICE_SRC=$(pwd)/cspice-src-cache/cspice" >> "$GITHUB_ENV"
          fi
          echo "Provisioned CSPICE_SRC for ${{ runner.os }}"
      - name: Build SpiceyPy
        shell: bash
        env:
          SYSTEM_VERSION_COMPAT: 0
        run:
          uv build
      - name: Install SpiceyPy 🌢️ πŸ₯§
        shell: bash
        env:
          SYSTEM_VERSION_COMPAT: 0
        run:
          uv pip install dist/*.whl
      - name: Test πŸ§ͺ with coverage πŸ“ˆ
        shell: bash
        run: |
          coverage run --source spiceypy -m pytest --pyargs spiceypy --benchmark-disable
          # look for .coverage file
          ls -lahtr
      - name: Upload πŸ†™ coverage πŸ“ˆ report to codecov 
        uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
        with:
          fail_ci_if_error: false
          verbose: true
          use_oidc: true

The same workflow, on Latchkey

Removes redundant runs and caps runaway jobs. Added and changed lines are highlighted.

name: PR and Merge builds (on main branch)
 
on:
  pull_request:
    branches: [ main ]
  push:
    branches: [ main ]
  schedule:
    - cron: "0 4 * * 0"
env:
  # Base URL for the CSPICE release mirror (archived + hashed NAIF toolkits)
  MIRROR_BASE: https://raw.githubusercontent.com/AndrewAnnex/cspice-release-mirror/refs/heads/main/artifacts
  # bump to invalidate the cached CSPICE mirror archive
  CSPICE_CACHE: 0
  CMAKE_BUILD_PARALLEL_LEVEL: 2
 
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true
 
jobs:
  test-pyodide:
    timeout-minutes: 30
    name: Test SpiceyPy Pyodide build
    runs-on: 'ubuntu-latest'
    permissions:
      id-token: write
      contents: read
    steps:
      - name: Checkout 🌢️ πŸ₯§
        uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
        with:
          ref: ${{ github.event.pull_request.head.sha }}
      - name: Set up uv 🐍 πŸ“¦ with Python 3.14
        uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
        with:
          python-version: "3.14"
          activate-environment: true
          enable-cache: true
      # Pyodide/emscripten uses the 32-bit Linux package (matches the
      # PC_Linux_GCC_32bit toolkit cspice-cmake-spiceypy downloads). The build
      # runs directly on the host, so CSPICE_SRC is a host path and is inherited
      # from $GITHUB_ENV by the (non-container) cibuildwheel build.
      - name: Cache CSPICE mirror archive
        uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
        with:
          path: cspice-src-cache/N67_lin_x86_cspice.tar.gz
          key: cspice-mirror-N67_lin_x86_cspice.tar.gz-${{ env.CSPICE_CACHE }}
      - name: Provision CSPICE source from mirror
        shell: bash
        run: |
          set -euo pipefail
          mkdir -p cspice-src-cache
          archive="cspice-src-cache/N67_lin_x86_cspice.tar.gz"
          if [ ! -f "$archive" ]; then
            echo "Cache miss; downloading N67_lin_x86_cspice.tar.gz from mirror"
            curl -fsSL "${MIRROR_BASE}/N67_lin_x86_cspice.tar.gz" -o "$archive"
          else
            echo "Cache hit; using cached $archive"
          fi
          curl -fsSL "${MIRROR_BASE}/N67_lin_x86_cspice.tar.gz.sha256" -o "$archive.sha256"
          expected="$(tr -d '[:space:]' < "$archive.sha256")"
          actual="$(cmake -E sha256sum "$archive" | awk '{print $1}')"
          if [ "$expected" != "$actual" ]; then
            echo "::error::SHA256 mismatch for N67_lin_x86_cspice.tar.gz"
            exit 1
          fi
          echo "Checksum OK"
          rm -rf cspice-src-cache/cspice
          ( cd cspice-src-cache && cmake -E tar xf "N67_lin_x86_cspice.tar.gz" )
          test -d cspice-src-cache/cspice/src/cspice
          echo "CSPICE_SRC=$(pwd)/cspice-src-cache/cspice" >> "$GITHUB_ENV"
          echo "Provisioned CSPICE_SRC for Pyodide"
      - name: Build Pyodide wheel for SpiceyPy 🌢️ πŸ₯§
        uses: pypa/cibuildwheel@294735312765b09d24a2fbec22660ce817587d55 # v4.1.0
        env:
          CIBW_PLATFORM: pyodide
          CIBW_ARCHS: "wasm32"
          CMAKE_BUILD_PARALLEL_LEVEL: 2
          SKBUILD_BUILD_VERBOSE: "true"
  test-conda:
    timeout-minutes: 30
    name: Test SpiceyPy Conda install
    runs-on: 'ubuntu-latest'
    permissions:
      id-token: write
      contents: read
    steps:
      - name: Checkout 🌢️ πŸ₯§
        uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
        with:
          ref: ${{ github.event.pull_request.head.sha }}
      - uses: mamba-org/setup-micromamba@d7c9bd84e824b79d2af72a2d4196c7f4300d3476 # v3.0.0
        with:
          environment-name: test-env
          init-shell: >-
            bash
          cache-environment: true
          create-args: >-
            python=3.13
            cspice
            cython
            numpy
            pip
      - name: Install dependencies
        shell: bash -el {0}
        run: |
          python -m pip install --user --group devwheel
      - name: Build SpiceyPy 
        shell: bash -el {0}
        env:
          SYSTEM_VERSION_COMPAT: 0
        run: 
          CMAKE_PREFIX_PATH=$CONDA_PREFIX python -m build
      - name: Install SpiceyPy 🌢️ πŸ₯§
        shell: bash -el {0}
        env:
          SYSTEM_VERSION_COMPAT: 0
        run: 
          python -m pip install dist/*.whl
      - name: Test πŸ§ͺ with coverage πŸ“ˆ
        shell: bash -el {0}
        run: |
          python -m coverage run --source spiceypy -m pytest --pyargs spiceypy --benchmark-disable
          # look for .coverage file
          ls -lahtr
      - name: Upload πŸ†™ coverage πŸ“ˆ report to codecov 
        uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
        with:
          fail_ci_if_error: false
          verbose: true
          use_oidc: true
  test:
    timeout-minutes: 30
    name: Test SpiceyPy 🌢️ πŸ₯§
    runs-on: ${{ matrix.os }}
    permissions:
      id-token: write
      contents: read
    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu-22.04-arm, ubuntu-latest, macos-15-intel, macos-15, windows-latest]
        python-version: ['3.12', '3.13', '3.14']
        # Attach the matching mirror archive per OS (CSPICE C source is
        # architecture-independent, so aarch64 Linux reuses the x86_64 archive).
        include:
          - { os: ubuntu-22.04-arm, archive: N67_lin_x64_cspice.tar.gz }
          - { os: ubuntu-latest,    archive: N67_lin_x64_cspice.tar.gz }
          - { os: macos-15-intel,   archive: N67_mac_x64_cspice.tar.gz }
          - { os: macos-15,         archive: N67_mac_arm_cspice.tar.gz }
          - { os: windows-latest,   archive: N67_win_x64_cspice.zip }
    steps:
      - name: Setup windows msvc
        if: ${{ matrix.os == 'windows-latest'}}
        uses: ilammy/msvc-dev-cmd@0b201ec74fa43914dc39ae48a89fd1d8cb592756 # v1.13.0
      - name: Checkout 🌢️ πŸ₯§
        uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
        with:
          ref: ${{ github.event.pull_request.head.sha }}
      - name: Set up uv 🐍 πŸ“¦ with Python ${{ matrix.python-version }}
        uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
        with:
          python-version: ${{ matrix.python-version }}
          activate-environment: true
          enable-cache: true
      - name: Display Python 🐍 version
        run: python --version
      - name: Install dependencies
        run: |
          uv pip install --group devwheel
      # Provide CSPICE from the mirror so the build reuses a cached, hash-verified
      # archive instead of downloading from NAIF. uv build runs directly on the
      # runner (no container), so CSPICE_SRC is a host path on every OS. The
      # cspice-cmake-spiceypy CMake copies + patches this tree, never mutating it.
      - name: Cache CSPICE mirror archive
        uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
        with:
          path: cspice-src-cache/${{ matrix.archive }}
          key: cspice-mirror-${{ matrix.archive }}-${{ env.CSPICE_CACHE }}
      - name: Provision CSPICE source from mirror
        shell: bash
        run: |
          set -euo pipefail
          mkdir -p cspice-src-cache
          archive="cspice-src-cache/${{ matrix.archive }}"
          if [ ! -f "$archive" ]; then
            echo "Cache miss; downloading ${{ matrix.archive }} from mirror"
            curl -fsSL "${MIRROR_BASE}/${{ matrix.archive }}" -o "$archive"
          else
            echo "Cache hit; using cached $archive"
          fi
          # Always verify against the mirror's sha256 sidecar (also checks cache integrity)
          curl -fsSL "${MIRROR_BASE}/${{ matrix.archive }}.sha256" -o "$archive.sha256"
          expected="$(tr -d '[:space:]' < "$archive.sha256")"
          actual="$(cmake -E sha256sum "$archive" | awk '{print $1}')"
          if [ "$expected" != "$actual" ]; then
            echo "::error::SHA256 mismatch for ${{ matrix.archive }}"
            exit 1
          fi
          echo "Checksum OK"
          rm -rf cspice-src-cache/cspice
          ( cd cspice-src-cache && cmake -E tar xf "${{ matrix.archive }}" )
          test -d cspice-src-cache/cspice/src/cspice
          test -d cspice-src-cache/cspice/include
          if [ "${{ runner.os }}" = "Windows" ]; then
            echo "CSPICE_SRC=$(cygpath -m "$(pwd)/cspice-src-cache/cspice")" >> "$GITHUB_ENV"
          else
            echo "CSPICE_SRC=$(pwd)/cspice-src-cache/cspice" >> "$GITHUB_ENV"
          fi
          echo "Provisioned CSPICE_SRC for ${{ runner.os }}"
      - name: Build SpiceyPy
        shell: bash
        env:
          SYSTEM_VERSION_COMPAT: 0
        run:
          uv build
      - name: Install SpiceyPy 🌢️ πŸ₯§
        shell: bash
        env:
          SYSTEM_VERSION_COMPAT: 0
        run:
          uv pip install dist/*.whl
      - name: Test πŸ§ͺ with coverage πŸ“ˆ
        shell: bash
        run: |
          coverage run --source spiceypy -m pytest --pyargs spiceypy --benchmark-disable
          # look for .coverage file
          ls -lahtr
      - name: Upload πŸ†™ coverage πŸ“ˆ report to codecov 
        uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
        with:
          fail_ci_if_error: false
          verbose: true
          use_oidc: true

What changed

What Latchkey heals here

This workflow has steps that commonly fail on transient issues (network, registries, flaky browsers). On Latchkey managed runners they are detected, retried, and self-healed instead of failing your build:

This workflow runs 3 jobs (17 with the matrix expanded) per trigger. On Latchkey the same minutes cost up to 58% less than GitHub-hosted, with zero queue time.

Actions used in this workflow