From 5f86c7b42a0c76af0caa8ce7398be610e3f2ba03 Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Mon, 7 Sep 2026 00:24:34 +0200 Subject: [PATCH 1/2] dumb-init: add build-dumb-init.yml for riscv64 wheels Builds dumb-init's plain C signal-forwarding executable from an upstream checkout via cibuildwheel, mirroring the manylinux+auditwheel repair upstream's own Makefile drives. Two patches backport a testing-helper fix and replicate a PyPI-only setup.py version bump that Yelp never pushed back to the v1.2.5 tag. --- .github/workflows/build-dumb-init.yml | 89 +++++++++++++++++++ ...001-testing-eliminate-usage-of-pylib.patch | 77 ++++++++++++++++ ...1.2.5.post1-and-drop-testing-package.patch | 41 +++++++++ 3 files changed, 207 insertions(+) create mode 100644 .github/workflows/build-dumb-init.yml create mode 100644 patches/dumb-init/1.2.5.post1/0001-testing-eliminate-usage-of-pylib.patch create mode 100644 patches/dumb-init/1.2.5.post1/0002-setup.py-version-1.2.5.post1-and-drop-testing-package.patch diff --git a/.github/workflows/build-dumb-init.yml b/.github/workflows/build-dumb-init.yml new file mode 100644 index 000000000..007c6f1e3 --- /dev/null +++ b/.github/workflows/build-dumb-init.yml @@ -0,0 +1,89 @@ +# SPDX-FileCopyrightText: 2026 The RISE Project +# SPDX-License-Identifier: MIT +--- +# This workflow is based on: https://github.com/Yelp/dumb-init/blob/v1.2.5/Makefile python-dists-% target +name: Build dumb-init wheels (riscv64) + +on: + workflow_dispatch: + inputs: + version: + description: 'dumb-init version to build (PyPI version, e.g. 1.2.5.post1)' + required: true + default: '1.2.5.post1' + pull_request: + paths: + - '.github/workflows/build-dumb-init.yml' + - 'patches/dumb-init/**' + +concurrency: + group: ${{ github.workflow }}-${{ inputs.version || '1.2.5.post1' }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +permissions: + contents: read # to fetch code (actions/checkout) + +env: + # `inputs.version` is empty on pull_request events; default to 1.2.5.post1 there. + DUMB_INIT_VERSION: ${{ inputs.version || '1.2.5.post1' }} + MANYLINUX_RISCV64_IMAGE: quay.io/pypa/manylinux_2_39_riscv64 + +jobs: + setup: + uses: $/.github/workflows/_setup.yml + + build_wheel: + needs: [setup] + name: Build dumb-init ${{ inputs.version || '1.2.5.post1' }} py2.py3-none-manylinux_riscv64 + runs-on: ubuntu-24.04-riscv + timeout-minutes: 30 + + steps: + # The v1.2.5 tag's setup.py has no .post1 suffix; 0002-*.patch adds it to + # match the sdist Yelp actually uploaded to PyPI as 1.2.5.post1. + - name: Determine upstream git tag + run: echo "DUMB_INIT_TAG=v${DUMB_INIT_VERSION%.post*}" >> "$GITHUB_ENV" + + - name: Checkout dumb-init ${{ env.DUMB_INIT_TAG }} + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + repository: Yelp/dumb-init + ref: ${{ env.DUMB_INIT_TAG }} + persist-credentials: false + + - name: Checkout python-wheels + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + path: python-wheels + persist-credentials: false + + - name: Patch dumb-init source + run: git apply python-wheels/patches/dumb-init/${{ env.DUMB_INIT_VERSION }}/*.patch + + - name: Build wheel + uses: pypa/cibuildwheel@1828c10ab37f080699c7b81cea34097c684a7074 # v4.2.0 + with: + output-dir: wheelhouse/ + # setup.py's own bdist_wheel override forces the py2.py3-none tag, so one build serves every interpreter. + only: cp312-manylinux_riscv64 + env: + CIBW_MANYLINUX_RISCV64_IMAGE: ${{ env.MANYLINUX_RISCV64_IMAGE }} + CIBW_TEST_REQUIRES: pytest pytest-timeout<2.0.0 + CIBW_TEST_SOURCES: tests testing VERSION pytest.ini + CIBW_TEST_COMMAND: python -m pytest tests -vv + + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: dumb-init-${{ env.DUMB_INIT_VERSION }}-py2.py3-none-manylinux_riscv64 + path: wheelhouse/*.whl + if-no-files-found: error + + publish: + name: Publish dumb-init ${{ inputs.version || '1.2.5.post1' }} + needs: [setup, build_wheel] + permissions: + contents: write + pull-requests: write + uses: $/.github/workflows/_publish-wheel.yml + with: + artifact-pattern: dumb-init-${{ inputs.version || '1.2.5.post1' }}-*-manylinux_riscv64 diff --git a/patches/dumb-init/1.2.5.post1/0001-testing-eliminate-usage-of-pylib.patch b/patches/dumb-init/1.2.5.post1/0001-testing-eliminate-usage-of-pylib.patch new file mode 100644 index 000000000..fa69835c2 --- /dev/null +++ b/patches/dumb-init/1.2.5.post1/0001-testing-eliminate-usage-of-pylib.patch @@ -0,0 +1,77 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Ludovic Henry +Date: Sun, 6 Sep 2026 00:00:00 +0200 +Subject: [PATCH] testing: eliminate usage of pylib + +v1.2.5's testing/__init__.py imports `py._path.local.LocalPath` from the +standalone py package, which recent pytest releases no longer pull in +as a dependency, so `pip install pytest` alone leaves `import testing` +failing with ModuleNotFoundError: No module named 'py'. Backports +upstream's later fixes, replacing it with the os/os.path calls already +imported by the module and tolerating the "0" process state GitHub's +own QEMU runners can report. + +Upstream-Status: Backport [https://github.com/Yelp/dumb-init/commit/2c39101849abb835e9f368e3cf5c9570362a9589] +--- + testing/__init__.py | 25 +++++++++++++++++-------- + 1 file changed, 17 insertions(+), 8 deletions(-) + +diff --git a/testing/__init__.py b/testing/__init__.py +index 665df65..e860e09 100644 +--- a/testing/__init__.py ++++ b/testing/__init__.py +@@ -8,9 +8,6 @@ from contextlib import contextmanager + from subprocess import PIPE + from subprocess import Popen + +-from py._path.local import LocalPath +- +- + # these signals cause dumb-init to suspend itself + SUSPEND_SIGNALS = frozenset([ + signal.SIGTSTP, +@@ -49,14 +46,24 @@ def print_signals(args=()): + def child_pids(pid): + """Return a list of direct child PIDs for the given PID.""" + children = set() +- for p in LocalPath('/proc').listdir(): ++ for p in os.listdir('/proc'): + try: +- stat = open(p.join('stat').strpath).read() +- m = re.match(r'^\d+ \(.+?\) [a-zA-Z] (\d+) ', stat) ++ with open(os.path.join('/proc', p, 'stat')) as f: ++ stat = f.read() ++ m = re.match( ++ r'^\d+ \(.+?\) ' ++ # This field, state, is normally a single letter, but can be ++ # "0" if there are some unusual security settings that prevent ++ # reading the process state (happens under GitHub Actions with ++ # QEMU for some reason). ++ '[0a-zA-Z] ' ++ r'(\d+) ', ++ stat, ++ ) + assert m, stat + ppid = int(m.group(1)) + if ppid == pid: +- children.add(int(p.basename)) ++ children.add(int(p)) + except OSError: + # Happens when the process exits after listing it, or between + # opening stat and reading it. +@@ -76,12 +83,13 @@ def pid_tree(pid): + + def is_alive(pid): + """Return whether a process is running with the given PID.""" +- return LocalPath('/proc').join(str(pid)).isdir() ++ return os.path.isdir(os.path.join('/proc', str(pid))) + + + def process_state(pid): + """Return a process' state, such as "stopped" or "running".""" +- status = LocalPath('/proc').join(str(pid), 'status').read() ++ with open(os.path.join('/proc', str(pid), 'status')) as f: ++ status = f.read() + m = re.search(r'^State:\s+[A-Z] \(([a-z]+)\)$', status, re.MULTILINE) + return m.group(1) + diff --git a/patches/dumb-init/1.2.5.post1/0002-setup.py-version-1.2.5.post1-and-drop-testing-package.patch b/patches/dumb-init/1.2.5.post1/0002-setup.py-version-1.2.5.post1-and-drop-testing-package.patch new file mode 100644 index 000000000..8df6701d8 --- /dev/null +++ b/patches/dumb-init/1.2.5.post1/0002-setup.py-version-1.2.5.post1-and-drop-testing-package.patch @@ -0,0 +1,41 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Ludovic Henry +Date: Sun, 6 Sep 2026 00:00:00 +0200 +Subject: [PATCH] setup.py: version 1.2.5.post1, drop the dev-only testing/ + package + +The v1.2.5 tag's setup.py has no `packages=` argument and computes +`version` from the bare VERSION file, but the actual dumb-init-1.2.5.post1 +sdist Yelp uploaded to PyPI carries a setup.py with `version=... + ".post1"` +and an explicit `packages=[]` -- a metadata fix applied to the release +artifact and never pushed back to this tag (there is no newer tag on +GitHub). Without this, `pip download dumb-init==1.2.5.post1` and a build +from this checkout produce different version strings, and the checkout's +build also ships the tests/-only `testing` helper package inside the +wheel (visible: `dumb_init-1.2.5.data/purelib/testing/`), which the real +PyPI wheel excludes. + +Upstream-Status: Inappropriate [Yelp's own PyPI release process applied this to the uploaded sdist but never committed or tagged it in git; there is no upstream ref to submit against] +--- + setup.py | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/setup.py b/setup.py +index 0ec2f18..dd9e59b 100644 +--- a/setup.py ++++ b/setup.py +@@ -121,10 +121,11 @@ class build_cexe(Command): + setup( + name='dumb-init', + description='Simple wrapper script which proxies signals to a child', +- version=open('VERSION').read().strip(), ++ version=open('VERSION').read().strip() + ".post1", + author='Yelp', + url='https://github.com/Yelp/dumb-init/', + platforms='linux', ++ packages=[], + c_executables=[Extension('dumb-init', ['dumb-init.c'])], + cmdclass={ + 'bdist_wheel': bdist_wheel, +-- +2.43.0 From 34582d3c8cbc7f49600cb3f2a24713c0e6157de1 Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Mon, 7 Sep 2026 01:02:22 +0200 Subject: [PATCH 2/2] dumb-init: drop stale pytest-timeout<2.0.0 pin (imports removed distutils on cp312+) --- .github/workflows/build-dumb-init.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-dumb-init.yml b/.github/workflows/build-dumb-init.yml index 007c6f1e3..d8fcbd278 100644 --- a/.github/workflows/build-dumb-init.yml +++ b/.github/workflows/build-dumb-init.yml @@ -68,7 +68,10 @@ jobs: only: cp312-manylinux_riscv64 env: CIBW_MANYLINUX_RISCV64_IMAGE: ${{ env.MANYLINUX_RISCV64_IMAGE }} - CIBW_TEST_REQUIRES: pytest pytest-timeout<2.0.0 + # pytest-timeout<2.0.0 (upstream's own pin) imports distutils.version.LooseVersion, + # which cp312+ dropped entirely (PEP 632); a modern pytest-timeout has no such + # dependency, so let pip pick the latest instead of upstream's stale Python-2-era cap. + CIBW_TEST_REQUIRES: pytest pytest-timeout CIBW_TEST_SOURCES: tests testing VERSION pytest.ini CIBW_TEST_COMMAND: python -m pytest tests -vv