Skip to content

Commit fa4d8f2

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 fa4d8f2

File tree

4 files changed

+340
-0
lines changed

4 files changed

+340
-0
lines changed

.github/workflows/build-python.yml

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
name: Build Python (riscv64)
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+
jobs:
20+
build:
21+
runs-on: ubuntu-24.04-riscv
22+
env:
23+
CPYTHON_TAG: ${{ inputs.cpython_tag }}
24+
FREETHREADED: ${{ inputs.freethreaded }}
25+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
26+
steps:
27+
- name: Checkout this repo
28+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
29+
30+
- name: Install build dependencies
31+
run: |
32+
sudo apt-get update -qq
33+
sudo apt-get install -qq -y --no-install-recommends \
34+
build-essential \
35+
ca-certificates \
36+
g++-14 \
37+
gcc-14 \
38+
git \
39+
libbz2-dev \
40+
libexpat1-dev \
41+
libffi-dev \
42+
libgdbm-dev \
43+
liblzma-dev \
44+
libncursesw5-dev \
45+
libreadline-dev \
46+
libsqlite3-dev \
47+
libssl-dev \
48+
make \
49+
patchelf \
50+
pkg-config \
51+
tk-dev \
52+
uuid-dev \
53+
wget \
54+
xz-utils \
55+
zlib1g-dev
56+
57+
- name: Compute normalised version
58+
id: vars
59+
run: |
60+
set -euo pipefail
61+
tag="$CPYTHON_TAG"
62+
# Strip leading v, expand a/b/rc suffixes
63+
normalised="${tag#v}"
64+
normalised="$(echo "$normalised" | sed -E 's/a([0-9]+)$/-alpha.\1/; s/b([0-9]+)$/-beta.\1/; s/rc([0-9]+)$/-rc.\1/')"
65+
# X.Y
66+
minor_num="$(echo "$normalised" | cut -d. -f2)"
67+
minor="3.${minor_num}"
68+
echo "normalised=${normalised}" >> "$GITHUB_OUTPUT"
69+
echo "minor=${minor}" >> "$GITHUB_OUTPUT"
70+
echo "minor_num=${minor_num}" >> "$GITHUB_OUTPUT"
71+
echo "archive_base=python-${normalised}-linux-24.04-riscv64" >> "$GITHUB_OUTPUT"
72+
echo "Normalised version: ${normalised}"
73+
echo "Python X.Y: ${minor}"
74+
if [ "${FREETHREADED}" = "true" ] && [ "${minor_num}" -lt 13 ]; then
75+
echo "ERROR: freethreaded build requires CPython 3.13+" >&2
76+
exit 1
77+
fi
78+
79+
- name: Checkout CPython
80+
run: git clone --depth 1 --branch "$CPYTHON_TAG" https://github.com/python/cpython.git src
81+
82+
- name: Build standard CPython
83+
env:
84+
MINOR: ${{ steps.vars.outputs.minor }}
85+
ARCHIVE_BASE: ${{ steps.vars.outputs.archive_base }}
86+
NORMALISED: ${{ steps.vars.outputs.normalised }}
87+
run: |
88+
set -euo pipefail
89+
mkdir -p output
90+
pushd src
91+
./configure --with-ensurepip=install --enable-shared
92+
make -j"$(nproc)" 2>&1 | tee ../build_output.txt
93+
make install DESTDIR="$PWD/../staging-std"
94+
popd
95+
96+
mkdir -p "$ARCHIVE_BASE"
97+
cp -a staging-std/usr/local/bin "$ARCHIVE_BASE/"
98+
cp -a staging-std/usr/local/include "$ARCHIVE_BASE/"
99+
cp -a staging-std/usr/local/lib "$ARCHIVE_BASE/"
100+
cp -a staging-std/usr/local/share "$ARCHIVE_BASE/"
101+
102+
patchelf --set-rpath '$ORIGIN/../lib' "$ARCHIVE_BASE/bin/python${MINOR}"
103+
104+
sed -e "s|{{__VERSION_FULL__}}|${NORMALISED}|g" \
105+
-e "s|{{__ARCH__}}|riscv64|g" \
106+
installers/nix-setup-template.sh > "$ARCHIVE_BASE/setup.sh"
107+
108+
mv build_output.txt "$ARCHIVE_BASE/build_output.txt"
109+
110+
(cd "$ARCHIVE_BASE" && find . \( -type f -o -type l \) | sed 's|^\.|/|') > "$ARCHIVE_BASE/tools_structure.txt"
111+
112+
tar -czf "output/${ARCHIVE_BASE}.tar.gz" "$ARCHIVE_BASE"
113+
rm -rf "$ARCHIVE_BASE" staging-std
114+
115+
- name: Build free-threaded CPython
116+
if: ${{ inputs.freethreaded == true }}
117+
env:
118+
MINOR: ${{ steps.vars.outputs.minor }}
119+
ARCHIVE_BASE: ${{ steps.vars.outputs.archive_base }}
120+
NORMALISED: ${{ steps.vars.outputs.normalised }}
121+
run: |
122+
set -euo pipefail
123+
ft_base="${ARCHIVE_BASE}-freethreaded"
124+
pushd src
125+
git clean -fdx
126+
./configure --with-ensurepip=install --enable-shared --disable-gil
127+
make -j"$(nproc)" 2>&1 | tee ../build_output.txt
128+
make install DESTDIR="$PWD/../staging-ft"
129+
popd
130+
131+
mkdir -p "$ft_base"
132+
cp -a staging-ft/usr/local/bin "$ft_base/"
133+
cp -a staging-ft/usr/local/include "$ft_base/"
134+
cp -a staging-ft/usr/local/lib "$ft_base/"
135+
cp -a staging-ft/usr/local/share "$ft_base/"
136+
137+
patchelf --set-rpath '$ORIGIN/../lib' "$ft_base/bin/python${MINOR}"
138+
139+
sed -e "s|{{__VERSION_FULL__}}|${NORMALISED}|g" \
140+
-e "s|{{__ARCH__}}|riscv64|g" \
141+
installers/nix-setup-template.sh > "$ft_base/setup.sh"
142+
143+
mv build_output.txt "$ft_base/build_output.txt"
144+
145+
(cd "$ft_base" && find . \( -type f -o -type l \) | sed 's|^\.|/|') > "$ft_base/tools_structure.txt"
146+
147+
tar -czf "output/${ft_base}.tar.gz" "$ft_base"
148+
rm -rf "$ft_base" staging-ft
149+
150+
- name: Print tarball hashes
151+
run: sha256sum output/*.tar.gz
152+
153+
- name: Publish release
154+
env:
155+
NORMALISED: ${{ steps.vars.outputs.normalised }}
156+
run: |
157+
set -euo pipefail
158+
if gh release view "$NORMALISED" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
159+
gh release upload "$NORMALISED" --repo "$GITHUB_REPOSITORY" --clobber ./output/*.tar.gz
160+
else
161+
gh release create "$NORMALISED" --repo "$GITHUB_REPOSITORY" \
162+
--title "$NORMALISED" \
163+
--notes "Python $NORMALISED" \
164+
./output/*.tar.gz
165+
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
33+
mapfile -t refs < <(gh api --paginate repos/python/cpython/git/matching-refs/tags/v -q '.[].ref')
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: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
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 (riscv64)** workflow with
41+
`cpython_tag=v3.12.13` (optionally `freethreaded=true` for 3.13+).
42+
- Backfill: trigger **Check CPython releases** with `max_dispatches` set to
43+
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)