Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
215 changes: 215 additions & 0 deletions .github/workflows/build-pyinstaller.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
# SPDX-FileCopyrightText: 2026 The RISE Project
# SPDX-License-Identifier: MIT
#
# This workflow is based on: https://github.com/pyinstaller/pyinstaller/blob/develop/release/linux-bootloaders,
# https://github.com/pyinstaller/pyinstaller/blob/develop/release/build-wheels and
# https://github.com/pyinstaller/pyinstaller/blob/develop/.github/workflows/ci.yml
name: Build PyInstaller wheels (riscv64)

on:
workflow_dispatch:
inputs:
version:
description: 'PyInstaller version to build (git tag without leading v, e.g. 6.22.2)'
required: true
default: '6.22.2'
pull_request:
paths:
- '.github/workflows/build-pyinstaller.yml'

concurrency:
group: ${{ github.workflow }}-${{ inputs.version || '6.22.2' }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true

permissions:
contents: read

env:
PYINSTALLER_VERSION: ${{ inputs.version || '6.22.2' }}
MANYLINUX_RISCV64_IMAGE: quay.io/pypa/manylinux_2_39_riscv64
# PyInstaller's own name for the bootloader directory on 64-bit RISC-V glibc
# (PyInstaller/_shared_with_waf.py maps machine "riscv*" to "riscv").
PYI_PLATFORM: Linux-64bit-riscv
# Upstream ships manylinux2014_<arch> wheels, but manylinux2014 is not defined
# for riscv64; the bootloader is built against the image's glibc 2.39 instead.
PYI_WHEEL_TAG: manylinux_2_39_riscv64

jobs:
build_wheel:
name: Build PyInstaller ${{ inputs.version || '6.22.2' }} py3-none-manylinux_riscv64
runs-on: ubuntu-24.04-riscv

steps:
- name: Checkout PyInstaller v${{ env.PYINSTALLER_VERSION }}
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
repository: pyinstaller/pyinstaller
ref: v${{ env.PYINSTALLER_VERSION }}
persist-credentials: false

- name: Compile bootloader and build wheel
shell: bash
run: |
podman run -t \
--log-driver=none \
--network=host \
-v "$(pwd)":/workspace \
--workdir /workspace \
-e PYI_PLATFORM="${PYI_PLATFORM}" \
-e PYI_WHEEL_TAG="${PYI_WHEEL_TAG}" \
--pull=newer \
"${MANYLINUX_RISCV64_IMAGE}" \
bash -euxo pipefail -c '
# libcmocka lives in Rocky 10 CRB, which manylinux enables.
dnf -y install libcmocka-devel
export PATH="/opt/python/cp312-cp312/bin:$PATH"
python -m pip install --upgrade hatchling
(cd bootloader && python ./waf --tests all)
python -m hatchling build -t wheel
'

- name: Verify wheel contents
shell: bash
run: |
python3 - dist/*.whl <<'EOF'
import struct
import sys
import zipfile

wheel = sys.argv[1]
assert wheel.endswith("-py3-none-manylinux_2_39_riscv64.whl"), wheel

archive = zipfile.ZipFile(wheel)
names = set(archive.namelist())

bootloaders = {name for name in names if name.startswith("PyInstaller/bootloader/Linux")}
expected = {
"PyInstaller/bootloader/Linux-64bit-riscv/run",
"PyInstaller/bootloader/Linux-64bit-riscv/run_d",
}
assert bootloaders == expected, bootloaders

for name in sorted(bootloaders):
data = archive.read(name)
assert data[:4] == b"\x7fELF", name
e_machine, = struct.unpack_from("<H", data, 0x12)
assert e_machine == 0xF3, (name, hex(e_machine)) # EM_RISCV
print(f"{name}: {len(data)} bytes, EM_RISCV")

assert any(n.endswith(".dist-info/licenses/COPYING.txt") for n in names), sorted(names)[:10]
EOF

- name: Store wheel
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: pyinstaller-${{ env.PYINSTALLER_VERSION }}-py3-none-manylinux_riscv64
path: dist/*.whl
overwrite: true
retention-days: 1
if-no-files-found: error
compression-level: 0

test_wheel:
name: Test PyInstaller ${{ inputs.version || '6.22.2' }} on ${{ matrix.python-version }}
needs: [build_wheel]
runs-on: ubuntu-24.04-riscv
strategy:
fail-fast: false
matrix:
# Upstream's CI matrix has no free-threaded entry, and neither does the
# set of interpreters it publishes wheels for, so cp314t is left out.
python-version: ['3.12', '3.13', '3.14']
include:
# python-build-standalone, which setup-uv provisions for 3.13+, builds
# _ctypes and OpenSSL into the interpreter and ships no Tcl/Tk, so these
# assert on the interpreter build rather than on the wheel. Identical on
# x86_64; the 3.12 entry gets the runner's own interpreter and keeps them.
- python-version: '3.13'
deselect: >-
--deselect tests/unit/test_modulegraph_more.py::test_extension
--deselect "tests/functional/test_basic.py::test_symlink_basename_is_kept[onedir-splash]"
--deselect "tests/functional/test_basic.py::test_symlink_basename_is_kept[onefile-splash]"
--deselect tests/functional/test_binary_vs_data_reclassification.py::test_automatic_reclassification_binary
--deselect tests/functional/test_multipackage.py::test_spec_with_multipackage
--deselect tests/functional/test_libraries.py::test_idlelib
--deselect tests/functional/test_libraries.py::test_tkinter_tcl_library_dir
- python-version: '3.14'
deselect: >-
--deselect tests/unit/test_modulegraph_more.py::test_extension
--deselect "tests/functional/test_basic.py::test_symlink_basename_is_kept[onedir-splash]"
--deselect "tests/functional/test_basic.py::test_symlink_basename_is_kept[onefile-splash]"
--deselect tests/functional/test_binary_vs_data_reclassification.py::test_automatic_reclassification_binary
--deselect tests/functional/test_multipackage.py::test_spec_with_multipackage
--deselect tests/functional/test_libraries.py::test_idlelib
--deselect tests/functional/test_libraries.py::test_tkinter_tcl_library_dir

steps:
- name: Checkout PyInstaller v${{ env.PYINSTALLER_VERSION }}
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
repository: pyinstaller/pyinstaller
ref: v${{ env.PYINSTALLER_VERSION }}
persist-credentials: false

- uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
with:
python-version: ${{ matrix.python-version }}
activate-environment: true
enable-cache: false

- name: Download wheel
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: pyinstaller-${{ env.PYINSTALLER_VERSION }}-py3-none-manylinux_riscv64
path: dist

# test_basic.py::test_user_preferred_locale and ::test_time_module_localized
# need these locales, exactly as upstream's ubuntu CI job does.
- name: Install and enable additional locales
run: |
sudo apt-get update -qq
sudo apt-get install -qq --no-install-recommends locales
sudo locale-gen en_US.UTF-8 en_US sl_SI.UTF-8 sl_SI cs_CZ.UTF-8

- name: Install PyInstaller and test dependencies
env:
UV_EXTRA_INDEX_URL: https://pypi.riseproject.dev/simple/
UV_INDEX_STRATEGY: unsafe-best-match
run: |
uv pip install dist/*.whl
uv pip install --requirement tests/requirements-base.txt

# Run from a directory holding only the test suite so that `import
# PyInstaller` resolves to the installed wheel and not to the checkout.
- name: Stage test suite
run: |
mkdir testrun
cp -a tests pytest.ini testrun/

- name: Run tests
working-directory: testrun
env:
# pytest.ini's 600s per-test timeout is sized for upstream's x86 CI; each
# functional test freezes and runs a whole application, which is slower here.
PYTEST_ADDOPTS: --timeout=1800
run: |
python -c "import PyInstaller; assert 'site-packages' in PyInstaller.__file__, PyInstaller.__file__; print(PyInstaller.__file__)"
python -m pytest -n 4 --durations 10 ${{ matrix.deselect }} tests/unit tests/functional

publish:
name: Publish PyInstaller ${{ inputs.version || '6.22.2' }} to GitLab
needs: [build_wheel, test_wheel]
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write

steps:
- name: Publish wheels and open docs PR
uses: riseproject-dev/python-wheels/actions/publish-wheels@main
with:
artifact-pattern: pyinstaller-${{ env.PYINSTALLER_VERSION }}-*-manylinux_riscv64
gitlab-username: ${{ vars.GITLAB_DEPLOY_USER }}
gitlab-token: ${{ secrets.GITLAB_DEPLOY_TOKEN }}
gitlab-project-id: ${{ vars.GITLAB_PROJECT_ID }}
gh-token: ${{ secrets.GITHUB_TOKEN }}
Loading