Skip to content

Commit 48851de

Browse files
committed
Generate the Docker CI matrix
1 parent 240b639 commit 48851de

File tree

4 files changed

+127
-59
lines changed

4 files changed

+127
-59
lines changed

.github/workflows/linux.yml

Lines changed: 20 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -54,14 +54,9 @@ jobs:
5454
- generate-matrix
5555
strategy:
5656
fail-fast: false
57-
matrix:
58-
image:
59-
- build
60-
- build.cross
61-
- build.cross-riscv64
62-
- gcc
63-
name: ${{ matrix.image }}
64-
runs-on: depot-ubuntu-22.04
57+
matrix: ${{ fromJson(needs.generate-matrix.outputs.docker-build-matrix) }}
58+
name: ${{ matrix.name }}
59+
runs-on: ${{ matrix.runner }}
6560
permissions:
6661
packages: write
6762
steps:
@@ -95,37 +90,38 @@ jobs:
9590
uses: docker/build-push-action@v5
9691
with:
9792
context: .
98-
file: build/${{ matrix.image }}.Dockerfile
93+
file: build/${{ matrix.name }}.Dockerfile
9994
labels: org.opencontainers.image.source=https://github.com/${{ env.REPO_NAME }}
10095
# Cache from/to the current branch of the current repo as the primary cache key.
10196
# Cache from the default branch of the current repo so branches can have cache hits.
10297
# Cache from the default branch of the canonical repo so forks can have cache hits.
10398
# Ignore errors on cache writes so CI of forks works without a valid GHCR config.
10499
cache-from: |
105-
type=registry,ref=ghcr.io/${{ env.REPO_NAME }}:${{ matrix.image }}-${{ env.GIT_REF_NAME }}
106-
type=registry,ref=ghcr.io/${{ env.REPO_NAME }}:${{ matrix.image }}-main
107-
type=registry,ref=ghcr.io/astral-sh/python-build-standalone:${{ matrix.image }}-main
100+
type=registry,ref=ghcr.io/${{ env.REPO_NAME }}:${{ matrix.name }}-${{ env.GIT_REF_NAME }}
101+
type=registry,ref=ghcr.io/${{ env.REPO_NAME }}:${{ matrix.name }}-main
102+
type=registry,ref=ghcr.io/astral-sh/python-build-standalone:${{ matrix.name }}-main
108103
cache-to: |
109-
type=registry,ref=ghcr.io/${{ env.REPO_NAME }}:${{ matrix.image }}-${{ env.GIT_REF_NAME }},ignore-error=true
104+
type=registry,ref=ghcr.io/${{ env.REPO_NAME }}:${{ matrix.name }}-${{ env.GIT_REF_NAME }},ignore-error=true
110105
outputs: |
111-
type=docker,dest=build/image-${{ matrix.image }}.tar
106+
type=docker,dest=build/image-${{ matrix.name }}.tar
112107
113108
- name: Compress Image
114109
run: |
115-
echo ${{ steps.build-image.outputs.imageid }} > build/image-${{ matrix.image }}
110+
echo ${{ steps.build-image.outputs.imageid }} > build/image-${{ matrix.name }}
116111
zstd -v -T0 -6 --rm build/image-*.tar
117112
118113
- name: Upload Docker Image
119114
uses: actions/upload-artifact@v4
120115
with:
121-
name: image-${{ matrix.image }}
116+
name: image-${{ matrix.name }}
122117
path: build/image-*
123118

124119
generate-matrix:
125120
runs-on: ubuntu-latest
126121
outputs:
127-
matrix-0: ${{ steps.set-matrix.outputs.matrix-0 }}
128-
matrix-1: ${{ steps.set-matrix.outputs.matrix-1 }}
122+
python-build-matrix-0: ${{ steps.set-matrix.outputs.python-build-matrix-0 }}
123+
python-build-matrix-1: ${{ steps.set-matrix.outputs.python-build-matrix-1 }}
124+
docker-build-matrix: ${{ steps.set-matrix.outputs.docker-build-matrix }}
129125
any_builds: ${{ steps.set-matrix.outputs.any_builds }}
130126
pythonbuild_changed: ${{ steps.check-pythonbuild.outputs.changed }}
131127
steps:
@@ -152,13 +148,14 @@ jobs:
152148
--max-shards 2 \
153149
> matrix.json
154150
155-
echo "matrix-0=$(jq -c '.["0"]' matrix.json)" >> $GITHUB_OUTPUT
156-
echo "matrix-1=$(jq -c '.["1"]' matrix.json)" >> $GITHUB_OUTPUT
151+
echo "python-build-matrix-0=$(jq -c '."python-build"["0"]' matrix.json)" >> $GITHUB_OUTPUT
152+
echo "python-build-matrix-1=$(jq -c '."python-build"["1"]' matrix.json)" >> $GITHUB_OUTPUT
153+
echo "docker-build-matrix=$(jq -c '."docker-build"' matrix.json)" >> $GITHUB_OUTPUT
157154
158155
# Display the matrix for debugging too
159156
cat matrix.json | jq
160157
161-
if jq -e '.["0"].include | length > 0' matrix.json > /dev/null; then
158+
if jq -e '."python-build"["0"].include | length > 0' matrix.json > /dev/null; then
162159
# Build matrix has entries
163160
echo "any_builds=true" >> $GITHUB_OUTPUT
164161
else
@@ -189,7 +186,7 @@ jobs:
189186
attestations: write
190187
runs-on: ${{ matrix.runner }}
191188
strategy:
192-
matrix: ${{ fromJson(needs.generate-matrix.outputs.matrix-0) }}
189+
matrix: ${{ fromJson(needs.generate-matrix.outputs.python-build-matrix-0) }}
193190
fail-fast: false
194191
name: ${{ matrix.target_triple }} / ${{ matrix.python }} / ${{ matrix.build_options }}
195192
steps:
@@ -289,7 +286,7 @@ jobs:
289286
attestations: write
290287
runs-on: ${{ matrix.runner }}
291288
strategy:
292-
matrix: ${{ fromJson(needs.generate-matrix.outputs.matrix-1) }}
289+
matrix: ${{ fromJson(needs.generate-matrix.outputs.python-build-matrix-1) }}
293290
fail-fast: false
294291
name: ${{ matrix.target_triple }} / ${{ matrix.python }} / ${{ matrix.build_options }}
295292
steps:

.github/workflows/macos.yml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,11 +67,15 @@ jobs:
6767
- name: Generate build matrix
6868
id: set-matrix
6969
run: |
70-
uv run ci-matrix.py --platform darwin --labels '${{ steps.get-labels.outputs.labels }}' > matrix.json && echo "matrix=$(cat matrix.json)" >> $GITHUB_OUTPUT
70+
uv run ci-matrix.py --platform darwin --labels '${{ steps.get-labels.outputs.labels }}' > matrix.json
71+
72+
# Extract python-build matrix
73+
echo "matrix=$(jq -c '."python-build"' matrix.json)" >> $GITHUB_OUTPUT
74+
7175
# Display the matrix for debugging too
7276
cat matrix.json | jq
7377
74-
if jq -e '.include | length > 0' matrix.json > /dev/null; then
78+
if jq -e '."python-build".include | length > 0' matrix.json > /dev/null; then
7579
# Build matrix has entries
7680
echo "any_builds=true" >> $GITHUB_OUTPUT
7781
else

.github/workflows/windows.yml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,11 +67,15 @@ jobs:
6767
- name: Generate build matrix
6868
id: set-matrix
6969
run: |
70-
uv run ci-matrix.py --platform windows --labels '${{ steps.get-labels.outputs.labels }}' > matrix.json && echo "matrix=$(cat matrix.json)" >> $GITHUB_OUTPUT
70+
uv run ci-matrix.py --platform windows --labels '${{ steps.get-labels.outputs.labels }}' > matrix.json
71+
72+
# Extract python-build matrix
73+
echo "matrix=$(jq -c '."python-build"' matrix.json)" >> $GITHUB_OUTPUT
74+
7175
# Display the matrix for debugging too
7276
cat matrix.json | jq
7377
74-
if jq -e '.include | length > 0' matrix.json > /dev/null; then
78+
if jq -e '."python-build".include | length > 0' matrix.json > /dev/null; then
7579
# Build matrix has entries
7680
echo "any_builds=true" >> $GITHUB_OUTPUT
7781
else

ci-matrix.py

Lines changed: 95 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,14 @@
1919
CI_EXTRA_SKIP_LABELS = ["documentation"]
2020
CI_MATRIX_SIZE_LIMIT = 256 # The maximum size of a matrix in GitHub Actions
2121

22+
# Docker images for building toolchains and dependencies
23+
DOCKER_BUILD_IMAGES = [
24+
{"name": "build", "platform": "linux64"},
25+
{"name": "build.cross", "platform": "linux64"},
26+
{"name": "build.cross-riscv64", "platform": "linux64"},
27+
{"name": "gcc", "platform": "linux64"},
28+
]
29+
2230

2331
def meets_conditional_version(version: str, min_version: str) -> bool:
2432
return Version(version) >= Version(min_version)
@@ -89,26 +97,51 @@ def should_include_entry(entry: dict[str, str], filters: dict[str, set[str]]) ->
8997
return True
9098

9199

92-
def generate_matrix_entries(
100+
def generate_docker_matrix_entries(
101+
runners: dict[str, Any],
102+
platform_filter: Optional[str] = None,
103+
) -> list[dict[str, str]]:
104+
"""Generate matrix entries for docker image builds."""
105+
if platform_filter and platform_filter != "linux":
106+
return []
107+
108+
matrix_entries = []
109+
for image in DOCKER_BUILD_IMAGES:
110+
# Find appropriate runner based on platform
111+
platform_arch = "aarch64" if image["platform"] == "linux_aarch64" else "x86_64"
112+
runner = find_runner(runners, "linux", platform_arch)
113+
114+
entry = {
115+
"name": image["name"],
116+
"platform": image["platform"],
117+
"runner": runner,
118+
}
119+
matrix_entries.append(entry)
120+
121+
return matrix_entries
122+
123+
124+
def generate_python_build_matrix_entries(
93125
config: dict[str, Any],
94126
runners: dict[str, Any],
95127
platform_filter: Optional[str] = None,
96128
label_filters: Optional[dict[str, set[str]]] = None,
97129
) -> list[dict[str, str]]:
130+
"""Generate matrix entries for python builds."""
98131
matrix_entries = []
99132

100133
for platform, platform_config in config.items():
101134
if platform_filter and platform != platform_filter:
102135
continue
103136

104137
for target_triple, target_config in platform_config.items():
105-
add_matrix_entries_for_config(
138+
add_python_build_entries_for_config(
106139
matrix_entries,
107140
target_triple,
108141
target_config,
109142
platform,
110143
runners,
111-
label_filters.get("directives", set()),
144+
label_filters.get("directives", set()) if label_filters else set(),
112145
)
113146

114147
# Apply label filters if present
@@ -144,14 +177,15 @@ def find_runner(runners: dict[str, Any], platform: str, arch: str) -> str:
144177
raise RuntimeError(f"No runner found for platform {platform!r} and arch {arch!r}")
145178

146179

147-
def add_matrix_entries_for_config(
180+
def add_python_build_entries_for_config(
148181
matrix_entries: list[dict[str, str]],
149182
target_triple: str,
150183
config: dict[str, Any],
151184
platform: str,
152185
runners: dict[str, Any],
153186
directives: set[str],
154187
) -> None:
188+
"""Add python build matrix entries for a specific target configuration."""
155189
python_versions = config["python_versions"]
156190
build_options = config["build_options"]
157191
arch = config["arch"]
@@ -233,6 +267,12 @@ def parse_args() -> argparse.Namespace:
233267
action="store_true",
234268
help="If only free runners should be used.",
235269
)
270+
parser.add_argument(
271+
"--matrix-type",
272+
choices=["python-build", "docker-build", "all"],
273+
default="all",
274+
help="Which matrix types to generate (default: all)",
275+
)
236276
return parser.parse_args()
237277

238278

@@ -254,36 +294,59 @@ def main() -> None:
254294
if runner_config.get("free")
255295
}
256296

257-
entries = generate_matrix_entries(
258-
config,
259-
runners,
260-
args.platform,
261-
labels,
262-
)
263-
264-
if args.max_shards:
265-
matrix = {}
266-
shards = (len(entries) // CI_MATRIX_SIZE_LIMIT) + 1
267-
if shards > args.max_shards:
268-
print(
269-
f"error: matrix of size {len(entries)} requires {shards} shards, but the maximum is {args.max_shards}; consider increasing `--max-shards`",
270-
file=sys.stderr,
271-
)
272-
sys.exit(1)
273-
for shard in range(args.max_shards):
274-
shard_entries = entries[
275-
shard * CI_MATRIX_SIZE_LIMIT : (shard + 1) * CI_MATRIX_SIZE_LIMIT
276-
]
277-
matrix[str(shard)] = {"include": shard_entries}
278-
else:
279-
if len(entries) > CI_MATRIX_SIZE_LIMIT:
280-
print(
281-
f"warning: matrix of size {len(entries)} exceeds limit of {CI_MATRIX_SIZE_LIMIT} but sharding is not enabled; consider setting `--max-shards`",
282-
file=sys.stderr,
297+
result = {}
298+
299+
# Generate python-build matrix if requested
300+
python_entries = []
301+
if args.matrix_type in ["python-build", "all"]:
302+
python_entries = generate_python_build_matrix_entries(
303+
config,
304+
runners,
305+
args.platform,
306+
labels,
307+
)
308+
309+
if args.max_shards:
310+
python_build_matrix = {}
311+
shards = (len(python_entries) // CI_MATRIX_SIZE_LIMIT) + 1
312+
if shards > args.max_shards:
313+
print(
314+
f"error: python-build matrix of size {len(python_entries)} requires {shards} shards, but the maximum is {args.max_shards}; consider increasing `--max-shards`",
315+
file=sys.stderr,
316+
)
317+
sys.exit(1)
318+
for shard in range(args.max_shards):
319+
shard_entries = python_entries[
320+
shard * CI_MATRIX_SIZE_LIMIT : (shard + 1) * CI_MATRIX_SIZE_LIMIT
321+
]
322+
python_build_matrix[str(shard)] = {"include": shard_entries}
323+
result["python-build"] = python_build_matrix
324+
else:
325+
if len(python_entries) > CI_MATRIX_SIZE_LIMIT:
326+
print(
327+
f"warning: python-build matrix of size {len(python_entries)} exceeds limit of {CI_MATRIX_SIZE_LIMIT} but sharding is not enabled; consider setting `--max-shards`",
328+
file=sys.stderr,
329+
)
330+
result["python-build"] = {"include": python_entries}
331+
332+
# Generate docker-build matrix if requested
333+
# Only include docker builds if there are Linux python builds
334+
if args.matrix_type in ["docker-build", "all"]:
335+
# Check if we have any Linux python builds
336+
has_linux_builds = any(
337+
entry.get("platform") == "linux" for entry in python_entries
338+
)
339+
340+
# If no platform filter or explicitly requesting docker-build only, include docker builds
341+
# Otherwise, only include if there are Linux python builds
342+
if args.matrix_type == "docker-build" or has_linux_builds:
343+
docker_entries = generate_docker_matrix_entries(
344+
runners,
345+
args.platform,
283346
)
284-
matrix = {"include": entries}
347+
result["docker-build"] = {"include": docker_entries}
285348

286-
print(json.dumps(matrix))
349+
print(json.dumps(result))
287350

288351

289352
if __name__ == "__main__":

0 commit comments

Comments
 (0)