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.
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 →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
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: trueThe 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
- Cancel superseded runs when a branch or PR gets a newer push.
- Add a job timeout so a hung step cannot burn hours of runner time.
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:
- Dependency installs
- Network fetches
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.