Skip to content

Commit 4938f6c

Browse files
claudeluhenry
authored andcommitted
Bootstrap riscv64 CPython build & release pipeline
Adds two workflows: - build-python.yml: workflow_dispatch per CPython tag on ubuntu-24.04-riscv, builds standard (+ optional --disable-gil for 3.13+), packages as python-<ver>-linux-24.04-riscv64[-freethreaded].tar.gz following the actions/python-versions layout (bin/ include/ lib/ share/ setup.sh build_output.txt tools_structure.txt), and publishes/uploads to a GitHub Release tagged with the normalised version. - check-releases.yml: scheduled poll of python/cpython tags (3.10+ stable plus a/b/rc prereleases), de-duplicates against existing releases, and fan-out dispatches build-python.yml with a max_dispatches cap. Vendors actions/python-versions' nix-setup-template.sh so extracted tarballs are drop-in compatible with setup-python's toolcache layout.
0 parents  commit 4938f6c

File tree

4 files changed

+346
-0
lines changed

4 files changed

+346
-0
lines changed

.github/workflows/build-python.yml

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
name: Build Python
2+
3+
on:
4+
workflow_dispatch:
5+
inputs:
6+
cpython_tag:
7+
description: "CPython git tag to build (e.g. v3.12.13, v3.15.0a7)"
8+
required: true
9+
type: string
10+
freethreaded:
11+
description: "Also build a --disable-gil variant (3.13+)"
12+
required: false
13+
default: false
14+
type: boolean
15+
16+
permissions:
17+
contents: write
18+
19+
defaults:
20+
run:
21+
shell: bash --noprofile --norc -exo pipefail {0}
22+
23+
jobs:
24+
build:
25+
runs-on: ubuntu-24.04-riscv
26+
env:
27+
CPYTHON_TAG: ${{ inputs.cpython_tag }}
28+
FREETHREADED: ${{ inputs.freethreaded }}
29+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
30+
steps:
31+
- name: Checkout this repo
32+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
33+
34+
- name: Install build dependencies
35+
run: |
36+
sudo apt-get update -qq
37+
sudo apt-get install -qq -y --no-install-recommends \
38+
build-essential \
39+
ca-certificates \
40+
g++-14 \
41+
gcc-14 \
42+
git \
43+
libbz2-dev \
44+
libexpat1-dev \
45+
libffi-dev \
46+
libgdbm-dev \
47+
liblzma-dev \
48+
libncursesw5-dev \
49+
libreadline-dev \
50+
libsqlite3-dev \
51+
libssl-dev \
52+
make \
53+
patchelf \
54+
pkg-config \
55+
tk-dev \
56+
uuid-dev \
57+
wget \
58+
xz-utils \
59+
zlib1g-dev
60+
61+
# Make sure Python is built with gcc 14
62+
echo "CC=gcc-14" >> $GITHUB_ENV
63+
echo "CXX=g++-14" >> $GITHUB_ENV
64+
65+
- name: Compute normalised version
66+
id: vars
67+
run: |
68+
set -euo pipefail
69+
tag="$CPYTHON_TAG"
70+
# Strip leading v, expand a/b/rc suffixes
71+
normalised="${tag#v}"
72+
normalised="$(echo "$normalised" | sed -E 's/a([0-9]+)$/-alpha.\1/; s/b([0-9]+)$/-beta.\1/; s/rc([0-9]+)$/-rc.\1/')"
73+
# X.Y
74+
minor_num="$(echo "$normalised" | cut -d. -f2)"
75+
minor="3.${minor_num}"
76+
echo "normalised=${normalised}" >> "$GITHUB_OUTPUT"
77+
echo "minor=${minor}" >> "$GITHUB_OUTPUT"
78+
echo "minor_num=${minor_num}" >> "$GITHUB_OUTPUT"
79+
echo "archive_base=python-${normalised}-linux-24.04-riscv64" >> "$GITHUB_OUTPUT"
80+
echo "Normalised version: ${normalised}"
81+
echo "Python X.Y: ${minor}"
82+
if [ "${FREETHREADED}" = "true" ] && [ "${minor_num}" -lt 13 ]; then
83+
echo "ERROR: freethreaded build requires CPython 3.13+" >&2
84+
exit 1
85+
fi
86+
87+
- name: Checkout CPython
88+
run: git clone --depth 1 --branch "$CPYTHON_TAG" https://github.com/python/cpython.git src
89+
90+
- name: Build standard CPython
91+
env:
92+
MINOR: ${{ steps.vars.outputs.minor }}
93+
ARCHIVE_BASE: ${{ steps.vars.outputs.archive_base }}
94+
NORMALISED: ${{ steps.vars.outputs.normalised }}
95+
run: |
96+
set -euo pipefail
97+
mkdir -p output
98+
pushd src
99+
./configure --with-ensurepip=install --enable-shared
100+
make -j"$(nproc)" 2>&1 | tee ../build_output.txt
101+
make install DESTDIR="$PWD/../staging-std"
102+
popd
103+
104+
mkdir -p "$ARCHIVE_BASE"
105+
cp -a staging-std/usr/local/bin "$ARCHIVE_BASE/"
106+
cp -a staging-std/usr/local/include "$ARCHIVE_BASE/"
107+
cp -a staging-std/usr/local/lib "$ARCHIVE_BASE/"
108+
cp -a staging-std/usr/local/share "$ARCHIVE_BASE/"
109+
110+
patchelf --set-rpath '$ORIGIN/../lib' "$ARCHIVE_BASE/bin/python${MINOR}"
111+
112+
sed -e "s|{{__VERSION_FULL__}}|${NORMALISED}|g" \
113+
-e "s|{{__ARCH__}}|riscv64|g" \
114+
installers/nix-setup-template.sh > "$ARCHIVE_BASE/setup.sh"
115+
116+
mv build_output.txt "$ARCHIVE_BASE/build_output.txt"
117+
118+
(cd "$ARCHIVE_BASE" && find . \( -type f -o -type l \) | sed 's|^\.|/|') > "$ARCHIVE_BASE/tools_structure.txt"
119+
120+
tar -czf "output/${ARCHIVE_BASE}.tar.gz" "$ARCHIVE_BASE"
121+
rm -rf "$ARCHIVE_BASE" staging-std
122+
123+
- name: Build free-threaded CPython
124+
if: ${{ inputs.freethreaded == true }}
125+
env:
126+
MINOR: ${{ steps.vars.outputs.minor }}
127+
ARCHIVE_BASE: ${{ steps.vars.outputs.archive_base }}
128+
NORMALISED: ${{ steps.vars.outputs.normalised }}
129+
run: |
130+
set -euo pipefail
131+
ft_base="${ARCHIVE_BASE}-freethreaded"
132+
pushd src
133+
git clean -fdx
134+
./configure --with-ensurepip=install --enable-shared --disable-gil
135+
make -j"$(nproc)" 2>&1 | tee ../build_output.txt
136+
make install DESTDIR="$PWD/../staging-ft"
137+
popd
138+
139+
mkdir -p "$ft_base"
140+
cp -a staging-ft/usr/local/bin "$ft_base/"
141+
cp -a staging-ft/usr/local/include "$ft_base/"
142+
cp -a staging-ft/usr/local/lib "$ft_base/"
143+
cp -a staging-ft/usr/local/share "$ft_base/"
144+
145+
patchelf --set-rpath '$ORIGIN/../lib' "$ft_base/bin/python${MINOR}"
146+
147+
sed -e "s|{{__VERSION_FULL__}}|${NORMALISED}|g" \
148+
-e "s|{{__ARCH__}}|riscv64|g" \
149+
installers/nix-setup-template.sh > "$ft_base/setup.sh"
150+
151+
mv build_output.txt "$ft_base/build_output.txt"
152+
153+
(cd "$ft_base" && find . \( -type f -o -type l \) | sed 's|^\.|/|') > "$ft_base/tools_structure.txt"
154+
155+
tar -czf "output/${ft_base}.tar.gz" "$ft_base"
156+
rm -rf "$ft_base" staging-ft
157+
158+
- name: Print tarball hashes
159+
run: sha256sum output/*.tar.gz
160+
161+
- name: Publish release
162+
env:
163+
NORMALISED: ${{ steps.vars.outputs.normalised }}
164+
run: |
165+
set -euo pipefail
166+
if gh release view "$NORMALISED" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
167+
gh release upload "$NORMALISED" --repo "$GITHUB_REPOSITORY" --clobber ./output/*.tar.gz
168+
else
169+
gh release create "$NORMALISED" --repo "$GITHUB_REPOSITORY" \
170+
--title "$NORMALISED" \
171+
--notes "Python $NORMALISED" \
172+
./output/*.tar.gz
173+
fi
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
name: Check CPython releases
2+
3+
on:
4+
schedule:
5+
- cron: '0 6 * * *'
6+
workflow_dispatch:
7+
inputs:
8+
max_dispatches:
9+
description: "Maximum number of build dispatches (0 = dry run)"
10+
required: false
11+
default: "20"
12+
type: string
13+
14+
permissions:
15+
contents: read
16+
actions: write
17+
18+
jobs:
19+
discover:
20+
runs-on: ubuntu-latest
21+
env:
22+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
23+
MAX_DISPATCHES: ${{ inputs.max_dispatches || '20' }}
24+
steps:
25+
- name: Enumerate and dispatch
26+
run: |
27+
set -euo pipefail
28+
max="${MAX_DISPATCHES}"
29+
dispatched=0
30+
dispatched_list=""
31+
32+
# Fetch all v* tags from python/cpython, ordered by version, descending (newer first)
33+
mapfile -t refs < <(gh api --paginate repos/python/cpython/git/matching-refs/tags/v -q '.[].ref' | sort -V --reverse)
34+
35+
for ref in "${refs[@]}"; do
36+
tag="${ref#refs/tags/}"
37+
# Filter: 3.10+, with optional a/b/rc suffix, no dev/post
38+
if [[ ! "$tag" =~ ^v(3\.(1[0-9]|[2-9][0-9]))\.([0-9]+)(a[0-9]+|b[0-9]+|rc[0-9]+)?$ ]]; then
39+
continue
40+
fi
41+
minor_int="${BASH_REMATCH[2]}"
42+
43+
# Normalise
44+
normalised="${tag#v}"
45+
normalised="$(echo "$normalised" | sed -E 's/a([0-9]+)$/-alpha.\1/; s/b([0-9]+)$/-beta.\1/; s/rc([0-9]+)$/-rc.\1/')"
46+
47+
echo "Tag $tag -> release $normalised"
48+
49+
if gh release view "$normalised" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
50+
echo " already released, skipping"
51+
continue
52+
fi
53+
54+
if [ "$dispatched" -ge "$max" ]; then
55+
echo " would dispatch (cap reached)"
56+
continue
57+
fi
58+
59+
ft="false"
60+
if [ "$minor_int" -ge 13 ]; then
61+
ft="true"
62+
fi
63+
64+
echo " dispatching build-python.yml (freethreaded=$ft)"
65+
gh workflow run build-python.yml --repo "$GITHUB_REPOSITORY" \
66+
-f cpython_tag="$tag" \
67+
-f freethreaded="$ft"
68+
dispatched=$((dispatched + 1))
69+
dispatched_list="${dispatched_list}${tag} -> ${normalised} (ft=${ft})"$'\n'
70+
done
71+
72+
echo ""
73+
echo "=== Dispatched $dispatched build(s) ==="
74+
printf '%s' "$dispatched_list"

README.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# python-versions (riscv64)
2+
3+
Prebuilt CPython binaries for `linux/riscv64`, published as GitHub Releases.
4+
5+
A scheduled workflow polls [`python/cpython`](https://github.com/python/cpython)
6+
for new version tags (stable plus `a`/`b`/`rc` prereleases from 3.10 onward) and
7+
dispatches a build on the GitHub-hosted `ubuntu-24.04-riscv` runner for every
8+
tag that does not yet have a release here.
9+
10+
Tarball layout and naming follow
11+
[`actions/python-versions`](https://github.com/actions/python-versions) so
12+
downstream tooling (`actions/setup-python`, toolcache scripts) can consume
13+
them unchanged. Free-threaded (`--disable-gil`) variants are published as
14+
additional assets on the same release for CPython 3.13+.
15+
16+
## Release / asset naming
17+
18+
| CPython tag | Release tag | Asset |
19+
| ------------ | ---------------- | ------------------------------------------------------------------ |
20+
| `v3.12.13` | `3.12.13` | `python-3.12.13-linux-24.04-riscv64.tar.gz` |
21+
| `v3.13.3` | `3.13.3` | `python-3.13.3-linux-24.04-riscv64.tar.gz` (+ `-freethreaded`) |
22+
| `v3.15.0a7` | `3.15.0-alpha.7` | `python-3.15.0-alpha.7-linux-24.04-riscv64.tar.gz` (+ `-freethreaded`) |
23+
24+
## Using a release
25+
26+
```sh
27+
tar -xzf python-3.12.13-linux-24.04-riscv64.tar.gz
28+
./python-3.12.13-linux-24.04-riscv64/bin/python3.12 --version
29+
```
30+
31+
Or, to install into the GitHub Actions toolcache layout (`setup-python` compatible):
32+
33+
```sh
34+
cd python-3.12.13-linux-24.04-riscv64
35+
./setup.sh
36+
```
37+
38+
## Manual dispatch
39+
40+
- Build one tag: trigger the **Build Python** workflow with `cpython_tag=v3.12.13` (optionally `freethreaded=true` for 3.13+).
41+
- Backfill: trigger **Check CPython releases** with `max_dispatches` set to the number of tags you want to build (use `0` for a dry run).

installers/nix-setup-template.sh

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
set -e
2+
3+
PYTHON_FULL_VERSION="{{__VERSION_FULL__}}"
4+
ARCH="{{__ARCH__}}"
5+
MAJOR_VERSION=$(echo $PYTHON_FULL_VERSION | cut -d '.' -f 1)
6+
MINOR_VERSION=$(echo $PYTHON_FULL_VERSION | cut -d '.' -f 2)
7+
8+
PYTHON_MAJOR=python$MAJOR_VERSION
9+
PYTHON_MAJOR_DOT_MINOR=python$MAJOR_VERSION.$MINOR_VERSION
10+
PYTHON_MAJORMINOR=python$MAJOR_VERSION$MINOR_VERSION
11+
12+
if [ -z ${AGENT_TOOLSDIRECTORY+x} ]; then
13+
# No AGENT_TOOLSDIRECTORY on GitHub images
14+
TOOLCACHE_ROOT=$RUNNER_TOOL_CACHE
15+
else
16+
TOOLCACHE_ROOT=$AGENT_TOOLSDIRECTORY
17+
fi
18+
19+
PYTHON_TOOLCACHE_PATH=$TOOLCACHE_ROOT/Python
20+
PYTHON_TOOLCACHE_VERSION_PATH=$PYTHON_TOOLCACHE_PATH/$PYTHON_FULL_VERSION
21+
PYTHON_TOOLCACHE_VERSION_ARCH_PATH=$PYTHON_TOOLCACHE_VERSION_PATH/$ARCH
22+
23+
echo "Check if Python hostedtoolcache folder exist..."
24+
if [ ! -d $PYTHON_TOOLCACHE_PATH ]; then
25+
echo "Creating Python hostedtoolcache folder..."
26+
mkdir -p $PYTHON_TOOLCACHE_PATH
27+
elif [ -d $PYTHON_TOOLCACHE_VERSION_ARCH_PATH ]; then
28+
echo "Deleting Python $PYTHON_FULL_VERSION ($ARCH)"
29+
rm -rf $PYTHON_TOOLCACHE_VERSION_ARCH_PATH
30+
fi
31+
32+
echo "Create Python $PYTHON_FULL_VERSION folder"
33+
mkdir -p $PYTHON_TOOLCACHE_VERSION_ARCH_PATH
34+
35+
echo "Copy Python binaries to hostedtoolcache folder"
36+
cp -R ./* $PYTHON_TOOLCACHE_VERSION_ARCH_PATH
37+
rm $PYTHON_TOOLCACHE_VERSION_ARCH_PATH/setup.sh
38+
39+
cd $PYTHON_TOOLCACHE_VERSION_ARCH_PATH
40+
41+
echo "Create additional symlinks (Required for the UsePythonVersion Azure Pipelines task and the setup-python GitHub Action)"
42+
ln -s ./bin/$PYTHON_MAJOR_DOT_MINOR python
43+
44+
cd bin/
45+
ln -s $PYTHON_MAJOR_DOT_MINOR $PYTHON_MAJORMINOR
46+
if [ ! -f python ]; then
47+
ln -s $PYTHON_MAJOR_DOT_MINOR python
48+
fi
49+
50+
chmod +x ../python $PYTHON_MAJOR $PYTHON_MAJOR_DOT_MINOR $PYTHON_MAJORMINOR python
51+
52+
echo "Upgrading pip..."
53+
export PIP_ROOT_USER_ACTION=ignore
54+
./python -m ensurepip
55+
./python -m pip install --upgrade --force-reinstall pip --disable-pip-version-check --no-warn-script-location
56+
57+
echo "Create complete file"
58+
touch $PYTHON_TOOLCACHE_VERSION_PATH/$ARCH.complete

0 commit comments

Comments
 (0)