Skip to content
Latchkey

Package workflow (libtcod/python-tcod)

The Package workflow from libtcod/python-tcod, explained and optimized by Latchkey.

B

CI health: B - good

Point runs-on at Latchkey and get caching, SHA-pinned actions, self-healing for flaky steps, and up to 58% lower cost, applied automatically.

Grade your own workflow free or run it on Latchkey →
Source: libtcod/python-tcod.github/workflows/python-package.ymlLicense BSD-2-ClauseView source

What it does

This is the Package workflow from the libtcod/python-tcod repository, a real project running GitHub Actions. It is shown here with attribution under its BSD-2-Clause license.

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

The workflow

workflow (.yml)
# This workflow will install Python dependencies, run tests and lint with a variety of Python versions
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions

name: Package

on:
  workflow_dispatch:
  push:
  pull_request:

concurrency:
  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
  cancel-in-progress: true

defaults:
  run:
    shell: bash

env:
  git-depth: 0 # Depth to search for tags.
  sdl-version: "3.2.16" # SDL version to bundle

jobs:
  ruff:
    runs-on: ubuntu-latest
    timeout-minutes: 5
    steps:
      - uses: actions/checkout@v7
        with:
          persist-credentials: false
      - name: Install Ruff
        run: pip install ruff
      - name: Ruff Check
        run: ruff check . --fix-only --exit-non-zero-on-fix --output-format=github
      - name: Ruff Format
        run: ruff format . --check

  mypy:
    runs-on: ubuntu-latest
    timeout-minutes: 5
    steps:
      - uses: actions/checkout@v7
        with:
          persist-credentials: false
      - name: Checkout submodules
        run: git submodule update --init --recursive --depth 1
      - name: Install typing dependencies
        run: pip install mypy pytest -r requirements.txt
      - name: Mypy
        run: mypy

  sdist:
    runs-on: ubuntu-latest
    timeout-minutes: 5
    steps:
      - uses: libsdl-org/setup-sdl@1894460666e4fe0c6603ea0cce5733f1cca56ba1
        with:
          install-linux-dependencies: true
          build-type: "Debug"
          version: ${{ env.sdl-version }}
      - uses: actions/checkout@v7
        with:
          persist-credentials: false
          fetch-depth: ${{ env.git-depth }}
      - name: Checkout submodules
        run: git submodule update --init --recursive --depth 1
      - name: Install build
        run: pip install build
      - name: Build source distribution
        run: python -m build --sdist
      - uses: actions/upload-artifact@v7
        with:
          name: sdist
          path: dist/tcod-*.tar.gz
          retention-days: 7
          compression-level: 0

  # This makes sure that the latest versions of the SDL headers parse correctly.
  parse_sdl:
    needs: [ruff, mypy]
    runs-on: ${{ matrix.os }}
    timeout-minutes: 5
    strategy:
      matrix:
        os: ["windows-latest", "macos-latest"]
        sdl-version: ["3.2.16"]
      fail-fast: true
    steps:
      - uses: actions/checkout@v7
        with:
          persist-credentials: false
          fetch-depth: ${{ env.git-depth }}
      - name: Checkout submodules
        run: git submodule update --init --recursive --depth 1
      - uses: actions/setup-python@v6
        with:
          python-version: "3.x"
      - name: Install build dependencies
        run: pip install -r requirements.txt
      - name: Test SDL parsing
        run: python build_sdl.py
        env:
          SDL_VERSION: ${{ matrix.sdl-version }}

  build:
    needs: [ruff, mypy, sdist]
    runs-on: ${{ matrix.os }}
    timeout-minutes: 15
    strategy:
      matrix:
        os: ["ubuntu-latest", "windows-latest"]
        python-version: ["3.10", "3.14t", "pypy-3.10"]
        architecture: ["x64"]
        include:
          - os: "windows-latest"
            python-version: "3.10"
            architecture: "x86"
          - os: "windows-latest"
            python-version: "3.14t"
            architecture: "x86"
          - os: "windows-11-arm"
            python-version: "3.11"
            architecture: "arm64"
          - os: "windows-11-arm"
            python-version: "3.14t"
            architecture: "arm64"
      fail-fast: false

    steps:
      - uses: actions/checkout@v7
        with:
          persist-credentials: false
          fetch-depth: ${{ env.git-depth }}
      - name: Checkout submodules
        run: git submodule update --init --recursive --depth 1
      - name: Set up Python ${{ matrix.python-version }}
        uses: actions/setup-python@v6
        with:
          python-version: ${{ matrix.python-version }}
          architecture: ${{ matrix.architecture }}
      - name: Install APT dependencies
        if: runner.os == 'Linux'
        run: |
          sudo apt-get update
          sudo apt-get install xvfb
      - uses: libsdl-org/setup-sdl@1894460666e4fe0c6603ea0cce5733f1cca56ba1
        if: runner.os == 'Linux'
        with:
          install-linux-dependencies: true
          build-type: "Release"
          version: ${{ env.sdl-version }}
      - name: Install Python dependencies
        run: |
          python -m pip install --upgrade pip
          pip install pytest pytest-cov pytest-benchmark pytest-timeout build
          if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
      - name: Initialize package
        run: pip install -e . # Install the package in-place.
      - name: Build package
        run: python -m build
      - name: Test with pytest
        if: runner.os == 'Windows'
        run: pytest --cov-report=xml --timeout=300
      - name: Test with pytest (Xvfb)
        if: always() && runner.os != 'Windows'
        run: xvfb-run -e /tmp/xvfb.log --server-num=$RANDOM --auto-servernum pytest --cov-report=xml --timeout=300
      - name: Xvfb logs
        if: runner.os != 'Windows'
        run: cat /tmp/xvfb.log
      - uses: codecov/codecov-action@v7
      - uses: actions/upload-artifact@v7
        if: runner.os == 'Windows'
        with:
          name: wheels-windows-${{ matrix.architecture }}-${{ matrix.python-version }}
          path: dist/*.whl
          retention-days: 7
          compression-level: 0

  test-docs:
    needs: [ruff, mypy, sdist]
    runs-on: ubuntu-latest
    timeout-minutes: 15
    steps:
      - uses: libsdl-org/setup-sdl@1894460666e4fe0c6603ea0cce5733f1cca56ba1
        if: runner.os == 'Linux'
        with:
          install-linux-dependencies: true
          build-type: "Debug"
          version: ${{ env.sdl-version }}
      - uses: actions/checkout@v7
        with:
          persist-credentials: false
          fetch-depth: ${{ env.git-depth }}
      - name: Checkout submodules
        run: git submodule update --init --recursive --depth 1
      - name: Install Python dependencies
        run: |
          python -m pip install --upgrade pip
          pip install -r docs/requirements.txt
      - name: Install package
        run: pip install -e .
        env:
          TDL_BUILD: DEBUG
      - name: Test doc generation
        working-directory: docs
        run: python -m sphinx -T -E -W --keep-going . _build/html

  tox:
    needs: [ruff, sdist]
    runs-on: ${{ matrix.os }}
    timeout-minutes: 15
    strategy:
      matrix:
        os: ["ubuntu-latest"] # "windows-latest" disabled due to free-threaded build issues
    steps:
      - uses: actions/checkout@v7
        with:
          persist-credentials: false
          fetch-depth: ${{ env.git-depth }}
      - name: Checkout submodules
        run: git submodule update --init --depth 1
      - name: Set up Python
        uses: actions/setup-python@v6
        with:
          python-version: "3.x"
      - name: Install Python dependencies
        run: |
          python -m pip install --upgrade pip tox
      - uses: libsdl-org/setup-sdl@1894460666e4fe0c6603ea0cce5733f1cca56ba1
        if: runner.os == 'Linux'
        with:
          install-linux-dependencies: true
          build-type: "Debug"
          version: ${{ env.sdl-version }}
      - name: Run tox
        run: |
          tox -vv

  linux-wheels:
    needs: [ruff, mypy, sdist]
    runs-on: ${{ matrix.arch == 'aarch64' && 'ubuntu-24.04-arm' || 'ubuntu-latest'}}
    timeout-minutes: 15
    strategy:
      matrix:
        arch: ["x86_64", "aarch64"]
        build: ["cp310-manylinux*", "pp311-manylinux*", "cp314t-manylinux*"]
    env:
      BUILD_DESC: ""
    steps:
      - uses: actions/checkout@v7
        with:
          persist-credentials: false
          fetch-depth: ${{ env.git-depth }}
      - name: Checkout submodules
        run: git submodule update --init --recursive --depth 1
      - name: Build wheels
        uses: pypa/cibuildwheel@v4.0.0rc1
        env:
          CIBW_BUILD: ${{ matrix.build }}
          CIBW_ARCHS_LINUX: ${{ matrix.arch }}
          CIBW_MANYLINUX_*_IMAGE: manylinux2014
          CIBW_MANYLINUX_PYPY_X86_64_IMAGE: manylinux2014
          CIBW_MANYLINUX_PYPY_AARCH64_IMAGE: manylinux2014
          CIBW_BEFORE_ALL_LINUX: >
            yum install -y epel-release &&
            yum-config-manager --enable epel &&
            yum install -y gcc git-core make cmake \
                alsa-lib-devel pulseaudio-libs-devel \
                libX11-devel libXext-devel libXrandr-devel libXcursor-devel libXfixes-devel \
                libXi-devel libXScrnSaver-devel dbus-devel ibus-devel \
                systemd-devel mesa-libGL-devel libxkbcommon-devel mesa-libGLES-devel \
                mesa-libEGL-devel vulkan-devel wayland-devel wayland-protocols-devel \
                libdrm-devel mesa-libgbm-devel libusb-devel
            git clone --depth 1 --branch release-${{env.sdl-version}} https://github.com/libsdl-org/SDL.git sdl_repo &&
            cmake -S sdl_repo -B sdl_build &&
            cmake --build sdl_build --config Release &&
            cmake --install sdl_build --config Release --prefix /usr/local &&
            cp --verbose /usr/local/lib64/pkgconfig/sdl3.pc /lib64/pkgconfig/sdl3.pc
          CIBW_BEFORE_TEST: pip install numpy
          CIBW_TEST_COMMAND: python -c "import tcod.context"
          # Skip test on emulated architectures
          CIBW_TEST_SKIP: "*_aarch64"
      - name: Remove asterisk from label
        env:
          BUILD_DESC: ${{ matrix.build }}
        run: |
          BUILD_DESC=${BUILD_DESC//\*}
          echo BUILD_DESC=${BUILD_DESC} >> $GITHUB_ENV
      - name: Archive wheel
        uses: actions/upload-artifact@v7
        with:
          name: wheels-linux-${{ matrix.arch }}-${{ env.BUILD_DESC }}
          path: wheelhouse/*.whl
          retention-days: 7
          compression-level: 0

  build-macos:
    needs: [ruff, mypy, sdist]
    runs-on: "macos-14"
    timeout-minutes: 15
    strategy:
      fail-fast: true
      matrix:
        python: ["cp310-*_universal2", "cp314t-*_universal2", "pp311-*"]
    env:
      PYTHON_DESC: ""
    steps:
      - uses: actions/checkout@v7
        with:
          persist-credentials: false
          fetch-depth: ${{ env.git-depth }}
      - name: Checkout submodules
        run: git submodule update --init --recursive --depth 1
      - uses: actions/setup-python@v6
        with:
          python-version: "3.x"
      - name: Install Python dependencies
        run: pip install -r requirements.txt
      - name: Prepare package
        # Downloads SDL for the later step.
        run: python build_sdl.py
      - name: Build wheels
        uses: pypa/cibuildwheel@v4.0.0rc1
        env:
          CIBW_BUILD: ${{ matrix.python }}
          CIBW_ARCHS_MACOS: x86_64 arm64 universal2
          CIBW_BEFORE_BUILD_MACOS: pip install --upgrade delocate
          CIBW_BEFORE_TEST: pip install numpy
          CIBW_TEST_COMMAND: python -c "import tcod.context"
          CIBW_TEST_SKIP: "pp* *-macosx_arm64 *-macosx_universal2:arm64"
          MACOSX_DEPLOYMENT_TARGET: "10.13"
      - name: Remove asterisk from label
        env:
          PYTHON_DESC: ${{ matrix.python }}
        run: |
          PYTHON_DESC=${PYTHON_DESC//\*/X}
          echo PYTHON_DESC=${PYTHON_DESC} >> $GITHUB_ENV
      - name: Archive wheel
        uses: actions/upload-artifact@v7
        with:
          name: wheels-macos-${{ env.PYTHON_DESC }}
          path: wheelhouse/*.whl
          retention-days: 7
          compression-level: 0

  pyodide:
    needs: [ruff, mypy, sdist]
    runs-on: ubuntu-24.04
    timeout-minutes: 15
    steps:
      - uses: actions/checkout@v7
        with:
          persist-credentials: false
          fetch-depth: ${{ env.git-depth }}
      - name: Checkout submodules
        run: git submodule update --init --recursive --depth 1
      - uses: libsdl-org/setup-sdl@1894460666e4fe0c6603ea0cce5733f1cca56ba1
        with:
          install-linux-dependencies: true
          build-type: "Debug"
          version: "3.2.4" # Should be equal or less than the version used by Emscripten
      - uses: pypa/cibuildwheel@v4.0.0rc1
        env:
          CIBW_BUILD: cp313-pyodide_wasm32
          CIBW_PLATFORM: pyodide
      - name: Archive wheel
        uses: actions/upload-artifact@v7
        with:
          name: wheels-pyodide
          path: wheelhouse/*.whl
          retention-days: 30
          compression-level: 0

  publish:
    needs: [sdist, build, build-macos, linux-wheels, pyodide]
    runs-on: ubuntu-latest
    timeout-minutes: 5
    if: github.ref_type == 'tag'
    environment:
      name: pypi
      url: https://pypi.org/project/tcod/${{ github.ref_name }}
    permissions:
      id-token: write # Attestation
    steps:
      - uses: actions/download-artifact@v8
        with:
          name: sdist
          path: dist/
      - uses: actions/download-artifact@v8
        with:
          pattern: wheels-*
          path: dist/
          merge-multiple: true
      - uses: pypa/gh-action-pypi-publish@release/v1
        with:
          skip-existing: true

The same workflow, on Latchkey

Estimated ~20% faster on cache hits, plus fewer wasted runs and a safer supply chain. Added and changed lines are highlighted.

# This workflow will install Python dependencies, run tests and lint with a variety of Python versions
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
 
name: Package
 
on:
  workflow_dispatch:
  push:
  pull_request:
 
concurrency:
  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
  cancel-in-progress: true
 
defaults:
  run:
    shell: bash
 
env:
  git-depth: 0 # Depth to search for tags.
  sdl-version: "3.2.16" # SDL version to bundle
 
jobs:
  ruff:
    runs-on: latchkey-small
    timeout-minutes: 5
    steps:
      - uses: actions/checkout@v7
        with:
          persist-credentials: false
      - name: Install Ruff
        run: pip install ruff
      - name: Ruff Check
        run: ruff check . --fix-only --exit-non-zero-on-fix --output-format=github
      - name: Ruff Format
        run: ruff format . --check
 
  mypy:
    runs-on: latchkey-small
    timeout-minutes: 5
    steps:
      - uses: actions/checkout@v7
        with:
          persist-credentials: false
      - name: Checkout submodules
        run: git submodule update --init --recursive --depth 1
      - name: Install typing dependencies
        run: pip install mypy pytest -r requirements.txt
      - name: Mypy
        run: mypy
 
  sdist:
    runs-on: latchkey-small
    timeout-minutes: 5
    steps:
      - uses: libsdl-org/setup-sdl@1894460666e4fe0c6603ea0cce5733f1cca56ba1
        with:
          install-linux-dependencies: true
          build-type: "Debug"
          version: ${{ env.sdl-version }}
      - uses: actions/checkout@v7
        with:
          persist-credentials: false
          fetch-depth: ${{ env.git-depth }}
      - name: Checkout submodules
        run: git submodule update --init --recursive --depth 1
      - name: Install build
        run: pip install build
      - name: Build source distribution
        run: python -m build --sdist
      - uses: actions/upload-artifact@v7
        with:
          name: sdist
          path: dist/tcod-*.tar.gz
          retention-days: 7
          compression-level: 0
 
  # This makes sure that the latest versions of the SDL headers parse correctly.
  parse_sdl:
    needs: [ruff, mypy]
    runs-on: ${{ matrix.os }}
    timeout-minutes: 5
    strategy:
      matrix:
        os: ["windows-latest", "macos-latest"]
        sdl-version: ["3.2.16"]
      fail-fast: true
    steps:
      - uses: actions/checkout@v7
        with:
          persist-credentials: false
          fetch-depth: ${{ env.git-depth }}
      - name: Checkout submodules
        run: git submodule update --init --recursive --depth 1
      - uses: actions/setup-python@v6
        with:
          cache: 'pip'
          python-version: "3.x"
      - name: Install build dependencies
        run: pip install -r requirements.txt
      - name: Test SDL parsing
        run: python build_sdl.py
        env:
          SDL_VERSION: ${{ matrix.sdl-version }}
 
  build:
    needs: [ruff, mypy, sdist]
    runs-on: ${{ matrix.os }}
    timeout-minutes: 15
    strategy:
      matrix:
        os: ["ubuntu-latest", "windows-latest"]
        python-version: ["3.10", "3.14t", "pypy-3.10"]
        architecture: ["x64"]
        include:
          - os: "windows-latest"
            python-version: "3.10"
            architecture: "x86"
          - os: "windows-latest"
            python-version: "3.14t"
            architecture: "x86"
          - os: "windows-11-arm"
            python-version: "3.11"
            architecture: "arm64"
          - os: "windows-11-arm"
            python-version: "3.14t"
            architecture: "arm64"
      fail-fast: false
 
    steps:
      - uses: actions/checkout@v7
        with:
          persist-credentials: false
          fetch-depth: ${{ env.git-depth }}
      - name: Checkout submodules
        run: git submodule update --init --recursive --depth 1
      - name: Set up Python ${{ matrix.python-version }}
        uses: actions/setup-python@v6
        with:
          cache: 'pip'
          python-version: ${{ matrix.python-version }}
          architecture: ${{ matrix.architecture }}
      - name: Install APT dependencies
        if: runner.os == 'Linux'
        run: |
          sudo apt-get update
          sudo apt-get install xvfb
      - uses: libsdl-org/setup-sdl@1894460666e4fe0c6603ea0cce5733f1cca56ba1
        if: runner.os == 'Linux'
        with:
          install-linux-dependencies: true
          build-type: "Release"
          version: ${{ env.sdl-version }}
      - name: Install Python dependencies
        run: |
          python -m pip install --upgrade pip
          pip install pytest pytest-cov pytest-benchmark pytest-timeout build
          if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
      - name: Initialize package
        run: pip install -e . # Install the package in-place.
      - name: Build package
        run: python -m build
      - name: Test with pytest
        if: runner.os == 'Windows'
        run: pytest --cov-report=xml --timeout=300
      - name: Test with pytest (Xvfb)
        if: always() && runner.os != 'Windows'
        run: xvfb-run -e /tmp/xvfb.log --server-num=$RANDOM --auto-servernum pytest --cov-report=xml --timeout=300
      - name: Xvfb logs
        if: runner.os != 'Windows'
        run: cat /tmp/xvfb.log
      - uses: codecov/codecov-action@v7
      - uses: actions/upload-artifact@v7
        if: runner.os == 'Windows'
        with:
          name: wheels-windows-${{ matrix.architecture }}-${{ matrix.python-version }}
          path: dist/*.whl
          retention-days: 7
          compression-level: 0
 
  test-docs:
    needs: [ruff, mypy, sdist]
    runs-on: latchkey-small
    timeout-minutes: 15
    steps:
      - uses: libsdl-org/setup-sdl@1894460666e4fe0c6603ea0cce5733f1cca56ba1
        if: runner.os == 'Linux'
        with:
          install-linux-dependencies: true
          build-type: "Debug"
          version: ${{ env.sdl-version }}
      - uses: actions/checkout@v7
        with:
          persist-credentials: false
          fetch-depth: ${{ env.git-depth }}
      - name: Checkout submodules
        run: git submodule update --init --recursive --depth 1
      - name: Install Python dependencies
        run: |
          python -m pip install --upgrade pip
          pip install -r docs/requirements.txt
      - name: Install package
        run: pip install -e .
        env:
          TDL_BUILD: DEBUG
      - name: Test doc generation
        working-directory: docs
        run: python -m sphinx -T -E -W --keep-going . _build/html
 
  tox:
    needs: [ruff, sdist]
    runs-on: ${{ matrix.os }}
    timeout-minutes: 15
    strategy:
      matrix:
        os: ["ubuntu-latest"] # "windows-latest" disabled due to free-threaded build issues
    steps:
      - uses: actions/checkout@v7
        with:
          persist-credentials: false
          fetch-depth: ${{ env.git-depth }}
      - name: Checkout submodules
        run: git submodule update --init --depth 1
      - name: Set up Python
        uses: actions/setup-python@v6
        with:
          cache: 'pip'
          python-version: "3.x"
      - name: Install Python dependencies
        run: |
          python -m pip install --upgrade pip tox
      - uses: libsdl-org/setup-sdl@1894460666e4fe0c6603ea0cce5733f1cca56ba1
        if: runner.os == 'Linux'
        with:
          install-linux-dependencies: true
          build-type: "Debug"
          version: ${{ env.sdl-version }}
      - name: Run tox
        run: |
          tox -vv
 
  linux-wheels:
    needs: [ruff, mypy, sdist]
    runs-on: ${{ matrix.arch == 'aarch64' && 'ubuntu-24.04-arm' || 'ubuntu-latest'}}
    timeout-minutes: 15
    strategy:
      matrix:
        arch: ["x86_64", "aarch64"]
        build: ["cp310-manylinux*", "pp311-manylinux*", "cp314t-manylinux*"]
    env:
      BUILD_DESC: ""
    steps:
      - uses: actions/checkout@v7
        with:
          persist-credentials: false
          fetch-depth: ${{ env.git-depth }}
      - name: Checkout submodules
        run: git submodule update --init --recursive --depth 1
      - name: Build wheels
        uses: pypa/cibuildwheel@v4.0.0rc1
        env:
          CIBW_BUILD: ${{ matrix.build }}
          CIBW_ARCHS_LINUX: ${{ matrix.arch }}
          CIBW_MANYLINUX_*_IMAGE: manylinux2014
          CIBW_MANYLINUX_PYPY_X86_64_IMAGE: manylinux2014
          CIBW_MANYLINUX_PYPY_AARCH64_IMAGE: manylinux2014
          CIBW_BEFORE_ALL_LINUX: >
            yum install -y epel-release &&
            yum-config-manager --enable epel &&
            yum install -y gcc git-core make cmake \
                alsa-lib-devel pulseaudio-libs-devel \
                libX11-devel libXext-devel libXrandr-devel libXcursor-devel libXfixes-devel \
                libXi-devel libXScrnSaver-devel dbus-devel ibus-devel \
                systemd-devel mesa-libGL-devel libxkbcommon-devel mesa-libGLES-devel \
                mesa-libEGL-devel vulkan-devel wayland-devel wayland-protocols-devel \
                libdrm-devel mesa-libgbm-devel libusb-devel
            git clone --depth 1 --branch release-${{env.sdl-version}} https://github.com/libsdl-org/SDL.git sdl_repo &&
            cmake -S sdl_repo -B sdl_build &&
            cmake --build sdl_build --config Release &&
            cmake --install sdl_build --config Release --prefix /usr/local &&
            cp --verbose /usr/local/lib64/pkgconfig/sdl3.pc /lib64/pkgconfig/sdl3.pc
          CIBW_BEFORE_TEST: pip install numpy
          CIBW_TEST_COMMAND: python -c "import tcod.context"
          # Skip test on emulated architectures
          CIBW_TEST_SKIP: "*_aarch64"
      - name: Remove asterisk from label
        env:
          BUILD_DESC: ${{ matrix.build }}
        run: |
          BUILD_DESC=${BUILD_DESC//\*}
          echo BUILD_DESC=${BUILD_DESC} >> $GITHUB_ENV
      - name: Archive wheel
        uses: actions/upload-artifact@v7
        with:
          name: wheels-linux-${{ matrix.arch }}-${{ env.BUILD_DESC }}
          path: wheelhouse/*.whl
          retention-days: 7
          compression-level: 0
 
  build-macos:
    needs: [ruff, mypy, sdist]
    runs-on: "macos-14"
    timeout-minutes: 15
    strategy:
      fail-fast: true
      matrix:
        python: ["cp310-*_universal2", "cp314t-*_universal2", "pp311-*"]
    env:
      PYTHON_DESC: ""
    steps:
      - uses: actions/checkout@v7
        with:
          persist-credentials: false
          fetch-depth: ${{ env.git-depth }}
      - name: Checkout submodules
        run: git submodule update --init --recursive --depth 1
      - uses: actions/setup-python@v6
        with:
          cache: 'pip'
          python-version: "3.x"
      - name: Install Python dependencies
        run: pip install -r requirements.txt
      - name: Prepare package
        # Downloads SDL for the later step.
        run: python build_sdl.py
      - name: Build wheels
        uses: pypa/cibuildwheel@v4.0.0rc1
        env:
          CIBW_BUILD: ${{ matrix.python }}
          CIBW_ARCHS_MACOS: x86_64 arm64 universal2
          CIBW_BEFORE_BUILD_MACOS: pip install --upgrade delocate
          CIBW_BEFORE_TEST: pip install numpy
          CIBW_TEST_COMMAND: python -c "import tcod.context"
          CIBW_TEST_SKIP: "pp* *-macosx_arm64 *-macosx_universal2:arm64"
          MACOSX_DEPLOYMENT_TARGET: "10.13"
      - name: Remove asterisk from label
        env:
          PYTHON_DESC: ${{ matrix.python }}
        run: |
          PYTHON_DESC=${PYTHON_DESC//\*/X}
          echo PYTHON_DESC=${PYTHON_DESC} >> $GITHUB_ENV
      - name: Archive wheel
        uses: actions/upload-artifact@v7
        with:
          name: wheels-macos-${{ env.PYTHON_DESC }}
          path: wheelhouse/*.whl
          retention-days: 7
          compression-level: 0
 
  pyodide:
    needs: [ruff, mypy, sdist]
    runs-on: latchkey-small
    timeout-minutes: 15
    steps:
      - uses: actions/checkout@v7
        with:
          persist-credentials: false
          fetch-depth: ${{ env.git-depth }}
      - name: Checkout submodules
        run: git submodule update --init --recursive --depth 1
      - uses: libsdl-org/setup-sdl@1894460666e4fe0c6603ea0cce5733f1cca56ba1
        with:
          install-linux-dependencies: true
          build-type: "Debug"
          version: "3.2.4" # Should be equal or less than the version used by Emscripten
      - uses: pypa/cibuildwheel@v4.0.0rc1
        env:
          CIBW_BUILD: cp313-pyodide_wasm32
          CIBW_PLATFORM: pyodide
      - name: Archive wheel
        uses: actions/upload-artifact@v7
        with:
          name: wheels-pyodide
          path: wheelhouse/*.whl
          retention-days: 30
          compression-level: 0
 
  publish:
    needs: [sdist, build, build-macos, linux-wheels, pyodide]
    runs-on: latchkey-small
    timeout-minutes: 5
    if: github.ref_type == 'tag'
    environment:
      name: pypi
      url: https://pypi.org/project/tcod/${{ github.ref_name }}
    permissions:
      id-token: write # Attestation
    steps:
      - uses: actions/download-artifact@v8
        with:
          name: sdist
          path: dist/
      - uses: actions/download-artifact@v8
        with:
          pattern: wheels-*
          path: dist/
          merge-multiple: true
      - uses: pypa/gh-action-pypi-publish@release/v1
        with:
          skip-existing: true
 

What changed

3 third-party actions are referenced by a movable tag. Pin them to the commit SHA (Latchkey resolves and applies this automatically) so a repointed tag cannot change what runs.

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 11 jobs (24 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