Skip to content

Commit 89a2b00

Browse files
committed
Initial commit
0 parents  commit 89a2b00

File tree

8 files changed

+231
-0
lines changed

8 files changed

+231
-0
lines changed

.github/workflows/build-root.yml

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
name: Build Python Interpreters
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
8+
jobs:
9+
build-base:
10+
runs-on: ubuntu-latest
11+
steps:
12+
- name: Checkout code
13+
uses: actions/checkout@v5
14+
15+
- name: Set up Docker Buildx
16+
uses: docker/setup-buildx-action@v3
17+
18+
- name: Build and push Python builder base
19+
uses: docker/build-push-action@v6
20+
with:
21+
context: .
22+
file: ./Dockerfile.base
23+
push: true
24+
tags: python-discord/python-builds:builder-base
25+
26+
resolve-versions:
27+
runs-on: ubuntu-latest
28+
needs: build-base
29+
outputs:
30+
resolved_versions: ${{ steps.resolve_versions.outputs.resolved_versions }}
31+
steps:
32+
- name: Checkout code
33+
uses: actions/checkout@v5
34+
35+
- name: Run Docker container to resolve versions
36+
id: resolve_versions
37+
run: |
38+
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)
39+
# JSON array with objects containing "tag" and "version" keys
40+
echo "resolved_versions=$resolved_versions" >> $GITHUB_OUTPUT
41+
42+
build-versions:
43+
needs: [build-base, resolve-versions]
44+
strategy:
45+
matrix:
46+
version_info: ${{ fromJson(needs.resolve-versions.outputs.resolved_versions) }}
47+
uses: ./.github/workflows/build-version.yml
48+
with:
49+
version: ${{ matrix.version_info.version }}
50+
tag: ${{ matrix.version_info.tag }}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
on:
2+
workflow_call:
3+
inputs:
4+
version:
5+
description: "Specific version (major.minor.patch[suffix]) of Python to build"
6+
required: true
7+
type: string
8+
tag:
9+
description: "Version tag to apply to Docker image (e.g. 3.14, 3.14j)"
10+
required: true
11+
type: string
12+
13+
jobs:
14+
build:
15+
runs-on: ubuntu-latest
16+
name: Build Python ${{ inputs.version }}
17+
18+
steps:
19+
- name: Checkout code
20+
uses: actions/checkout@v5
21+
22+
- name: Set up Docker Buildx
23+
uses: docker/setup-buildx-action@v3
24+
25+
- name: Build and push Docker image
26+
uses: docker/build-push-action@v6
27+
with:
28+
context: .
29+
file: ./Dockerfile
30+
push: true
31+
tags: ghcr.io/python-discord/python-builds:${{ inputs.tag }}
32+
build-args: |
33+
PYTHON_VERSION=${{ inputs.version }}

Dockerfile

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
FROM ghcr.io/python-discord/python-builds:builder-base AS python-builder
2+
LABEL org.opencontainers.image.authors="Joe Banks <[email protected]>"
3+
4+
ARG PYTHON_VERSION
5+
6+
RUN [ -z "$PYTHON_VERSION" ] && echo "PYTHON_VERSION Docker build arg is required" && exit 1 || true
7+
8+
RUN /scripts/build_python.sh ${PYTHON_VERSION}

Dockerfile.base

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

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Python Builds
2+
3+
This repository hosts prebuilt versions of Python that are used for the `!eval`
4+
functionality of [`python-discord/bot`](https://github.com/python-discord/bot).
5+
6+
Built images contain a folder `/snekbin/` which contains the built Python
7+
interpreter (with a binary at `/snekbin/bin/python`).
8+
9+

scripts/build_python.sh

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#!/usr/bin/env bash
2+
set -euxo pipefail
3+
shopt -s inherit_errexit
4+
5+
py_version="${1}"
6+
7+
if [[ $py_version == *j ]]; then
8+
# Enable JIT mode when passed a version that ends with a "j"
9+
py_version="${py_version%j}"
10+
PYTHON_CONFIGURE_OPTS+=" --enable-experimental-jit"
11+
fi
12+
13+
"${PYENV_ROOT}/plugins/python-build/bin/python-build" \
14+
"${py_version}" \
15+
"/snekbin/"
16+
"/snekbin/bin/python" -m pip install -U pip
17+
18+
# Clean up some unnecessary files to reduce image size bloat.
19+
find /snekbin/ -depth \
20+
\( \
21+
\( -type d -a \( \
22+
-name test -o -name tests -o -name idle_test \
23+
\) \) \
24+
-o \( -type f -a \( \
25+
-name '*.pyc' -o -name '*.pyo' -o -name 'libpython*.a' \
26+
\) \) \
27+
\) -exec rm -rf '{}' +

scripts/find_version.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
from pathlib import Path
2+
import tomllib
3+
import json
4+
5+
def resolve_version(friendly_version: str) -> str:
6+
"""Resolve a friendly version like '3.10t' to an exact version like '3.10.12'."""
7+
# Strip JIT for now as it's not recognized by pyenv
8+
is_free_threaded = friendly_version.endswith('t')
9+
is_jit = friendly_version.endswith('j')
10+
base_version = friendly_version.rstrip('jt')
11+
12+
pyenv_versions = Path("/pyenv/plugins/python-build/share/python-build")
13+
14+
matching_versions = []
15+
16+
for version_file in pyenv_versions.glob(f"{base_version}.*"):
17+
version_str = version_file.name
18+
19+
if version_str.endswith("-dev"):
20+
continue
21+
22+
if version_str.endswith("t") and is_free_threaded:
23+
matching_versions.append(version_str.rstrip('t'))
24+
elif not version_str.endswith("t"):
25+
matching_versions.append(version_str)
26+
27+
if not matching_versions:
28+
raise ValueError(f"No matching versions found for base version '{base_version}'")
29+
30+
# Sort versions to get the latest
31+
matching_versions.sort(key=lambda s: list(map(int, s.split('.'))))
32+
33+
latest_version = matching_versions[-1]
34+
35+
latest_version += 'j' if is_jit else ''
36+
latest_version += 't' if is_free_threaded else ''
37+
38+
return latest_version
39+
40+
if __name__ == "__main__":
41+
versions_toml_path = Path("/") / "versions.toml"
42+
43+
if not versions_toml_path.exists():
44+
raise FileNotFoundError(f"Could not find versions.toml at expected path: {versions_toml_path}")
45+
46+
with versions_toml_path.open("rb") as f:
47+
versions_config = tomllib.load(f)
48+
49+
friendly_versions = versions_config["config"]["versions"]
50+
51+
resolved_versions = [
52+
{"tag": v, "version": resolve_version(v)} for v in friendly_versions
53+
]
54+
55+
print(json.dumps(resolved_versions))

versions.toml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# This file lists the versions of Python to build.
2+
3+
# Version suffixes indicate the following:
4+
# - Any version suffixed with "t" will be "free-threaded" (see PEP703 and PEP779)
5+
# - Any version suffixed with "j" will have the JIT compiler enabled (see PEP744)
6+
7+
# Only the major and minor version numbers are needed here. Patch versions will be
8+
# determined automatically based on the latest stable releases. Builds will run
9+
# periodically to pick up new patch releases.
10+
11+
# If two entries have the same major.minor, the same suffix and both resolve to
12+
# the same patch version, they will be deduplicated.
13+
14+
# As a result of automatic version resolution, there is no support for using old
15+
# patch versions.
16+
17+
[config]
18+
versions = [
19+
"3.14",
20+
"3.14t",
21+
"3.14j",
22+
"3.13t",
23+
]

0 commit comments

Comments
 (0)