From 8047f172f7a3858e5612774de6475446952e1f03 Mon Sep 17 00:00:00 2001
From: Zanie Blue <contact@zanie.dev>
Date: Tue, 17 Dec 2024 16:19:58 -0600
Subject: [PATCH] Generate CI matrix at runtime

---
 .github/workflows/apple.yml   | 166 ++----
 .github/workflows/linux.yml   | 997 +---------------------------------
 .github/workflows/windows.yml |  57 +-
 ci-matrix.py                  | 191 +++++++
 ci-targets.yaml               | 322 +++++++++++
 5 files changed, 608 insertions(+), 1125 deletions(-)
 create mode 100644 ci-matrix.py
 create mode 100644 ci-targets.yaml

diff --git a/.github/workflows/apple.yml b/.github/workflows/apple.yml
index deb21c61..05220096 100644
--- a/.github/workflows/apple.yml
+++ b/.github/workflows/apple.yml
@@ -40,135 +40,39 @@ jobs:
           name: pythonbuild
           path: target/release/pythonbuild
 
+  generate-matrix:
+    runs-on: ubuntu-latest
+    outputs:
+      matrix: ${{ steps.set-matrix.outputs.matrix }}
+    steps:
+      - uses: actions/checkout@v4
+
+      - name: Set up Python
+        uses: astral-sh/setup-uv@v4
+
+      - name: Get pull request labels
+        id: get-labels
+        run: |
+          # Convert GitHub labels array to comma-separated string
+          LABELS=$(echo '${{ toJson(github.event.pull_request.labels.*.name) }}' | jq -r 'join(",")')
+          echo "labels=$LABELS" >> $GITHUB_OUTPUT
+
+      - name: Generate build matrix
+        id: set-matrix
+        run: |
+          uv run ci-matrix.py --platform darwin --labels "${{ steps.get-labels.outputs.labels }}" > matrix.json && echo "matrix=$(cat matrix.json)" >> $GITHUB_OUTPUT
+          # Display the matrix for debugging too
+          cat matrix.json | jq
+
   build:
-    strategy:
-      fail-fast: false
-      matrix:
-        build:
-          - target_triple: 'aarch64-apple-darwin'
-            py: 'cpython-3.9'
-            options: 'debug'
-          - target_triple: 'aarch64-apple-darwin'
-            py: 'cpython-3.9'
-            options: 'pgo'
-          - target_triple: 'aarch64-apple-darwin'
-            py: 'cpython-3.9'
-            options: 'pgo+lto'
-
-          - target_triple: 'aarch64-apple-darwin'
-            py: 'cpython-3.10'
-            options: 'debug'
-          - target_triple: 'aarch64-apple-darwin'
-            py: 'cpython-3.10'
-            options: 'pgo'
-          - target_triple: 'aarch64-apple-darwin'
-            py: 'cpython-3.10'
-            options: 'pgo+lto'
-
-          - target_triple: 'aarch64-apple-darwin'
-            py: 'cpython-3.11'
-            options: 'debug'
-          - target_triple: 'aarch64-apple-darwin'
-            py: 'cpython-3.11'
-            options: 'pgo'
-          - target_triple: 'aarch64-apple-darwin'
-            py: 'cpython-3.11'
-            options: 'pgo+lto'
-
-          - target_triple: 'aarch64-apple-darwin'
-            py: 'cpython-3.12'
-            options: 'debug'
-          - target_triple: 'aarch64-apple-darwin'
-            py: 'cpython-3.12'
-            options: 'pgo'
-          - target_triple: 'aarch64-apple-darwin'
-            py: 'cpython-3.12'
-            options: 'pgo+lto'
-
-          - target_triple: 'aarch64-apple-darwin'
-            py: 'cpython-3.13'
-            options: 'debug'
-          - target_triple: 'aarch64-apple-darwin'
-            py: 'cpython-3.13'
-            options: 'pgo'
-          - target_triple: 'aarch64-apple-darwin'
-            py: 'cpython-3.13'
-            options: 'pgo+lto'
-
-          - target_triple: 'aarch64-apple-darwin'
-            py: 'cpython-3.13'
-            options: 'freethreaded+debug'
-          - target_triple: 'aarch64-apple-darwin'
-            py: 'cpython-3.13'
-            options: 'freethreaded+pgo'
-          - target_triple: 'aarch64-apple-darwin'
-            py: 'cpython-3.13'
-            options: 'freethreaded+pgo+lto'
-
-          # macOS on Intel hardware. This is pretty straightforward. We exclude
-          # noopt because it doesn't provide any compelling advantages over PGO
-          # or LTO builds.
-          - target_triple: 'x86_64-apple-darwin'
-            py: 'cpython-3.9'
-            options: 'debug'
-          - target_triple: 'x86_64-apple-darwin'
-            py: 'cpython-3.9'
-            options: 'pgo'
-          - target_triple: 'x86_64-apple-darwin'
-            py: 'cpython-3.9'
-            options: 'pgo+lto'
-
-          - target_triple: 'x86_64-apple-darwin'
-            py: 'cpython-3.10'
-            options: 'debug'
-          - target_triple: 'x86_64-apple-darwin'
-            py: 'cpython-3.10'
-            options: 'pgo'
-          - target_triple: 'x86_64-apple-darwin'
-            py: 'cpython-3.10'
-            options: 'pgo+lto'
-
-          - target_triple: 'x86_64-apple-darwin'
-            py: 'cpython-3.11'
-            options: 'debug'
-          - target_triple: 'x86_64-apple-darwin'
-            py: 'cpython-3.11'
-            options: 'pgo'
-          - target_triple: 'x86_64-apple-darwin'
-            py: 'cpython-3.11'
-            options: 'pgo+lto'
-
-          - target_triple: 'x86_64-apple-darwin'
-            py: 'cpython-3.12'
-            options: 'debug'
-          - target_triple: 'x86_64-apple-darwin'
-            py: 'cpython-3.12'
-            options: 'pgo'
-          - target_triple: 'x86_64-apple-darwin'
-            py: 'cpython-3.12'
-            options: 'pgo+lto'
-
-          - target_triple: 'x86_64-apple-darwin'
-            py: 'cpython-3.13'
-            options: 'debug'
-          - target_triple: 'x86_64-apple-darwin'
-            py: 'cpython-3.13'
-            options: 'pgo'
-          - target_triple: 'x86_64-apple-darwin'
-            py: 'cpython-3.13'
-            options: 'pgo+lto'
-          - target_triple: 'x86_64-apple-darwin'
-            py: 'cpython-3.13'
-            options: 'freethreaded+debug'
-          - target_triple: 'x86_64-apple-darwin'
-            py: 'cpython-3.13'
-            options: 'freethreaded+pgo'
-          - target_triple: 'x86_64-apple-darwin'
-            py: 'cpython-3.13'
-            options: 'freethreaded+pgo+lto'
     needs:
+      - generate-matrix
       - pythonbuild
-    runs-on: depot-macos-14
+    strategy:
+      matrix: ${{ fromJson(needs.generate-matrix.outputs.matrix) }}
+      fail-fast: false
+    runs-on: macos-14
+    name: ${{ matrix.target_triple }} / ${{ matrix.python }} / ${{ matrix.build_options }}
     steps:
       - uses: actions/checkout@v4
         with:
@@ -187,21 +91,21 @@ jobs:
 
       - name: Build
         run: |
-          if [ "${{ matrix.build.target_triple }}" = "aarch64-apple-darwin" ]; then
+          if [ "${{ matrix.target_triple }}" = "aarch64-apple-darwin" ]; then
             export APPLE_SDK_PATH=/Applications/Xcode_15.2.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX14.2.sdk
-          elif [ "${{ matrix.build.target_triple }}" = "x86_64-apple-darwin" ]; then
+          elif [ "${{ matrix.target_triple }}" = "x86_64-apple-darwin" ]; then
             export APPLE_SDK_PATH=/Applications/Xcode_15.2.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX14.2.sdk
           else
-            echo "unhandled target triple: ${{ matrix.build.target_triple }}"
+            echo "unhandled target triple: ${{ matrix.target_triple }}"
             exit 1
           fi
 
-          ./build-macos.py --target-triple ${{ matrix.build.target_triple }} --python ${{ matrix.build.py }} --options ${{ matrix.build.options }}
+          ./build-macos.py --target-triple ${{ matrix.target_triple }} --python cpython-${{ matrix.python }} --options ${{ matrix.build_options }}
 
       - name: Upload Distributions
         uses: actions/upload-artifact@v4
         with:
-          name: ${{ matrix.build.py }}-${{ matrix.build.target_triple }}-${{ matrix.build.options }}
+          name: cpython-${{ matrix.python }}-${{ matrix.target_triple }}-${{ matrix.build_options }}
           path: dist/*
 
       - uses: actions/checkout@v4
diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml
index 14a5cb34..3bac4e77 100644
--- a/.github/workflows/linux.yml
+++ b/.github/workflows/linux.yml
@@ -115,981 +115,40 @@ jobs:
           name: image-${{ matrix.image }}
           path: build/image-*
 
-  build:
-    strategy:
-      fail-fast: false
-      matrix:
-        build:
-          # Cross-compiles can't do PGO.
-
-          - target_triple: 'aarch64-unknown-linux-gnu'
-            py: 'cpython-3.9'
-            options: 'debug'
-          - target_triple: 'aarch64-unknown-linux-gnu'
-            py: 'cpython-3.9'
-            options: 'noopt'
-          - target_triple: 'aarch64-unknown-linux-gnu'
-            py: 'cpython-3.9'
-            options: 'lto'
-
-          - target_triple: 'aarch64-unknown-linux-gnu'
-            py: 'cpython-3.10'
-            options: 'debug'
-          - target_triple: 'aarch64-unknown-linux-gnu'
-            py: 'cpython-3.10'
-            options: 'noopt'
-          - target_triple: 'aarch64-unknown-linux-gnu'
-            py: 'cpython-3.10'
-            options: 'lto'
-
-          - target_triple: 'aarch64-unknown-linux-gnu'
-            py: 'cpython-3.11'
-            options: 'debug'
-          - target_triple: 'aarch64-unknown-linux-gnu'
-            py: 'cpython-3.11'
-            options: 'noopt'
-          - target_triple: 'aarch64-unknown-linux-gnu'
-            py: 'cpython-3.11'
-            options: 'lto'
-
-          - target_triple: 'aarch64-unknown-linux-gnu'
-            py: 'cpython-3.12'
-            options: 'debug'
-          - target_triple: 'aarch64-unknown-linux-gnu'
-            py: 'cpython-3.12'
-            options: 'noopt'
-          - target_triple: 'aarch64-unknown-linux-gnu'
-            py: 'cpython-3.12'
-            options: 'lto'
-
-          # Cross-compiles can't do PGO and require Python 3.9.
-          - target_triple: 'armv7-unknown-linux-gnueabi'
-            py: 'cpython-3.9'
-            options: 'debug'
-          - target_triple: 'armv7-unknown-linux-gnueabi'
-            py: 'cpython-3.9'
-            options: 'noopt'
-          - target_triple: 'armv7-unknown-linux-gnueabi'
-            py: 'cpython-3.9'
-            options: 'lto'
-
-          - target_triple: 'armv7-unknown-linux-gnueabi'
-            py: 'cpython-3.10'
-            options: 'debug'
-          - target_triple: 'armv7-unknown-linux-gnueabi'
-            py: 'cpython-3.10'
-            options: 'noopt'
-          - target_triple: 'armv7-unknown-linux-gnueabi'
-            py: 'cpython-3.10'
-            options: 'lto'
-
-          - target_triple: 'armv7-unknown-linux-gnueabi'
-            py: 'cpython-3.11'
-            options: 'debug'
-          - target_triple: 'armv7-unknown-linux-gnueabi'
-            py: 'cpython-3.11'
-            options: 'noopt'
-          - target_triple: 'armv7-unknown-linux-gnueabi'
-            py: 'cpython-3.11'
-            options: 'lto'
-
-          - target_triple: 'armv7-unknown-linux-gnueabi'
-            py: 'cpython-3.12'
-            options: 'debug'
-          - target_triple: 'armv7-unknown-linux-gnueabi'
-            py: 'cpython-3.12'
-            options: 'noopt'
-          - target_triple: 'armv7-unknown-linux-gnueabi'
-            py: 'cpython-3.12'
-            options: 'lto'
-
-          # Cross-compiles can't do PGO and require Python 3.9.
-          - target_triple: 'armv7-unknown-linux-gnueabihf'
-            py: 'cpython-3.9'
-            options: 'debug'
-          - target_triple: 'armv7-unknown-linux-gnueabihf'
-            py: 'cpython-3.9'
-            options: 'noopt'
-          - target_triple: 'armv7-unknown-linux-gnueabihf'
-            py: 'cpython-3.9'
-            options: 'lto'
-
-          - target_triple: 'armv7-unknown-linux-gnueabihf'
-            py: 'cpython-3.10'
-            options: 'debug'
-          - target_triple: 'armv7-unknown-linux-gnueabihf'
-            py: 'cpython-3.10'
-            options: 'noopt'
-          - target_triple: 'armv7-unknown-linux-gnueabihf'
-            py: 'cpython-3.10'
-            options: 'lto'
-
-          - target_triple: 'armv7-unknown-linux-gnueabihf'
-            py: 'cpython-3.11'
-            options: 'debug'
-          - target_triple: 'armv7-unknown-linux-gnueabihf'
-            py: 'cpython-3.11'
-            options: 'noopt'
-          - target_triple: 'armv7-unknown-linux-gnueabihf'
-            py: 'cpython-3.11'
-            options: 'lto'
-
-          - target_triple: 'armv7-unknown-linux-gnueabihf'
-            py: 'cpython-3.12'
-            options: 'debug'
-          - target_triple: 'armv7-unknown-linux-gnueabihf'
-            py: 'cpython-3.12'
-            options: 'noopt'
-          - target_triple: 'armv7-unknown-linux-gnueabihf'
-            py: 'cpython-3.12'
-            options: 'lto'
-
-          # Cross-compiles can't do PGO and require Python 3.9.
-          - target_triple: 's390x-unknown-linux-gnu'
-            py: 'cpython-3.9'
-            options: 'debug'
-          - target_triple: 's390x-unknown-linux-gnu'
-            py: 'cpython-3.9'
-            options: 'noopt'
-          - target_triple: 's390x-unknown-linux-gnu'
-            py: 'cpython-3.9'
-            options: 'lto'
-
-          - target_triple: 's390x-unknown-linux-gnu'
-            py: 'cpython-3.10'
-            options: 'debug'
-          - target_triple: 's390x-unknown-linux-gnu'
-            py: 'cpython-3.10'
-            options: 'noopt'
-          - target_triple: 's390x-unknown-linux-gnu'
-            py: 'cpython-3.10'
-            options: 'lto'
-
-          - target_triple: 's390x-unknown-linux-gnu'
-            py: 'cpython-3.11'
-            options: 'debug'
-          - target_triple: 's390x-unknown-linux-gnu'
-            py: 'cpython-3.11'
-            options: 'noopt'
-          - target_triple: 's390x-unknown-linux-gnu'
-            py: 'cpython-3.11'
-            options: 'lto'
-
-          - target_triple: 's390x-unknown-linux-gnu'
-            py: 'cpython-3.12'
-            options: 'debug'
-          - target_triple: 's390x-unknown-linux-gnu'
-            py: 'cpython-3.12'
-            options: 'noopt'
-          - target_triple: 's390x-unknown-linux-gnu'
-            py: 'cpython-3.12'
-            options: 'lto'
-
-          # Cross-compiles can't do PGO and require Python 3.9.
-          - target_triple: 'ppc64le-unknown-linux-gnu'
-            py: 'cpython-3.9'
-            options: 'debug'
-          - target_triple: 'ppc64le-unknown-linux-gnu'
-            py: 'cpython-3.9'
-            options: 'noopt'
-          - target_triple: 'ppc64le-unknown-linux-gnu'
-            py: 'cpython-3.9'
-            options: 'lto'
-
-          - target_triple: 'ppc64le-unknown-linux-gnu'
-            py: 'cpython-3.10'
-            options: 'debug'
-          - target_triple: 'ppc64le-unknown-linux-gnu'
-            py: 'cpython-3.10'
-            options: 'noopt'
-          - target_triple: 'ppc64le-unknown-linux-gnu'
-            py: 'cpython-3.10'
-            options: 'lto'
-
-          - target_triple: 'ppc64le-unknown-linux-gnu'
-            py: 'cpython-3.11'
-            options: 'debug'
-          - target_triple: 'ppc64le-unknown-linux-gnu'
-            py: 'cpython-3.11'
-            options: 'noopt'
-          - target_triple: 'ppc64le-unknown-linux-gnu'
-            py: 'cpython-3.11'
-            options: 'lto'
-
-          - target_triple: 'ppc64le-unknown-linux-gnu'
-            py: 'cpython-3.12'
-            options: 'debug'
-          - target_triple: 'ppc64le-unknown-linux-gnu'
-            py: 'cpython-3.12'
-            options: 'noopt'
-          - target_triple: 'ppc64le-unknown-linux-gnu'
-            py: 'cpython-3.12'
-            options: 'lto'
-
-          - target_triple: 'x86_64-unknown-linux-gnu'
-            py: 'cpython-3.9'
-            options: 'debug'
-            run: true
-          - target_triple: 'x86_64-unknown-linux-gnu'
-            py: 'cpython-3.9'
-            options: 'pgo'
-            run: true
-          - target_triple: 'x86_64-unknown-linux-gnu'
-            py: 'cpython-3.9'
-            options: 'pgo+lto'
-            run: true
-
-          - target_triple: 'x86_64-unknown-linux-gnu'
-            py: 'cpython-3.10'
-            options: 'debug'
-            run: true
-          - target_triple: 'x86_64-unknown-linux-gnu'
-            py: 'cpython-3.10'
-            options: 'pgo'
-            run: true
-          - target_triple: 'x86_64-unknown-linux-gnu'
-            py: 'cpython-3.10'
-            options: 'pgo+lto'
-            run: true
-
-          - target_triple: 'x86_64-unknown-linux-gnu'
-            py: 'cpython-3.11'
-            options: 'debug'
-            run: true
-          - target_triple: 'x86_64-unknown-linux-gnu'
-            py: 'cpython-3.11'
-            options: 'pgo'
-            run: true
-          - target_triple: 'x86_64-unknown-linux-gnu'
-            py: 'cpython-3.11'
-            options: 'pgo+lto'
-            run: true
-
-          - target_triple: 'x86_64-unknown-linux-gnu'
-            py: 'cpython-3.12'
-            options: 'debug'
-            run: true
-          - target_triple: 'x86_64-unknown-linux-gnu'
-            py: 'cpython-3.12'
-            options: 'pgo'
-            run: true
-          - target_triple: 'x86_64-unknown-linux-gnu'
-            py: 'cpython-3.12'
-            options: 'pgo+lto'
-            run: true
-
-          - target_triple: 'x86_64_v2-unknown-linux-gnu'
-            py: 'cpython-3.9'
-            options: 'debug'
-            run: true
-          - target_triple: 'x86_64_v2-unknown-linux-gnu'
-            py: 'cpython-3.9'
-            options: 'pgo'
-            run: true
-          - target_triple: 'x86_64_v2-unknown-linux-gnu'
-            py: 'cpython-3.9'
-            options: 'pgo+lto'
-            run: true
-
-          - target_triple: 'x86_64_v2-unknown-linux-gnu'
-            py: 'cpython-3.10'
-            options: 'debug'
-            run: true
-          - target_triple: 'x86_64_v2-unknown-linux-gnu'
-            py: 'cpython-3.10'
-            options: 'pgo'
-            run: true
-          - target_triple: 'x86_64_v2-unknown-linux-gnu'
-            py: 'cpython-3.10'
-            options: 'pgo+lto'
-            run: true
-
-          - target_triple: 'x86_64_v2-unknown-linux-gnu'
-            py: 'cpython-3.11'
-            options: 'debug'
-            run: true
-          - target_triple: 'x86_64_v2-unknown-linux-gnu'
-            py: 'cpython-3.11'
-            options: 'pgo'
-            run: true
-          - target_triple: 'x86_64_v2-unknown-linux-gnu'
-            py: 'cpython-3.11'
-            options: 'pgo+lto'
-            run: true
-
-          - target_triple: 'x86_64_v2-unknown-linux-gnu'
-            py: 'cpython-3.12'
-            options: 'debug'
-            run: true
-          - target_triple: 'x86_64_v2-unknown-linux-gnu'
-            py: 'cpython-3.12'
-            options: 'pgo'
-            run: true
-          - target_triple: 'x86_64_v2-unknown-linux-gnu'
-            py: 'cpython-3.12'
-            options: 'pgo+lto'
-            run: true
-
-          - target_triple: 'x86_64_v3-unknown-linux-gnu'
-            py: 'cpython-3.9'
-            options: 'debug'
-            run: true
-          - target_triple: 'x86_64_v3-unknown-linux-gnu'
-            py: 'cpython-3.9'
-            options: 'pgo'
-            run: true
-          - target_triple: 'x86_64_v3-unknown-linux-gnu'
-            py: 'cpython-3.9'
-            options: 'pgo+lto'
-            run: true
-
-          - target_triple: 'x86_64_v3-unknown-linux-gnu'
-            py: 'cpython-3.10'
-            options: 'debug'
-            run: true
-          - target_triple: 'x86_64_v3-unknown-linux-gnu'
-            py: 'cpython-3.10'
-            options: 'pgo'
-            run: true
-          - target_triple: 'x86_64_v3-unknown-linux-gnu'
-            py: 'cpython-3.10'
-            options: 'pgo+lto'
-            run: true
-
-          - target_triple: 'x86_64_v3-unknown-linux-gnu'
-            py: 'cpython-3.11'
-            options: 'debug'
-            run: true
-          - target_triple: 'x86_64_v3-unknown-linux-gnu'
-            py: 'cpython-3.11'
-            options: 'pgo'
-            run: true
-          - target_triple: 'x86_64_v3-unknown-linux-gnu'
-            py: 'cpython-3.11'
-            options: 'pgo+lto'
-            run: true
-
-          - target_triple: 'x86_64_v3-unknown-linux-gnu'
-            py: 'cpython-3.12'
-            options: 'debug'
-            run: true
-          - target_triple: 'x86_64_v3-unknown-linux-gnu'
-            py: 'cpython-3.12'
-            options: 'pgo'
-            run: true
-          - target_triple: 'x86_64_v3-unknown-linux-gnu'
-            py: 'cpython-3.12'
-            options: 'pgo+lto'
-            run: true
-
-          # GitHub Actions runners don't support x86-64-v4 so we can't PGO.
-          - target_triple: 'x86_64_v4-unknown-linux-gnu'
-            py: 'cpython-3.9'
-            options: 'debug'
-          - target_triple: 'x86_64_v4-unknown-linux-gnu'
-            py: 'cpython-3.9'
-            options: 'noopt'
-          - target_triple: 'x86_64_v4-unknown-linux-gnu'
-            py: 'cpython-3.9'
-            options: 'lto'
-
-          # GitHub Actions runners don't support x86-64-v4 so we can't PGO.
-          - target_triple: 'x86_64_v4-unknown-linux-gnu'
-            py: 'cpython-3.10'
-            options: 'debug'
-          - target_triple: 'x86_64_v4-unknown-linux-gnu'
-            py: 'cpython-3.10'
-            options: 'noopt'
-          - target_triple: 'x86_64_v4-unknown-linux-gnu'
-            py: 'cpython-3.10'
-            options: 'lto'
-
-          - target_triple: 'x86_64_v4-unknown-linux-gnu'
-            py: 'cpython-3.11'
-            options: 'debug'
-          - target_triple: 'x86_64_v4-unknown-linux-gnu'
-            py: 'cpython-3.11'
-            options: 'noopt'
-          - target_triple: 'x86_64_v4-unknown-linux-gnu'
-            py: 'cpython-3.11'
-            options: 'lto'
-
-          - target_triple: 'x86_64_v4-unknown-linux-gnu'
-            py: 'cpython-3.12'
-            options: 'debug'
-          - target_triple: 'x86_64_v4-unknown-linux-gnu'
-            py: 'cpython-3.12'
-            options: 'noopt'
-          - target_triple: 'x86_64_v4-unknown-linux-gnu'
-            py: 'cpython-3.12'
-            options: 'lto'
-
-          # musl doesn't support PGO.
-
-          - target_triple: 'x86_64-unknown-linux-musl'
-            py: 'cpython-3.9'
-            options: 'debug'
-            run: true
-          - target_triple: 'x86_64-unknown-linux-musl'
-            py: 'cpython-3.9'
-            options: 'noopt'
-            run: true
-          - target_triple: 'x86_64-unknown-linux-musl'
-            py: 'cpython-3.9'
-            options: 'lto'
-            run: true
-
-          - target_triple: 'x86_64-unknown-linux-musl'
-            py: 'cpython-3.10'
-            options: 'debug'
-            run: true
-          - target_triple: 'x86_64-unknown-linux-musl'
-            py: 'cpython-3.10'
-            options: 'noopt'
-            run: true
-          - target_triple: 'x86_64-unknown-linux-musl'
-            py: 'cpython-3.10'
-            options: 'lto'
-            run: true
-
-          - target_triple: 'x86_64-unknown-linux-musl'
-            py: 'cpython-3.11'
-            options: 'debug'
-            run: true
-          - target_triple: 'x86_64-unknown-linux-musl'
-            py: 'cpython-3.11'
-            options: 'noopt'
-            run: true
-          - target_triple: 'x86_64-unknown-linux-musl'
-            py: 'cpython-3.11'
-            options: 'lto'
-            run: true
-
-          - target_triple: 'x86_64-unknown-linux-musl'
-            py: 'cpython-3.12'
-            options: 'debug'
-            run: true
-          - target_triple: 'x86_64-unknown-linux-musl'
-            py: 'cpython-3.12'
-            options: 'noopt'
-            run: true
-          - target_triple: 'x86_64-unknown-linux-musl'
-            py: 'cpython-3.12'
-            options: 'lto'
-            run: true
-
-          - target_triple: 'x86_64_v2-unknown-linux-musl'
-            py: 'cpython-3.9'
-            options: 'debug'
-            run: true
-          - target_triple: 'x86_64_v2-unknown-linux-musl'
-            py: 'cpython-3.9'
-            options: 'noopt'
-            run: true
-          - target_triple: 'x86_64_v2-unknown-linux-musl'
-            py: 'cpython-3.9'
-            options: 'lto'
-            run: true
-
-          - target_triple: 'x86_64_v2-unknown-linux-musl'
-            py: 'cpython-3.10'
-            options: 'debug'
-            run: true
-          - target_triple: 'x86_64_v2-unknown-linux-musl'
-            py: 'cpython-3.10'
-            options: 'noopt'
-            run: true
-          - target_triple: 'x86_64_v2-unknown-linux-musl'
-            py: 'cpython-3.10'
-            options: 'lto'
-            run: true
-
-          - target_triple: 'x86_64_v2-unknown-linux-musl'
-            py: 'cpython-3.11'
-            options: 'debug'
-            run: true
-          - target_triple: 'x86_64_v2-unknown-linux-musl'
-            py: 'cpython-3.11'
-            options: 'noopt'
-            run: true
-          - target_triple: 'x86_64_v2-unknown-linux-musl'
-            py: 'cpython-3.11'
-            options: 'lto'
-            run: true
-
-          - target_triple: 'x86_64_v2-unknown-linux-musl'
-            py: 'cpython-3.12'
-            options: 'debug'
-            run: true
-          - target_triple: 'x86_64_v2-unknown-linux-musl'
-            py: 'cpython-3.12'
-            options: 'noopt'
-            run: true
-          - target_triple: 'x86_64_v2-unknown-linux-musl'
-            py: 'cpython-3.12'
-            options: 'lto'
-            run: true
-
-          - target_triple: 'x86_64_v3-unknown-linux-musl'
-            py: 'cpython-3.9'
-            options: 'debug'
-            run: true
-          - target_triple: 'x86_64_v3-unknown-linux-musl'
-            py: 'cpython-3.9'
-            options: 'noopt'
-            run: true
-          - target_triple: 'x86_64_v3-unknown-linux-musl'
-            py: 'cpython-3.9'
-            options: 'lto'
-            run: true
-
-          - target_triple: 'x86_64_v3-unknown-linux-musl'
-            py: 'cpython-3.10'
-            options: 'debug'
-            run: true
-          - target_triple: 'x86_64_v3-unknown-linux-musl'
-            py: 'cpython-3.10'
-            options: 'noopt'
-            run: true
-          - target_triple: 'x86_64_v3-unknown-linux-musl'
-            py: 'cpython-3.10'
-            options: 'lto'
-            run: true
-
-          - target_triple: 'x86_64_v3-unknown-linux-musl'
-            py: 'cpython-3.11'
-            options: 'debug'
-            run: true
-          - target_triple: 'x86_64_v3-unknown-linux-musl'
-            py: 'cpython-3.11'
-            options: 'noopt'
-            run: true
-          - target_triple: 'x86_64_v3-unknown-linux-musl'
-            py: 'cpython-3.11'
-            options: 'lto'
-            run: true
-
-          - target_triple: 'x86_64_v3-unknown-linux-musl'
-            py: 'cpython-3.12'
-            options: 'debug'
-            run: true
-          - target_triple: 'x86_64_v3-unknown-linux-musl'
-            py: 'cpython-3.12'
-            options: 'noopt'
-            run: true
-          - target_triple: 'x86_64_v3-unknown-linux-musl'
-            py: 'cpython-3.12'
-            options: 'lto'
-            run: true
-
-          - target_triple: 'x86_64_v4-unknown-linux-musl'
-            py: 'cpython-3.9'
-            options: 'debug'
-          - target_triple: 'x86_64_v4-unknown-linux-musl'
-            py: 'cpython-3.9'
-            options: 'noopt'
-          - target_triple: 'x86_64_v4-unknown-linux-musl'
-            py: 'cpython-3.9'
-            options: 'lto'
-
-          - target_triple: 'x86_64_v4-unknown-linux-musl'
-            py: 'cpython-3.10'
-            options: 'debug'
-          - target_triple: 'x86_64_v4-unknown-linux-musl'
-            py: 'cpython-3.10'
-            options: 'noopt'
-          - target_triple: 'x86_64_v4-unknown-linux-musl'
-            py: 'cpython-3.10'
-            options: 'lto'
-
-          - target_triple: 'x86_64_v4-unknown-linux-musl'
-            py: 'cpython-3.11'
-            options: 'debug'
-          - target_triple: 'x86_64_v4-unknown-linux-musl'
-            py: 'cpython-3.11'
-            options: 'noopt'
-          - target_triple: 'x86_64_v4-unknown-linux-musl'
-            py: 'cpython-3.11'
-            options: 'lto'
-
-          - target_triple: 'x86_64_v4-unknown-linux-musl'
-            py: 'cpython-3.12'
-            options: 'debug'
-          - target_triple: 'x86_64_v4-unknown-linux-musl'
-            py: 'cpython-3.12'
-            options: 'noopt'
-          - target_triple: 'x86_64_v4-unknown-linux-musl'
-            py: 'cpython-3.12'
-            options: 'lto'
-
-    needs:
-      - pythonbuild
-      - image
-    runs-on: depot-ubuntu-22.04
+  generate-matrix:
+    runs-on: ubuntu-latest
+    outputs:
+      matrix: ${{ steps.set-matrix.outputs.matrix }}
     steps:
       - uses: actions/checkout@v4
-        with:
-          fetch-depth: 0
-
-      - name: Install Python
-        uses: actions/setup-python@v5
-        with:
-          python-version: '3.11'
+      
+      - name: Set up Python
+        uses: astral-sh/setup-uv@v4
 
-      - name: Download pythonbuild
-        uses: actions/download-artifact@v4
-        with:
-          name: pythonbuild
-          path: build
-
-      - name: Download images
-        uses: actions/download-artifact@v4
-        with:
-          pattern: image-*
-          path: build
-          merge-multiple: true
-
-      - name: Load Docker Images
+      - name: Get pull request labels
+        id: get-labels
         run: |
-          for f in build/image-*.tar.zst; do
-            echo "decompressing $f"
-            zstd -d --rm ${f}
-          done
-
-          for f in build/image-*.tar; do
-            echo "loading $f"
-            docker load --input $f
-          done
-
-      - name: Build
+          # Convert GitHub labels array to comma-separated string
+          LABELS=$(echo '${{ toJson(github.event.pull_request.labels.*.name) }}' | jq -r 'join(",")')
+          echo "labels=$LABELS" >> $GITHUB_OUTPUT
+          
+      - name: Generate build matrix
+        id: set-matrix
         run: |
-          # Do empty target so all generated files are touched.
-          ./build-linux.py --make-target empty
-
-          # Touch mtimes of all images so they are newer than autogenerated files above.
-          touch build/image-*
-
-          ./build-linux.py --target-triple ${{ matrix.build.target_triple }} --python ${{ matrix.build.py }} --options ${{ matrix.build.options }}
-
-      - name: Validate Distribution
-        run: |
-          chmod +x build/pythonbuild
-
-          if [ -n "${{matrix.build.run}}" ]; then
-            EXTRA_ARGS="--run"
-          fi
-
-          build/pythonbuild validate-distribution ${EXTRA_ARGS} dist/*.tar.zst
-
-      - name: Upload Distribution
-        uses: actions/upload-artifact@v4
-        with:
-          name: ${{ matrix.build.py }}-${{ matrix.build.target_triple }}-${{ matrix.build.options }}
-          path: dist/*
-
-
-
-  # GitHub enforces a limit of 256 entries per matrix, which we exceeded above
-  # so the CPython 3.13 jobs are split out
-  build-313:
-    strategy:
-      fail-fast: false
-      matrix:
-        build:
-
-          - target_triple: 'aarch64-unknown-linux-gnu'
-            py: 'cpython-3.13'
-            options: 'debug'
-          - target_triple: 'aarch64-unknown-linux-gnu'
-            py: 'cpython-3.13'
-            options: 'noopt'
-          - target_triple: 'aarch64-unknown-linux-gnu'
-            py: 'cpython-3.13'
-            options: 'lto'
-          - target_triple: 'aarch64-unknown-linux-gnu'
-            py: 'cpython-3.13'
-            options: 'freethreaded+debug'
-          - target_triple: 'aarch64-unknown-linux-gnu'
-            py: 'cpython-3.13'
-            options: 'freethreaded+noopt'
-          - target_triple: 'aarch64-unknown-linux-gnu'
-            py: 'cpython-3.13'
-            options: 'freethreaded+lto'
-
-          - target_triple: 'armv7-unknown-linux-gnueabi'
-            py: 'cpython-3.13'
-            options: 'debug'
-          - target_triple: 'armv7-unknown-linux-gnueabi'
-            py: 'cpython-3.13'
-            options: 'noopt'
-          - target_triple: 'armv7-unknown-linux-gnueabi'
-            py: 'cpython-3.13'
-            options: 'lto'
-          - target_triple: 'armv7-unknown-linux-gnueabi'
-            py: 'cpython-3.13'
-            options: 'freethreaded+debug'
-          - target_triple: 'armv7-unknown-linux-gnueabi'
-            py: 'cpython-3.13'
-            options: 'freethreaded+noopt'
-          - target_triple: 'armv7-unknown-linux-gnueabi'
-            py: 'cpython-3.13'
-            options: 'freethreaded+lto'
-
-          - target_triple: 'armv7-unknown-linux-gnueabihf'
-            py: 'cpython-3.13'
-            options: 'debug'
-          - target_triple: 'armv7-unknown-linux-gnueabihf'
-            py: 'cpython-3.13'
-            options: 'noopt'
-          - target_triple: 'armv7-unknown-linux-gnueabihf'
-            py: 'cpython-3.13'
-            options: 'lto'
-          - target_triple: 'armv7-unknown-linux-gnueabihf'
-            py: 'cpython-3.13'
-            options: 'freethreaded+debug'
-          - target_triple: 'armv7-unknown-linux-gnueabihf'
-            py: 'cpython-3.13'
-            options: 'freethreaded+noopt'
-          - target_triple: 'armv7-unknown-linux-gnueabihf'
-            py: 'cpython-3.13'
-            options: 'freethreaded+lto'
-
-          - target_triple: 's390x-unknown-linux-gnu'
-            py: 'cpython-3.13'
-            options: 'debug'
-          - target_triple: 's390x-unknown-linux-gnu'
-            py: 'cpython-3.13'
-            options: 'noopt'
-          - target_triple: 's390x-unknown-linux-gnu'
-            py: 'cpython-3.13'
-            options: 'lto'
-          - target_triple: 's390x-unknown-linux-gnu'
-            py: 'cpython-3.13'
-            options: 'freethreaded+debug'
-          - target_triple: 's390x-unknown-linux-gnu'
-            py: 'cpython-3.13'
-            options: 'freethreaded+noopt'
-          - target_triple: 's390x-unknown-linux-gnu'
-            py: 'cpython-3.13'
-            options: 'freethreaded+lto'
-
-          - target_triple: 'ppc64le-unknown-linux-gnu'
-            py: 'cpython-3.13'
-            options: 'debug'
-          - target_triple: 'ppc64le-unknown-linux-gnu'
-            py: 'cpython-3.13'
-            options: 'noopt'
-          - target_triple: 'ppc64le-unknown-linux-gnu'
-            py: 'cpython-3.13'
-            options: 'lto'
-          - target_triple: 'ppc64le-unknown-linux-gnu'
-            py: 'cpython-3.13'
-            options: 'freethreaded+debug'
-          - target_triple: 'ppc64le-unknown-linux-gnu'
-            py: 'cpython-3.13'
-            options: 'freethreaded+noopt'
-          - target_triple: 'ppc64le-unknown-linux-gnu'
-            py: 'cpython-3.13'
-            options: 'freethreaded+lto'
-
-          - target_triple: 'x86_64-unknown-linux-gnu'
-            py: 'cpython-3.13'
-            options: 'debug'
-            run: true
-          - target_triple: 'x86_64-unknown-linux-gnu'
-            py: 'cpython-3.13'
-            options: 'pgo'
-            run: true
-          - target_triple: 'x86_64-unknown-linux-gnu'
-            py: 'cpython-3.13'
-            options: 'pgo+lto'
-            run: true
-          - target_triple: 'x86_64-unknown-linux-gnu'
-            py: 'cpython-3.13'
-            options: 'freethreaded+debug'
-            run: true
-          - target_triple: 'x86_64-unknown-linux-gnu'
-            py: 'cpython-3.13'
-            options: 'freethreaded+pgo'
-            run: true
-          - target_triple: 'x86_64-unknown-linux-gnu'
-            py: 'cpython-3.13'
-            options: 'freethreaded+pgo+lto'
-            run: true
-
-          - target_triple: 'x86_64_v2-unknown-linux-gnu'
-            py: 'cpython-3.13'
-            options: 'debug'
-            run: true
-          - target_triple: 'x86_64_v2-unknown-linux-gnu'
-            py: 'cpython-3.13'
-            options: 'pgo'
-            run: true
-          - target_triple: 'x86_64_v2-unknown-linux-gnu'
-            py: 'cpython-3.13'
-            options: 'pgo+lto'
-            run: true
-          - target_triple: 'x86_64_v2-unknown-linux-gnu'
-            py: 'cpython-3.13'
-            options: 'freethreaded+debug'
-            run: true
-          - target_triple: 'x86_64_v2-unknown-linux-gnu'
-            py: 'cpython-3.13'
-            options: 'freethreaded+pgo'
-            run: true
-          - target_triple: 'x86_64_v2-unknown-linux-gnu'
-            py: 'cpython-3.13'
-            options: 'freethreaded+pgo+lto'
-            run: true
-
-          - target_triple: 'x86_64_v3-unknown-linux-gnu'
-            py: 'cpython-3.13'
-            options: 'debug'
-            run: true
-          - target_triple: 'x86_64_v3-unknown-linux-gnu'
-            py: 'cpython-3.13'
-            options: 'pgo'
-            run: true
-          - target_triple: 'x86_64_v3-unknown-linux-gnu'
-            py: 'cpython-3.13'
-            options: 'pgo+lto'
-            run: true
-          - target_triple: 'x86_64_v3-unknown-linux-gnu'
-            py: 'cpython-3.13'
-            options: 'freethreaded+debug'
-            run: true
-          - target_triple: 'x86_64_v3-unknown-linux-gnu'
-            py: 'cpython-3.13'
-            options: 'freethreaded+pgo'
-            run: true
-          - target_triple: 'x86_64_v3-unknown-linux-gnu'
-            py: 'cpython-3.13'
-            options: 'freethreaded+pgo+lto'
-            run: true
-
-          - target_triple: 'x86_64-unknown-linux-musl'
-            py: 'cpython-3.13'
-            options: 'debug'
-            run: true
-          - target_triple: 'x86_64-unknown-linux-musl'
-            py: 'cpython-3.13'
-            options: 'noopt'
-            run: true
-          - target_triple: 'x86_64-unknown-linux-musl'
-            py: 'cpython-3.13'
-            options: 'lto'
-            run: true
-          # TODO: Free-threaded musl builds are blocked by upgrading LLVM 14 -> 18.
-          # - target_triple: 'x86_64-unknown-linux-musl'
-          #   py: 'cpython-3.13'
-          #   options: 'freethreaded+debug'
-          #   run: true
-          # - target_triple: 'x86_64-unknown-linux-musl'
-          #   py: 'cpython-3.13'
-          #   options: 'freethreaded+noopt'
-          #   run: true
-          # - target_triple: 'x86_64-unknown-linux-musl'
-          #   py: 'cpython-3.13'
-          #   options: 'freethreaded+lto'
-          #   run: true
-
-          - target_triple: 'x86_64_v2-unknown-linux-musl'
-            py: 'cpython-3.13'
-            options: 'debug'
-            run: true
-          - target_triple: 'x86_64_v2-unknown-linux-musl'
-            py: 'cpython-3.13'
-            options: 'noopt'
-            run: true
-          - target_triple: 'x86_64_v2-unknown-linux-musl'
-            py: 'cpython-3.13'
-            options: 'lto'
-            run: true
-          # TODO: Free-threaded musl builds are blocked by upgrading LLVM 14 -> 18.
-          # - target_triple: 'x86_64_v2-unknown-linux-musl'
-          #   py: 'cpython-3.13'
-          #   options: 'freethreaded+debug'
-          #   run: true
-          # - target_triple: 'x86_64_v2-unknown-linux-musl'
-          #   py: 'cpython-3.13'
-          #   options: 'freethreaded+noopt'
-          #   run: true
-          # - target_triple: 'x86_64_v2-unknown-linux-musl'
-          #   py: 'cpython-3.13'
-          #   options: 'freethreaded+lto'
-          #   run: true
-
-          - target_triple: 'x86_64_v4-unknown-linux-gnu'
-            py: 'cpython-3.13'
-            options: 'debug'
-          - target_triple: 'x86_64_v4-unknown-linux-gnu'
-            py: 'cpython-3.13'
-            options: 'noopt'
-          - target_triple: 'x86_64_v4-unknown-linux-gnu'
-            py: 'cpython-3.13'
-            options: 'lto'
-          - target_triple: 'x86_64_v4-unknown-linux-gnu'
-            py: 'cpython-3.13'
-            options: 'freethreaded+debug'
-          - target_triple: 'x86_64_v4-unknown-linux-gnu'
-            py: 'cpython-3.13'
-            options: 'freethreaded+noopt'
-          - target_triple: 'x86_64_v4-unknown-linux-gnu'
-            py: 'cpython-3.13'
-            options: 'freethreaded+lto'
-
-          - target_triple: 'x86_64_v3-unknown-linux-musl'
-            py: 'cpython-3.13'
-            options: 'debug'
-            run: true
-          - target_triple: 'x86_64_v3-unknown-linux-musl'
-            py: 'cpython-3.13'
-            options: 'noopt'
-            run: true
-          - target_triple: 'x86_64_v3-unknown-linux-musl'
-            py: 'cpython-3.13'
-            options: 'lto'
-            run: true
-          # TODO: Free-threaded musl builds are blocked by upgrading LLVM 14 -> 18.
-          # - target_triple: 'x86_64_v3-unknown-linux-musl'
-          #   py: 'cpython-3.13'
-          #   options: 'freethreaded+debug'
-          #   run: true
-          # - target_triple: 'x86_64_v3-unknown-linux-musl'
-          #   py: 'cpython-3.13'
-          #   options: 'freethreaded+noopt'
-          #   run: true
-          # - target_triple: 'x86_64_v3-unknown-linux-musl'
-          #   py: 'cpython-3.13'
-          #   options: 'freethreaded+lto'
-          #   run: true
-
-          - target_triple: 'x86_64_v4-unknown-linux-musl'
-            py: 'cpython-3.13'
-            options: 'debug'
-          - target_triple: 'x86_64_v4-unknown-linux-musl'
-            py: 'cpython-3.13'
-            options: 'noopt'
-          - target_triple: 'x86_64_v4-unknown-linux-musl'
-            py: 'cpython-3.13'
-            options: 'lto'
-          # TODO: Free-threaded musl builds are blocked by upgrading LLVM 14 -> 18.
-          # - target_triple: 'x86_64_v4-unknown-linux-musl'
-          #   py: 'cpython-3.13'
-          #   options: 'freethreaded+debug'
-          # - target_triple: 'x86_64_v4-unknown-linux-musl'
-          #   py: 'cpython-3.13'
-          #   options: 'freethreaded+noopt'
-          # - target_triple: 'x86_64_v4-unknown-linux-musl'
-          #   py: 'cpython-3.13'
-          #   options: 'freethreaded+lto'
-
+          uv run ci-matrix.py --platform linux --labels "${{ steps.get-labels.outputs.labels }}" > matrix.json && echo "matrix=$(cat matrix.json)" >> $GITHUB_OUTPUT
+          # Display the matrix for debugging too
+          cat matrix.json | jq
+        
+  build:
     needs:
+      - generate-matrix
       - pythonbuild
       - image
     runs-on: depot-ubuntu-22.04
-
-    # The above should remain an exact duplicate of the `build` job
+    strategy:
+      matrix: ${{ fromJson(needs.generate-matrix.outputs.matrix) }}
+      fail-fast: false
+    name: ${{ matrix.target_triple }} / ${{ matrix.python }} / ${{ matrix.build_options }}
     steps:
       - uses: actions/checkout@v4
         with:
@@ -1133,13 +192,13 @@ jobs:
           # Touch mtimes of all images so they are newer than autogenerated files above.
           touch build/image-*
 
-          ./build-linux.py --target-triple ${{ matrix.build.target_triple }} --python ${{ matrix.build.py }} --options ${{ matrix.build.options }}
+          ./build-linux.py --target-triple ${{ matrix.target_triple }} --python cpython-${{ matrix.python }} --options ${{ matrix.build_options }}
 
       - name: Validate Distribution
         run: |
           chmod +x build/pythonbuild
 
-          if [ -n "${{matrix.build.run}}" ]; then
+          if [ -n "${{ matrix.run }}" ]; then
             EXTRA_ARGS="--run"
           fi
 
@@ -1148,5 +207,5 @@ jobs:
       - name: Upload Distribution
         uses: actions/upload-artifact@v4
         with:
-          name: ${{ matrix.build.py }}-${{ matrix.build.target_triple }}-${{ matrix.build.options }}
+          name: cpython-${{ matrix.python }}-${{ matrix.target_triple }}-${{ matrix.build_options }}
           path: dist/*
diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml
index 69999a5c..e92d2b55 100644
--- a/.github/workflows/windows.yml
+++ b/.github/workflows/windows.yml
@@ -40,32 +40,39 @@ jobs:
           name: pythonbuild
           path: target/release/pythonbuild.exe
 
+  generate-matrix:
+    runs-on: ubuntu-latest
+    outputs:
+      matrix: ${{ steps.set-matrix.outputs.matrix }}
+    steps:
+      - uses: actions/checkout@v4
+      
+      - name: Set up Python
+        uses: astral-sh/setup-uv@v4
+
+      - name: Get pull request labels
+        id: get-labels
+        run: |
+          # Convert GitHub labels array to comma-separated string
+          LABELS=$(echo '${{ toJson(github.event.pull_request.labels.*.name) }}' | jq -r 'join(",")')
+          echo "labels=$LABELS" >> $GITHUB_OUTPUT
+          
+      - name: Generate build matrix
+        id: set-matrix
+        run: |
+          uv run ci-matrix.py --platform windows --labels "${{ steps.get-labels.outputs.labels }}" > matrix.json && echo "matrix=$(cat matrix.json)" >> $GITHUB_OUTPUT
+          # Display the matrix for debugging too
+          cat matrix.json | jq
+      
   build:
+    needs:
+      - generate-matrix
+      - pythonbuild
+    runs-on: windows-latest
     strategy:
+      matrix: ${{ fromJson(needs.generate-matrix.outputs.matrix) }}
       fail-fast: false
-      matrix:
-        py:
-          - 'cpython-3.9'
-          - 'cpython-3.10'
-          - 'cpython-3.11'
-          - 'cpython-3.12'
-          - 'cpython-3.13'
-        vcvars:
-          - 'vcvars32.bat'
-          - 'vcvars64.bat'
-        options:
-          - 'pgo'
-
-        include:
-          - py: 'cpython-3.13'
-            vcvars: 'vcvars32.bat'
-            options: 'freethreaded+pgo'
-          - py: 'cpython-3.13'
-            vcvars: 'vcvars64.bat'
-            options: 'freethreaded+pgo'
-
-    needs: pythonbuild
-    runs-on: windows-2022
+    name: ${{ matrix.target_triple }} / ${{ matrix.python }} / ${{ matrix.build_option }}
     steps:
       - uses: actions/checkout@v4
         with:
@@ -96,7 +103,7 @@ jobs:
         shell: cmd
         run: |
           call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\${{ matrix.vcvars }}"
-          py.exe -3.9 build-windows.py --python ${{ matrix.py }} --sh c:\cygwin\bin\sh.exe --options ${{ matrix.options }}
+          py.exe -3.9 build-windows.py --python cpython-${{ matrix.python }} --sh c:\cygwin\bin\sh.exe --options ${{ matrix.build_options }}
 
       - name: Validate Distribution
         run: |
@@ -106,5 +113,5 @@ jobs:
       - name: Upload Distributions
         uses: actions/upload-artifact@v4
         with:
-          name: ${{ matrix.py }}-${{ matrix.vcvars }}-${{ matrix.options }}
+          name: cpython-${{ matrix.python }}-${{ matrix.vcvars }}-${{ matrix.build_options }}
           path: dist/*
diff --git a/ci-matrix.py b/ci-matrix.py
new file mode 100644
index 00000000..fa9c7c20
--- /dev/null
+++ b/ci-matrix.py
@@ -0,0 +1,191 @@
+# /// script
+# requires-python = ">=3.13"
+# dependencies = [
+#     "packaging",
+#     "pyyaml",
+# ]
+# ///
+
+import argparse
+import json
+from typing import Any, Dict, List, Optional, Set
+
+import yaml
+from packaging.version import Version
+
+CI_TARGETS_YAML = "ci-targets.yaml"
+
+
+def meets_conditional_version(version: str, min_version: str) -> bool:
+    return Version(version) >= Version(min_version)
+
+
+def parse_labels(labels: Optional[str]) -> Dict[str, Set[str]]:
+    """Parse labels into a dict of category filters."""
+    if not labels:
+        return {}
+
+    result: Dict[str, Set[str]] = {
+        "platform": set(),
+        "python": set(),
+        "build": set(),
+        "arch": set(),
+        "libc": set(),
+    }
+
+    for label in labels.split(","):
+        label = label.strip()
+        if not label or ":" not in label:
+            continue
+        category, value = label.split(":", 1)
+        if category in result:
+            result[category].add(value)
+
+    return result
+
+
+def should_include_entry(entry: Dict[str, str], filters: Dict[str, Set[str]]) -> bool:
+    """Check if an entry satisfies the label filters."""
+    if filters.get("platform") and entry["platform"] not in filters["platform"]:
+        return False
+
+    if filters.get("python") and entry["python"] not in filters["python"]:
+        return False
+
+    if filters.get("arch") and entry["arch"] not in filters["arch"]:
+        return False
+
+    if filters.get("libc") and entry.get("libc") not in filters["libc"]:
+        return False
+
+    if filters.get("build"):
+        build_options = set(entry.get("build_options", "").split("+"))
+        if not all(f in build_options for f in filters["build"]):
+            return False
+
+    return True
+
+
+def generate_matrix_entries(
+    config: Dict[str, Any],
+    platform_filter: Optional[str] = None,
+    label_filters: Optional[Dict[str, Set[str]]] = None,
+) -> List[Dict[str, str]]:
+    matrix_entries = []
+
+    for platform, platform_config in config.items():
+        if platform_filter and platform != platform_filter:
+            continue
+
+        for target_triple, target_config in platform_config.items():
+            add_matrix_entries_for_config(
+                matrix_entries,
+                target_triple,
+                target_config,
+                platform,
+            )
+
+    # Apply label filters if present
+    if label_filters:
+        matrix_entries = [
+            entry
+            for entry in matrix_entries
+            if should_include_entry(entry, label_filters)
+        ]
+
+    return matrix_entries
+
+
+def add_matrix_entries_for_config(
+    matrix_entries: List[Dict[str, str]],
+    target_triple: str,
+    config: Dict[str, Any],
+    platform: str,
+) -> None:
+    python_versions = config["python_versions"]
+    build_options = config["build_options"]
+    arch = config["arch"]
+
+    # Create base entry that will be used for all variants
+    base_entry = {
+        "arch": arch,
+        "target_triple": target_triple,
+        "platform": platform,
+    }
+
+    # Add optional fields if they exist
+    if "arch_variant" in config:
+        base_entry["arch_variant"] = config["arch_variant"]
+    if "libc" in config:
+        base_entry["libc"] = config["libc"]
+    if "vcvars" in config:
+        base_entry["vcvars"] = config["vcvars"]
+    if "run" in config:
+        base_entry["run"] = str(config["run"]).lower()
+
+    # Process regular build options
+    for python_version in python_versions:
+        for build_option in build_options:
+            entry = base_entry.copy()
+            entry.update(
+                {
+                    "python": python_version,
+                    "build_options": build_option,
+                }
+            )
+            matrix_entries.append(entry)
+
+    # Process conditional build options (e.g., freethreaded)
+    for conditional in config.get("build_options_conditional", []):
+        min_version = conditional["minimum-python-version"]
+        for python_version in python_versions:
+            if not meets_conditional_version(python_version, min_version):
+                continue
+
+            for build_option in conditional["options"]:
+                entry = base_entry.copy()
+                entry.update(
+                    {
+                        "python": python_version,
+                        "build_options": build_option,
+                    }
+                )
+                matrix_entries.append(entry)
+
+
+def parse_args() -> argparse.Namespace:
+    parser = argparse.ArgumentParser(
+        description="Generate a JSON matrix for building distributions in CI"
+    )
+    parser.add_argument(
+        "--platform",
+        choices=["darwin", "linux", "windows"],
+        help="Filter matrix entries by platform",
+    )
+    parser.add_argument(
+        "--labels",
+        help="Comma-separated list of labels to filter by (e.g., 'platform:darwin,python:3.13,build:debug'), all must match.",
+    )
+    return parser.parse_args()
+
+
+def main() -> None:
+    args = parse_args()
+    labels = parse_labels(args.labels)
+
+    with open(CI_TARGETS_YAML, "r") as f:
+        config = yaml.safe_load(f)
+
+    matrix = {
+        "include": generate_matrix_entries(
+            config,
+            args.platform,
+            labels,
+        )
+    }
+
+    print(json.dumps(matrix))
+
+
+if __name__ == "__main__":
+    main()
diff --git a/ci-targets.yaml b/ci-targets.yaml
new file mode 100644
index 00000000..17d2d0e6
--- /dev/null
+++ b/ci-targets.yaml
@@ -0,0 +1,322 @@
+# Describes the targets that the CI system will build and test on.
+
+darwin:
+  aarch64-apple-darwin:
+    arch: aarch64
+    python_versions:
+      - "3.9"
+      - "3.10"
+      - "3.11"
+      - "3.12"
+      - "3.13"
+    build_options:
+      - debug
+      - pgo
+      - pgo+lto
+    build_options_conditional:
+      - options:
+          - freethreaded+debug
+          - freethreaded+pgo
+          - freethreaded+pgo+lto
+        minimum-python-version: "3.13"
+
+  x86_64-apple-darwin:
+    arch: x86_64
+    python_versions:
+      - "3.9"
+      - "3.10"
+      - "3.11"
+      - "3.12"
+      - "3.13"
+    build_options:
+      - debug
+      - pgo
+      - pgo+lto
+    build_options_conditional:
+      - options:
+          - freethreaded+debug
+          - freethreaded+pgo
+          - freethreaded+pgo+lto
+        minimum-python-version: "3.13"
+
+linux:
+  aarch64-unknown-linux-gnu:
+    arch: aarch64
+    libc: gnu
+    python_versions:
+      - "3.9"
+      - "3.10"
+      - "3.11"
+      - "3.12"
+      - "3.13"
+    build_options:
+      - debug
+      - noopt
+      - lto
+    build_options_conditional:
+      - options:
+          - freethreaded+debug
+          - freethreaded+noopt
+          - freethreaded+lto
+        minimum-python-version: "3.13"
+
+  armv7-unknown-linux-gnueabi:
+    arch: armv7
+    libc: gnu
+    python_versions:
+      - "3.9"
+      - "3.10"
+      - "3.11"
+      - "3.12"
+      - "3.13"
+    build_options:
+      - debug
+      - noopt
+      - lto
+    build_options_conditional:
+      - options:
+          - freethreaded+debug
+          - freethreaded+noopt
+          - freethreaded+lto
+        minimum-python-version: "3.13"
+
+  armv7-unknown-linux-gnueabihf:
+    arch: armv7
+    libc: gnu
+    python_versions:
+      - "3.9"
+      - "3.10"
+      - "3.11"
+      - "3.12"
+      - "3.13"
+    build_options:
+      - debug
+      - noopt
+      - lto
+    build_options_conditional:
+      - options:
+          - freethreaded+debug
+          - freethreaded+noopt
+          - freethreaded+lto
+        minimum-python-version: "3.13"
+
+  s390x-unknown-linux-gnu:
+    arch: s390x
+    libc: gnu
+    python_versions:
+      - "3.9"
+      - "3.10"
+      - "3.11"
+      - "3.12"
+      - "3.13"
+    build_options:
+      - debug
+      - noopt
+      - lto
+    build_options_conditional:
+      - options:
+          - freethreaded+debug
+          - freethreaded+noopt
+          - freethreaded+lto
+        minimum-python-version: "3.13"
+
+  ppc64le-unknown-linux-gnu:
+    arch: ppc64le
+    libc: gnu
+    python_versions:
+      - "3.9"
+      - "3.10"
+      - "3.11"
+      - "3.12"
+      - "3.13"
+    build_options:
+      - debug
+      - noopt
+      - lto
+    build_options_conditional:
+      - options:
+          - freethreaded+debug
+          - freethreaded+noopt
+          - freethreaded+lto
+        minimum-python-version: "3.13"
+
+  x86_64-unknown-linux-gnu:
+    arch: x86_64
+    libc: gnu
+    python_versions:
+      - "3.9"
+      - "3.10"
+      - "3.11"
+      - "3.12"
+      - "3.13"
+    build_options:
+      - debug
+      - pgo
+      - pgo+lto
+    build_options_conditional:
+      - options:
+          - freethreaded+debug
+          - freethreaded+pgo
+          - freethreaded+pgo+lto
+        minimum-python-version: "3.13"
+    run: true
+
+  x86_64_v2-unknown-linux-gnu:
+    arch: x86_64
+    arch_variant: v2
+    libc: gnu
+    python_versions:
+      - "3.9"
+      - "3.10"
+      - "3.11"
+      - "3.12"
+      - "3.13"
+    build_options:
+      - debug
+      - pgo
+      - pgo+lto
+    build_options_conditional:
+      - options:
+          - freethreaded+debug
+          - freethreaded+pgo
+          - freethreaded+pgo+lto
+        minimum-python-version: "3.13"
+    run: true
+
+  x86_64_v3-unknown-linux-gnu:
+    arch: x86_64
+    arch_variant: v3
+    libc: gnu
+    python_versions:
+      - "3.9"
+      - "3.10"
+      - "3.11"
+      - "3.12"
+      - "3.13"
+    build_options:
+      - debug
+      - pgo
+      - pgo+lto
+    build_options_conditional:
+      - options:
+          - freethreaded+debug
+          - freethreaded+pgo
+          - freethreaded+pgo+lto
+        minimum-python-version: "3.13"
+    run: true
+
+  x86_64_v4-unknown-linux-gnu:
+    arch: x86_64
+    arch_variant: v4
+    libc: gnu
+    python_versions:
+      - "3.9"
+      - "3.10"
+      - "3.11"
+      - "3.12"
+      - "3.13"
+    build_options:
+      - debug
+      - noopt
+      - lto
+    build_options_conditional:
+      - options:
+          - freethreaded+debug
+          - freethreaded+noopt
+          - freethreaded+lto
+        minimum-python-version: "3.13"
+
+  x86_64-unknown-linux-musl:
+    arch: x86_64
+    libc: musl
+    python_versions:
+      - "3.9"
+      - "3.10"
+      - "3.11"
+      - "3.12"
+      - "3.13"
+    build_options:
+      - debug
+      - noopt
+      - lto
+    run: true
+
+  x86_64_v2-unknown-linux-musl:
+    arch: x86_64
+    arch_variant: v2
+    libc: musl
+    python_versions:
+      - "3.9"
+      - "3.10"
+      - "3.11"
+      - "3.12"
+      - "3.13"
+    build_options:
+      - debug
+      - noopt
+      - lto
+    run: true
+
+  x86_64_v3-unknown-linux-musl:
+    arch: x86_64
+    arch_variant: v3
+    libc: musl
+    python_versions:
+      - "3.9"
+      - "3.10"
+      - "3.11"
+      - "3.12"
+      - "3.13"
+    build_options:
+      - debug
+      - noopt
+      - lto
+    run: true
+
+  x86_64_v4-unknown-linux-musl:
+    arch: x86_64
+    arch_variant: v4
+    libc: musl
+    python_versions:
+      - "3.9"
+      - "3.10"
+      - "3.11"
+      - "3.12"
+      - "3.13"
+    build_options:
+      - debug
+      - noopt
+      - lto
+
+windows:
+  i686-pc-windows-msvc:
+    arch: x86
+    vcvars: vcvars32.bat
+    python_versions:
+      - "3.9"
+      - "3.10"
+      - "3.11"
+      - "3.12"
+      - "3.13"
+    build_options:
+      - pgo
+    build_options_conditional:
+      - options:
+          - freethreaded+pgo
+        minimum-python-version: "3.13"
+
+  x86_64-pc-windows-msvc:
+    arch: x86_64
+    vcvars: vcvars64.bat
+    python_versions:
+      - "3.9"
+      - "3.10"
+      - "3.11"
+      - "3.12"
+      - "3.13"
+    build_options:
+      - pgo
+    build_options_conditional:
+      - options:
+          - freethreaded+pgo
+        minimum-python-version: "3.13"