Skip to content

Commit ce2f884

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

File tree

4 files changed

+126
-59
lines changed

4 files changed

+126
-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: 94 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", "arch": "x86_64"},
25+
{"name": "build.cross", "arch": "x86_64"},
26+
{"name": "build.cross-riscv64", "arch": "x86_64"},
27+
{"name": "gcc", "arch": "x86_64"},
28+
]
29+
2230

2331
def meets_conditional_version(version: str, min_version: str) -> bool:
2432
return Version(version) >= Version(min_version)
@@ -89,26 +97,50 @@ 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 for Linux platform with the specified architecture
111+
runner = find_runner(runners, "linux", image["arch"])
112+
113+
entry = {
114+
"name": image["name"],
115+
"arch": image["arch"],
116+
"runner": runner,
117+
}
118+
matrix_entries.append(entry)
119+
120+
return matrix_entries
121+
122+
123+
def generate_python_build_matrix_entries(
93124
config: dict[str, Any],
94125
runners: dict[str, Any],
95126
platform_filter: Optional[str] = None,
96127
label_filters: Optional[dict[str, set[str]]] = None,
97128
) -> list[dict[str, str]]:
129+
"""Generate matrix entries for python builds."""
98130
matrix_entries = []
99131

100132
for platform, platform_config in config.items():
101133
if platform_filter and platform != platform_filter:
102134
continue
103135

104136
for target_triple, target_config in platform_config.items():
105-
add_matrix_entries_for_config(
137+
add_python_build_entries_for_config(
106138
matrix_entries,
107139
target_triple,
108140
target_config,
109141
platform,
110142
runners,
111-
label_filters.get("directives", set()),
143+
label_filters.get("directives", set()) if label_filters else set(),
112144
)
113145

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

146178

147-
def add_matrix_entries_for_config(
179+
def add_python_build_entries_for_config(
148180
matrix_entries: list[dict[str, str]],
149181
target_triple: str,
150182
config: dict[str, Any],
151183
platform: str,
152184
runners: dict[str, Any],
153185
directives: set[str],
154186
) -> None:
187+
"""Add python build matrix entries for a specific target configuration."""
155188
python_versions = config["python_versions"]
156189
build_options = config["build_options"]
157190
arch = config["arch"]
@@ -233,6 +266,12 @@ def parse_args() -> argparse.Namespace:
233266
action="store_true",
234267
help="If only free runners should be used.",
235268
)
269+
parser.add_argument(
270+
"--matrix-type",
271+
choices=["python-build", "docker-build", "all"],
272+
default="all",
273+
help="Which matrix types to generate (default: all)",
274+
)
236275
return parser.parse_args()
237276

238277

@@ -254,36 +293,59 @@ def main() -> None:
254293
if runner_config.get("free")
255294
}
256295

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,
296+
result = {}
297+
298+
# Generate python-build matrix if requested
299+
python_entries = []
300+
if args.matrix_type in ["python-build", "all"]:
301+
python_entries = generate_python_build_matrix_entries(
302+
config,
303+
runners,
304+
args.platform,
305+
labels,
306+
)
307+
308+
if args.max_shards:
309+
python_build_matrix = {}
310+
shards = (len(python_entries) // CI_MATRIX_SIZE_LIMIT) + 1
311+
if shards > args.max_shards:
312+
print(
313+
f"error: python-build matrix of size {len(python_entries)} requires {shards} shards, but the maximum is {args.max_shards}; consider increasing `--max-shards`",
314+
file=sys.stderr,
315+
)
316+
sys.exit(1)
317+
for shard in range(args.max_shards):
318+
shard_entries = python_entries[
319+
shard * CI_MATRIX_SIZE_LIMIT : (shard + 1) * CI_MATRIX_SIZE_LIMIT
320+
]
321+
python_build_matrix[str(shard)] = {"include": shard_entries}
322+
result["python-build"] = python_build_matrix
323+
else:
324+
if len(python_entries) > CI_MATRIX_SIZE_LIMIT:
325+
print(
326+
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`",
327+
file=sys.stderr,
328+
)
329+
result["python-build"] = {"include": python_entries}
330+
331+
# Generate docker-build matrix if requested
332+
# Only include docker builds if there are Linux python builds
333+
if args.matrix_type in ["docker-build", "all"]:
334+
# Check if we have any Linux python builds
335+
has_linux_builds = any(
336+
entry.get("platform") == "linux" for entry in python_entries
337+
)
338+
339+
# If no platform filter or explicitly requesting docker-build only, include docker builds
340+
# Otherwise, only include if there are Linux python builds
341+
if args.matrix_type == "docker-build" or has_linux_builds:
342+
docker_entries = generate_docker_matrix_entries(
343+
runners,
344+
args.platform,
283345
)
284-
matrix = {"include": entries}
346+
result["docker-build"] = {"include": docker_entries}
285347

286-
print(json.dumps(matrix))
348+
print(json.dumps(result))
287349

288350

289351
if __name__ == "__main__":

0 commit comments

Comments
 (0)