diff --git a/.github/workflows/build-pyinstaller.yml b/.github/workflows/build-pyinstaller.yml new file mode 100644 index 00000000..c426074d --- /dev/null +++ b/.github/workflows/build-pyinstaller.yml @@ -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_ 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("- + --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 }}