From d48ca4c12aed734204c85992ed010e0a91073fb4 Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Mon, 21 Sep 2026 09:06:44 +0000 Subject: [PATCH] austin-dist: Add version 4.0.0 Builds austin, the C frame-stack sampling profiler for CPython, from the v4.0.0 tag in the manylinux riscv64 image and packs the binary into the wheel with upstream's own scripts/build-wheel.py, the way release_arch.yml does for aarch64, armv7 and ppc64le. The sampler reads its target through ptrace and /proc//mem and has no architecture-specific register or unwinder code of its own, so nothing in it needed porting. The wheel carries austin alone, which is the shape of upstream's own musllinux wheels. austinp, the variant that also unwinds native stacks with libunwind, segfaults on riscv64 in two independent places, both read from core dumps of an unstripped build on the runner: events.c:83 dereferences UNKNOWN_SCOPE, the (cached_string_t*)1 sentinel austin stores when libunwind cannot name a frame - a latent upstream bug - and libunwind's own _Uelf64_lookup_symbol_from_dynamic faults under _Uriscv_get_proc_name. Leaving austinp out also removes every dependency it needed, so the build installs nothing beyond autoconf and automake. Three patches, none riscv64-specific except the first: the wheel platform table has no riscv64 entry, the wheels ship no copy of the GPL for the binary they carry, and the test suite opens /var/log/syslog after every austin call, which a journald-only host does not have. Upstream's functional and integrity suites run against the binary the wheel installs, unprivileged and as root, on each of 3.12, 3.13 and 3.14. test_where_multiprocess is deselected: upstream marks it flaky and its single-snapshot race is lost on these runners. --- .github/workflows/build-austin-dist.yml | 250 ++++++++++++++++++ docs/packages/austin-dist.yaml | 11 + ...l-add-a-riscv64-Linux-wheel-platform.patch | 31 +++ ...-ship-the-licence-files-in-the-wheel.patch | 78 ++++++ ...lerate-a-host-with-no-var-log-syslog.patch | 42 +++ 5 files changed, 412 insertions(+) create mode 100644 .github/workflows/build-austin-dist.yml create mode 100644 docs/packages/austin-dist.yaml create mode 100644 patches/austin-dist/4.0.0/0001-build-wheel-add-a-riscv64-Linux-wheel-platform.patch create mode 100644 patches/austin-dist/4.0.0/0002-build-wheel-ship-the-licence-files-in-the-wheel.patch create mode 100644 patches/austin-dist/4.0.0/0003-test-tolerate-a-host-with-no-var-log-syslog.patch diff --git a/.github/workflows/build-austin-dist.yml b/.github/workflows/build-austin-dist.yml new file mode 100644 index 0000000000..98c80d0284 --- /dev/null +++ b/.github/workflows/build-austin-dist.yml @@ -0,0 +1,250 @@ +# SPDX-FileCopyrightText: 2026 The RISE Project +# SPDX-License-Identifier: MIT +--- +# This workflow is based on: https://github.com/P403n1x87/austin/blob/v4.0.0/.github/workflows/release_arch.yml +# and https://github.com/P403n1x87/austin/blob/v4.0.0/.github/workflows/tests.yml +name: Build austin-dist wheels (riscv64) + +on: + workflow_dispatch: + inputs: + version: + description: 'Version glob to (re)build; empty builds every version of docs/packages/austin-dist.yaml not released yet' + required: false + default: '' + pull_request: + branches: [main] + paths: + - '.github/workflows/build-austin-dist.yml' + - 'docs/packages/austin-dist.yaml' + - 'patches/austin-dist/**' + push: + branches: [main] + paths: + - '.github/workflows/build-austin-dist.yml' + - 'docs/packages/austin-dist.yaml' + - 'patches/austin-dist/**' + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +permissions: + contents: read # to fetch code (actions/checkout) + +env: + MANYLINUX_RISCV64_IMAGE: quay.io/pypa/manylinux_2_39_riscv64 + UV_EXTRA_INDEX_URL: https://pypi.riseproject.dev/simple/ + # austin-python pins protobuf~=3.12 and our registry only carries 7.x, which + # first-index resolution stops at. + UV_INDEX_STRATEGY: unsafe-best-match + +jobs: + setup: + uses: $/.github/workflows/_setup.yml + with: + package: austin-dist + version: ${{ inputs.version }} + + build_wheel: + needs: [setup] + if: needs.setup.outputs.versions != '[]' + strategy: + fail-fast: false + matrix: + version: ${{ fromJSON(needs.setup.outputs.versions) }} + name: Build austin-dist ${{ matrix.version }} py3-none-manylinux_riscv64 + runs-on: ubuntu-24.04-riscv + timeout-minutes: 60 + + env: + AUSTIN_DIST_VERSION: ${{ matrix.version }} + + steps: + - name: Checkout austin v${{ matrix.version }} + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + repository: P403n1x87/austin + ref: v${{ env.AUSTIN_DIST_VERSION }} + persist-credentials: false + + - name: Checkout python-wheels + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + path: python-wheels + persist-credentials: false + + - name: Apply patches + run: git apply -v python-wheels/patches/austin-dist/${{ env.AUSTIN_DIST_VERSION }}/*.patch + + # The wheel carries austin alone, the way upstream's musl wheels do: + # austinp's native unwinding segfaults on riscv64, in austin's own MOJO + # emitter and inside libunwind's riscv64 symbol lookup. + - name: Build austin and package the wheel + run: | + docker run --rm \ + -v "$(pwd)":/workspace \ + --workdir /workspace \ + -e AUSTIN_DIST_VERSION \ + "${{ env.MANYLINUX_RISCV64_IMAGE }}" \ + bash -c ' + set -euxo pipefail + dnf -y --disablerepo=extras install autoconf automake + autoreconf --install + ./configure + make -j"$(nproc)" + /opt/python/cp312-cp312/bin/python -m pip install -q wheel + /opt/python/cp312-cp312/bin/python scripts/build-wheel.py \ + --version "$AUSTIN_DIST_VERSION" \ + --platform manylinux_2_39_riscv64 \ + --files austin:src/austin + ' + + - name: Verify the wheel ships the riscv64 binary + run: | + python3 - dist/*.whl <<'EOF' + import sys, zipfile + with zipfile.ZipFile(sys.argv[1]) as zf: + names = zf.namelist() + print("\n".join(names)) + scripts = sorted(n.rsplit("/", 1)[-1] for n in names if ".data/scripts/" in n) + assert scripts == ["austin"], scripts + licenses = sorted(n.rsplit("/", 1)[-1] for n in names if ".dist-info/licenses/" in n) + assert licenses == ["LICENSE.md"], licenses + header = zf.read(next(n for n in names if ".data/scripts/" in n))[:20] + assert header[:4] == b"\x7fELF", header + assert header[18] == 0xF3, header + EOF + + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: austin-dist-${{ env.AUSTIN_DIST_VERSION }}-py3-none-manylinux_riscv64 + path: dist/*.whl + if-no-files-found: error + + test_wheel: + name: Test austin-dist ${{ matrix.version }} on Python ${{ matrix.python-version }} + needs: [setup, build_wheel] + if: needs.setup.outputs.versions != '[]' + runs-on: ubuntu-24.04-riscv + timeout-minutes: 120 + strategy: + fail-fast: false + matrix: + version: ${{ fromJSON(needs.setup.outputs.versions) }} + # Upstream tests 3.9 to 3.14; trimmed to the interpreters this registry targets. + python-version: ['3.12', '3.13', '3.14'] + + env: + AUSTIN_DIST_VERSION: ${{ matrix.version }} + AUSTIN_TESTS_PYTHON_VERSIONS: ${{ matrix.python-version }} + + steps: + - name: Checkout austin v${{ matrix.version }} + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + repository: P403n1x87/austin + ref: v${{ env.AUSTIN_DIST_VERSION }} + persist-credentials: false + + - name: Checkout python-wheels + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + path: python-wheels + persist-credentials: false + + - name: Apply patches + run: git apply -v python-wheels/patches/austin-dist/${{ env.AUSTIN_DIST_VERSION }}/*.patch + + - name: Download wheel + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: austin-dist-${{ env.AUSTIN_DIST_VERSION }}-py3-none-manylinux_riscv64 + path: wheelhouse + + - name: Install Python + uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1 + with: + python-version: ${{ matrix.python-version }} + activate-environment: true + enable-cache: false + + - name: Install the wheel and upstream's test dependencies + run: uv pip install -r test/requirements.txt wheelhouse/*.whl + + # The suite runs the binary from src/, which is where upstream's own test + # jobs download it to; here it comes out of the installed wheel. + - name: Stage the wheel's binary + run: cp "$(command -v austin)" src/ + + # Upstream's own test jobs do this so that the suite can print a + # backtrace of a crashed austin process. + - name: Enable crash diagnostics + run: | + sudo apt-get update -qq + sudo apt-get install -y -qq --no-install-recommends gdb + echo "core.%p" | sudo tee /proc/sys/kernel/core_pattern + + - name: Run upstream's tests + run: | + ulimit -c unlimited + python -m pytest -svr a test/functional test/integrity + + # test_where_multiprocess asserts that a single -w snapshot catches both + # children inside fact(); upstream marks it flaky and these runners are + # slow enough to lose that race on every attempt. + - name: Run upstream's tests as root + run: | + ulimit -c unlimited + sudo -E env PATH="$PATH" python -m pytest -svr a test/functional test/integrity \ + --deselect test/functional/test_attach.py::test_where_multiprocess + + gpl_sources: + needs: [setup] + if: needs.setup.outputs.versions != '[]' + strategy: + fail-fast: false + matrix: + version: ${{ fromJSON(needs.setup.outputs.versions) }} + name: Collect GPL sources for austin-dist ${{ matrix.version }} + runs-on: ubuntu-24.04-riscv + + env: + AUSTIN_DIST_VERSION: ${{ matrix.version }} + + steps: + - name: Checkout python-wheels + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - uses: ./actions/collect-gpl-sources + with: + image: ${{ env.MANYLINUX_RISCV64_IMAGE }} + packages: gcc + output: gpl-sources.tar + + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: austin-dist-${{ env.AUSTIN_DIST_VERSION }}-gpl-sources + path: gpl-sources.tar + if-no-files-found: error + + publish: + name: Publish austin-dist ${{ matrix.version }} + needs: [setup, build_wheel, test_wheel, gpl_sources] + if: needs.setup.outputs.versions != '[]' + strategy: + fail-fast: false + matrix: + version: ${{ fromJSON(needs.setup.outputs.versions) }} + permissions: + contents: write + pull-requests: write + uses: $/.github/workflows/_publish-wheel.yml + secrets: + app-private-key: ${{ secrets.RISEPROJECT_APP_PRIVATE_KEY }} + with: + artifact-pattern: austin-dist-${{ matrix.version }}-py3-none-manylinux_riscv64 + gpl-sources-artifact: austin-dist-${{ matrix.version }}-gpl-sources + gpl-sources-description: gcc diff --git a/docs/packages/austin-dist.yaml b/docs/packages/austin-dist.yaml new file mode 100644 index 0000000000..72d678db7d --- /dev/null +++ b/docs/packages/austin-dist.yaml @@ -0,0 +1,11 @@ +package-name: austin-dist +source-code: https://github.com/P403n1x87/austin +license: GPL-3.0-or-later +versions: +- version: 4.0.0 + patched: true + warning: >- + The riscv64 wheel ships the austin binary only, as upstream's musllinux wheels do. + austinp, the variant that also samples native stacks, segfaults on riscv64 while + sampling: once in austin's own MOJO emitter, and once inside libunwind's riscv64 + ELF symbol lookup. diff --git a/patches/austin-dist/4.0.0/0001-build-wheel-add-a-riscv64-Linux-wheel-platform.patch b/patches/austin-dist/4.0.0/0001-build-wheel-add-a-riscv64-Linux-wheel-platform.patch new file mode 100644 index 0000000000..d1f7054cba --- /dev/null +++ b/patches/austin-dist/4.0.0/0001-build-wheel-add-a-riscv64-Linux-wheel-platform.patch @@ -0,0 +1,31 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Ludovic Henry +Date: Mon, 21 Sep 2026 00:00:00 +0000 +Subject: [PATCH] build-wheel: add a riscv64 Linux wheel platform + +scripts/build-wheel.py drives --platform off the AUSTIN_WHEELS table, so a +platform missing from it cannot be built at all: + + build-wheel.py: error: argument --platform: invalid choice: 'manylinux_2_39_riscv64' + +Add the riscv64 entry. manylinux_2_39 is the oldest manylinux profile that +exists for riscv64 (glibc 2.39, Rocky 10). The wheel carries austin alone, as +the musl wheels do: austinp needs libunwind to walk native stacks, and on +riscv64 both austin's MOJO emitter and libunwind's own ELF symbol lookup +segfault while it samples. + +Upstream-Status: To upstream [the release asset this entry names, austin--gnu-linux-riscv64.tar.xz, is not produced by upstream yet, so the table entry is only useful there together with a riscv64 leg in release_arch.yml; the port builds the binary from source and passes it with --files] + +Signed-off-by: Ludovic Henry +--- +diff --git a/scripts/build-wheel.py b/scripts/build-wheel.py +--- a/scripts/build-wheel.py ++++ b/scripts/build-wheel.py +@@ -53,6 +53,7 @@ AUSTIN_WHEELS = { + "gnu-linux-ppc64le.tar.xz", + ("austin", "austinp"), + ), ++ "manylinux_2_39_riscv64": ("gnu-linux-riscv64.tar.xz", ("austin",)), + "macosx_11_0_x86_64": ("mac64.zip", ("austin",)), + "macosx_11_0_arm64": ("mac-arm64.zip", ("austin",)), + "musllinux_1_1_aarch64": ("musl-linux-aarch64.tar.xz", ("austin",)), diff --git a/patches/austin-dist/4.0.0/0002-build-wheel-ship-the-licence-files-in-the-wheel.patch b/patches/austin-dist/4.0.0/0002-build-wheel-ship-the-licence-files-in-the-wheel.patch new file mode 100644 index 0000000000..a29cd974f8 --- /dev/null +++ b/patches/austin-dist/4.0.0/0002-build-wheel-ship-the-licence-files-in-the-wheel.patch @@ -0,0 +1,78 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Ludovic Henry +Date: Mon, 21 Sep 2026 00:00:00 +0000 +Subject: [PATCH] build-wheel: ship the licence files in the wheel + +The wheels ship the austin and austinp binaries, and austin is GPL-3.0, but +no copy of the licence travels with them: the only trace of it is the +"License: GPLv3+" metadata field. Section 4 of the GPL asks whoever +distributes the program to hand out the licence text with it, and the +binaries also carry statically linked third-party code (libunwind, and +binutils' libbfd, libiberty and libsframe for austinp) whose licences belong +in the wheel too. + +Write every file named by the new --licenses option into +/licenses/ and declare it with License-File, which needs +Metadata-Version 2.4 (PEP 639). The default keeps the project's own +LICENSE.md in every wheel. + +Upstream-Status: To upstream [not yet submitted] + +Signed-off-by: Ludovic Henry +--- +diff --git a/scripts/build-wheel.py b/scripts/build-wheel.py +--- a/scripts/build-wheel.py ++++ b/scripts/build-wheel.py +@@ -80,7 +80,7 @@ def make_message(headers, payload=None): + return message.getvalue().encode("utf-8") + + +-def write_austin_wheel(out_dir, *, version, platform, austin_bin_data): ++def write_austin_wheel(out_dir, *, version, platform, austin_bin_data, license_files): + package_name = "austin-dist" + python = "py3" + dist_name = package_name.replace("-", "_") +@@ -99,10 +99,11 @@ def write_austin_wheel(out_dir, *, version, platform, austin_bin_data): + + contents[f"{dist_info}/METADATA"] = make_message( + { +- "Metadata-Version": "2.1", ++ "Metadata-Version": "2.4", + "Name": package_name, + "Version": version, + **METADATA, ++ "License-File": [_.name for _ in license_files], + }, + Path("README.md") + .read_text("utf-8") +@@ -111,6 +112,9 @@ def write_austin_wheel(out_dir, *, version, platform, austin_bin_data): + 'img src="https://raw.githubusercontent.com/P403n1x87/austin/refs/heads/master/art', + ), + ) ++ for license_file in license_files: ++ contents[f"{dist_info}/licenses/{license_file.name}"] = license_file.read_bytes() ++ + contents[f"{dist_info}/WHEEL"] = make_message( + { + "Wheel-Version": "1.0", +@@ -179,6 +183,14 @@ if __name__ == "__main__": + default=None, + ) + ++ argp.add_argument( ++ "--licenses", ++ help="Licence files to ship in the wheel", ++ nargs="+", ++ type=Path, ++ default=[Path("LICENSE.md")], ++ ) ++ + argp.add_argument( + "--files", + help="The variant to binary file mapping (e.g. austin:src/austin)", +@@ -215,4 +227,5 @@ if __name__ == "__main__": + version=version, + platform=platform, + austin_bin_data=bin_data, ++ license_files=args.licenses, + ) diff --git a/patches/austin-dist/4.0.0/0003-test-tolerate-a-host-with-no-var-log-syslog.patch b/patches/austin-dist/4.0.0/0003-test-tolerate-a-host-with-no-var-log-syslog.patch new file mode 100644 index 0000000000..d1c6556948 --- /dev/null +++ b/patches/austin-dist/4.0.0/0003-test-tolerate-a-host-with-no-var-log-syslog.patch @@ -0,0 +1,42 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Ludovic Henry +Date: Mon, 21 Sep 2026 00:00:00 +0000 +Subject: [PATCH] test: tolerate a host with no /var/log/syslog + +collect_logs() is called from Variant.prepare_result() after every single +austin invocation, and on Linux it opens /var/log/syslog unconditionally. That +file is an rsyslog artefact: a journald-only host (Fedora, RHEL, Rocky, Arch, +or a minimal Ubuntu image like the self-hosted riscv64 runners) has none, so +the helper raises before the test under it can assert anything: + + test/utils.py:154: in collect_logs + with Path("/var/log/syslog").open(errors="replace") as logfile: + E FileNotFoundError: [Errno 2] No such file or directory: '/var/log/syslog' + + ================== 40 failed, 27 skipped in 512.75s (0:08:32) ================== + +Every failure in that run was this line; nothing about austin was exercised. +Return no logs when the file is absent, which is what the macOS branch below +already does, and leave the diagnostics untouched where rsyslog is installed. + +Upstream-Status: To upstream [not yet submitted] + +Signed-off-by: Ludovic Henry +--- +diff --git a/test/utils.py b/test/utils.py +--- a/test/utils.py ++++ b/test/utils.py +@@ -151,7 +151,12 @@ def collect_logs(variant: str, pid: int) -> List[str]: + needles: tuple[str, ...] + match platform.system(): + case "Linux": +- with Path("/var/log/syslog").open(errors="replace") as logfile: ++ syslog = Path("/var/log/syslog") ++ if not syslog.is_file(): ++ # Not every Linux host has rsyslog; on a journald-only system ++ # there is no file to grep for austin's messages. ++ return [] ++ with syslog.open(errors="replace") as logfile: + needles = (f"{variant}[{pid}]", f"systemd-coredump[{pid}]") + return [ + f" logs for {variant}[{pid}] ".center(80, "="),