Skip to content

Commit a93af51

Browse files
Add arm64 support to CI Docker builds (#2)
* Add arm64 support to CI Docker builds * Bump CI action versions to latest * Co-opt credit for someone else's project * Merge Dockerfiles into a single multi-stage file * Pass PYENV_VERSION through to build-version to fix broken builds after Dockerfile merge * Use native arm64 runners instead of QEMU to fix build timeouts * Re-add 3.14 that was removed by mistake
1 parent 44b2629 commit a93af51

5 files changed

Lines changed: 120 additions & 67 deletions

File tree

.github/workflows/build-root.yml

Lines changed: 28 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -17,68 +17,77 @@ jobs:
1717
runs-on: ubuntu-latest
1818
outputs:
1919
pyenv_version: ${{ steps.get_pyenv_version.outputs.pyenv_version }}
20+
resolved_versions: ${{ steps.resolve_versions.outputs.resolved_versions }}
2021
steps:
2122
- name: Checkout code
22-
uses: actions/checkout@v5
23+
uses: actions/checkout@v7
24+
25+
- name: Checkout pyenv
26+
uses: actions/checkout@v7
2327
with:
2428
repository: pyenv/pyenv
2529
fetch-depth: 0
2630
fetch-tags: true
31+
path: pyenv
2732

2833
- name: Get latest pyenv version
2934
id: get_pyenv_version
3035
run: |
36+
cd pyenv
3137
echo "pyenv_version=$(git describe --abbrev=0 --tags)" >> $GITHUB_OUTPUT
3238
39+
- name: Resolve Python versions
40+
id: resolve_versions
41+
run: |
42+
resolved_versions=$(PYENV_ROOT=./pyenv VERSIONS_TOML=./versions.toml python3 scripts/find_version.py)
43+
# JSON array with objects containing "tag" and "version" keys
44+
echo "resolved_versions=$resolved_versions" >> $GITHUB_OUTPUT
45+
3346
build-base:
3447
name: Build Python Builder Base
3548
needs: pyenv-version
36-
outputs:
37-
resolved_versions: ${{ steps.resolve_versions.outputs.resolved_versions }}
3849
runs-on: ubuntu-latest
3950
steps:
4051
- name: Checkout code
41-
uses: actions/checkout@v5
52+
uses: actions/checkout@v7
53+
54+
- name: Set up QEMU
55+
uses: docker/setup-qemu-action@v4
4256

4357
- name: Set up Docker Buildx
44-
uses: docker/setup-buildx-action@v3
58+
uses: docker/setup-buildx-action@v4
4559

4660
- name: Login to Github Container Registry
47-
uses: docker/login-action@v3
61+
uses: docker/login-action@v4
4862
with:
4963
registry: ghcr.io
5064
username: ${{ github.repository_owner }}
5165
password: ${{ secrets.GITHUB_TOKEN }}
5266

53-
- name: Build and push Python builder base
54-
uses: docker/build-push-action@v6
67+
- name: Build and push Python builder base (multi-platform)
68+
uses: docker/build-push-action@v7
5569
with:
5670
context: .
57-
file: ./Dockerfile.base
71+
file: ./Dockerfile
72+
target: builder-base
73+
platforms: linux/amd64,linux/arm64
5874
push: ${{ github.ref == 'refs/heads/main' }}
5975
cache-to: type=inline
60-
load: true
6176
cache-from: type=registry,ref=ghcr.io/python-discord/python-builds:builder-base
6277
build-args: |
6378
PYENV_VERSION=${{ needs.pyenv-version.outputs.pyenv_version }}
6479
tags: |
6580
ghcr.io/python-discord/python-builds:builder-base
6681
ghcr.io/python-discord/python-builds:builder-base-${{ needs.pyenv-version.outputs.pyenv_version }}
6782
68-
- name: Run Docker container to resolve versions
69-
id: resolve_versions
70-
run: |
71-
resolved_versions=$(docker run --mount type=bind,src=./versions.toml,dst=/versions.toml --rm ghcr.io/python-discord/python-builds:builder-base python3 /scripts/find_version.py)
72-
# JSON array with objects containing "tag" and "version" keys
73-
echo "resolved_versions=$resolved_versions" >> $GITHUB_OUTPUT
74-
7583
build-versions:
7684
name: Build Python Versions
77-
needs: build-base
85+
needs: [pyenv-version, build-base]
7886
strategy:
7987
matrix:
80-
version_info: ${{ fromJson(needs.build-base.outputs.resolved_versions) }}
88+
version_info: ${{ fromJson(needs.pyenv-version.outputs.resolved_versions) }}
8189
uses: ./.github/workflows/build-version.yml
8290
with:
8391
version: ${{ matrix.version_info.version }}
8492
tag: ${{ matrix.version_info.tag }}
93+
pyenv_version: ${{ needs.pyenv-version.outputs.pyenv_version }}

.github/workflows/build-version.yml

Lines changed: 59 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -11,50 +11,91 @@ on:
1111
description: "Version tag to apply to Docker image (e.g. 3.14, 3.14j)"
1212
required: true
1313
type: string
14+
pyenv_version:
15+
description: "The pyenv version to use when building the builder-base stage"
16+
required: true
17+
type: string
1418

1519
jobs:
1620
build:
17-
runs-on: ubuntu-latest
18-
name: Build Python ${{ inputs.version }}
21+
name: Build Python ${{ inputs.version }} (${{ matrix.platform }})
22+
runs-on: ${{ matrix.runner }}
1923

20-
concurrency:
21-
group: build-python-${{ inputs.version }}
22-
cancel-in-progress: true
24+
strategy:
25+
fail-fast: false
26+
matrix:
27+
include:
28+
- platform: linux/amd64
29+
runner: ubuntu-latest
30+
- platform: linux/arm64
31+
runner: ubuntu-24.04-arm
2332

2433
steps:
2534
- name: Checkout code
26-
uses: actions/checkout@v5
35+
uses: actions/checkout@v7
2736

2837
- name: Set up Docker Buildx
29-
uses: docker/setup-buildx-action@v3
38+
uses: docker/setup-buildx-action@v4
3039

3140
- name: Login to Github Container Registry
32-
uses: docker/login-action@v3
41+
uses: docker/login-action@v4
3342
with:
3443
registry: ghcr.io
3544
username: ${{ github.repository_owner }}
3645
password: ${{ secrets.GITHUB_TOKEN }}
3746

38-
- name: Get Git commit short SHA
39-
id: git-sha
40-
run: echo "sha=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
47+
- name: Sanitize platform name
48+
id: platform
49+
run: echo "name=$(echo '${{ matrix.platform }}' | tr '/' '-')" >> $GITHUB_OUTPUT
4150

42-
- name: Build and push Docker image
43-
uses: docker/build-push-action@v6
51+
- name: Build and push platform image
52+
uses: docker/build-push-action@v7
4453
with:
4554
context: .
4655
file: ./Dockerfile
56+
target: python-builder
57+
platforms: ${{ matrix.platform }}
4758
push: ${{ github.ref == 'refs/heads/main' }}
4859
cache-to: type=inline
49-
cache-from: type=registry,ref=ghcr.io/python-discord/python-builds:${{ inputs.tag }}
60+
cache-from: |
61+
type=registry,ref=ghcr.io/python-discord/python-builds:builder-base
62+
type=registry,ref=ghcr.io/python-discord/python-builds:${{ inputs.tag }}-${{ steps.platform.outputs.name }}
5063
labels: |
5164
org.opencontainers.image.source=https://github.com/python-discord/python-builds
5265
org.opencontainers.image.description="Python ${{ inputs.version }} Docker image maintained by Python Discord."
5366
org.opencontainers.image.licenses=MIT
54-
outputs: type=image,name=target,annotation-index.org.opencontainers.image.description=Python ${{ inputs.version }} Docker image maintained by Python Discord.
5567
tags: |
56-
ghcr.io/python-discord/python-builds:${{ inputs.tag }}
57-
ghcr.io/python-discord/python-builds:${{ inputs.version }}
58-
ghcr.io/python-discord/python-builds:${{ inputs.tag }}-${{ steps.git-sha.outputs.sha }}
68+
ghcr.io/python-discord/python-builds:${{ inputs.tag }}-${{ steps.platform.outputs.name }}
5969
build-args: |
6070
PYTHON_VERSION=${{ inputs.version }}
71+
PYENV_VERSION=${{ inputs.pyenv_version }}
72+
73+
merge:
74+
name: Merge ${{ inputs.version }} manifests
75+
needs: build
76+
runs-on: ubuntu-latest
77+
if: github.ref == 'refs/heads/main'
78+
79+
steps:
80+
- name: Get Git commit short SHA
81+
id: git-sha
82+
run: echo "sha=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
83+
84+
- name: Login to Github Container Registry
85+
uses: docker/login-action@v4
86+
with:
87+
registry: ghcr.io
88+
username: ${{ github.repository_owner }}
89+
password: ${{ secrets.GITHUB_TOKEN }}
90+
91+
- name: Create and push multi-platform manifest
92+
run: |
93+
docker buildx imagetools create \
94+
--annotation "index:org.opencontainers.image.source=https://github.com/python-discord/python-builds" \
95+
--annotation "index:org.opencontainers.image.description=Python ${{ inputs.version }} Docker image maintained by Python Discord." \
96+
--annotation "index:org.opencontainers.image.licenses=MIT" \
97+
--tag ghcr.io/python-discord/python-builds:${{ inputs.tag }} \
98+
--tag ghcr.io/python-discord/python-builds:${{ inputs.version }} \
99+
--tag ghcr.io/python-discord/python-builds:${{ inputs.tag }}-${{ steps.git-sha.outputs.sha }} \
100+
ghcr.io/python-discord/python-builds:${{ inputs.tag }}-linux-amd64 \
101+
ghcr.io/python-discord/python-builds:${{ inputs.tag }}-linux-arm64

Dockerfile

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,32 @@
1-
FROM ghcr.io/python-discord/python-builds:builder-base AS python-builder
2-
LABEL org.opencontainers.image.authors="Joe Banks <joe@owlcorp.uk>"
1+
FROM buildpack-deps:bookworm AS builder-base
2+
LABEL org.opencontainers.image.authors="Joe Banks <joe@owlcorp.uk>, Chris Lovering <cj@owlcorp.uk>"
3+
4+
ARG PYENV_VERSION="v2.6.11"
5+
6+
RUN apt-get -y update \
7+
&& apt-get install -y --no-install-recommends \
8+
libxmlsec1-dev \
9+
tk-dev \
10+
lsb-release \
11+
software-properties-common \
12+
gnupg \
13+
&& rm -rf /var/lib/apt/lists/*
14+
15+
# Following guidance from https://github.com/python/cpython/blob/main/Tools/jit/README.md
16+
RUN curl -o /tmp/llvm.sh https://apt.llvm.org/llvm.sh \
17+
&& chmod +x /tmp/llvm.sh \
18+
&& /tmp/llvm.sh 19 \
19+
&& rm /tmp/llvm.sh
20+
21+
ENV PYENV_ROOT=/pyenv \
22+
PYTHON_CONFIGURE_OPTS='--disable-test-modules --enable-optimizations \
23+
--with-lto --without-ensurepip'
24+
25+
RUN git clone -b ${PYENV_VERSION} --depth 1 https://github.com/pyenv/pyenv.git $PYENV_ROOT
26+
27+
COPY --link scripts scripts
28+
29+
FROM builder-base AS python-builder
330

431
ARG PYTHON_VERSION
532

Dockerfile.base

Lines changed: 0 additions & 26 deletions
This file was deleted.

scripts/find_version.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from pathlib import Path
22
import tomllib
33
import json
4+
import os
45

56
def resolve_version(friendly_version: str) -> str:
67
"""Resolve a friendly version like '3.10t' to an exact version like '3.10.12'."""
@@ -9,7 +10,8 @@ def resolve_version(friendly_version: str) -> str:
910
is_jit = friendly_version.endswith('j')
1011
base_version = friendly_version.rstrip('jt')
1112

12-
pyenv_versions = Path("/pyenv/plugins/python-build/share/python-build")
13+
pyenv_root = Path(os.environ.get("PYENV_ROOT", "/pyenv"))
14+
pyenv_versions = pyenv_root / "plugins/python-build/share/python-build"
1315

1416
matching_versions = []
1517

@@ -38,7 +40,7 @@ def resolve_version(friendly_version: str) -> str:
3840
return latest_version
3941

4042
if __name__ == "__main__":
41-
versions_toml_path = Path("/") / "versions.toml"
43+
versions_toml_path = Path(os.environ.get("VERSIONS_TOML", "/versions.toml"))
4244

4345
if not versions_toml_path.exists():
4446
raise FileNotFoundError(f"Could not find versions.toml at expected path: {versions_toml_path}")

0 commit comments

Comments
 (0)