From ef001f1e5e7bcf6addf518419c4063d8fd8b7795 Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Tue, 13 Oct 2026 02:26:07 +0000 Subject: [PATCH 1/3] Add riscv64 build for glean-sdk 70.0.0 Maturin uniffi-bindings build (glean-core/bundle, cdylib loaded via ctypes), not a thin pure-Python wrapper over a prebuilt binary and not pyo3: upstream's own CI (CircleCI, not GitHub Actions) just runs `maturin build` per target, cross-compiling additional Linux arches with Zig. The wheel is py3-none (no CPython ABI), so cibuildwheel builds it once. Verified by building the real v70.0.0 tag locally with maturin and running upstream's full pytest suite against the resulting wheel (154 passed, 1 skipped, matching upstream's own skip). --- .github/workflows/build-glean-sdk.yml | 111 ++++++++++++++++++++++++++ docs/packages/glean-sdk.yaml | 5 ++ 2 files changed, 116 insertions(+) create mode 100644 .github/workflows/build-glean-sdk.yml create mode 100644 docs/packages/glean-sdk.yaml diff --git a/.github/workflows/build-glean-sdk.yml b/.github/workflows/build-glean-sdk.yml new file mode 100644 index 000000000000..16d1fd6731c0 --- /dev/null +++ b/.github/workflows/build-glean-sdk.yml @@ -0,0 +1,111 @@ +# SPDX-FileCopyrightText: 2026 The RISE Project +# SPDX-License-Identifier: MIT +--- +# This workflow is based on upstream's own `make build-python-wheel` (a plain +# `maturin build`, see Makefile) as driven by the `pypi-linux-release`/ +# `pypi-linux-additional-releases` jobs of: +# https://github.com/mozilla/glean/blob/v70.0.0/.circleci/config.yml +name: Build glean-sdk wheels (riscv64) + +on: + workflow_dispatch: + inputs: + version: + description: 'Version glob to (re)build; empty builds every version of docs/packages/glean-sdk.yaml not released yet' + required: false + default: '' + pull_request: + branches: [main] + paths: + - '.github/workflows/build-glean-sdk.yml' + - 'docs/packages/glean-sdk.yaml' + push: + branches: [main] + paths: + - '.github/workflows/build-glean-sdk.yml' + - 'docs/packages/glean-sdk.yaml' + +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 + +jobs: + setup: + uses: $/.github/workflows/_setup.yml + with: + package: glean-sdk + version: ${{ inputs.version }} + + build_wheels: + needs: [setup] + if: needs.setup.outputs.versions != '[]' + strategy: + fail-fast: false + matrix: + version: ${{ fromJSON(needs.setup.outputs.versions) }} + name: Build glean-sdk ${{ matrix.version }} py3-none-manylinux_riscv64 + runs-on: ubuntu-24.04-riscv + timeout-minutes: 90 + + env: + GLEAN_SDK_VERSION: ${{ matrix.version }} + + steps: + - name: Checkout glean v${{ env.GLEAN_SDK_VERSION }} + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + repository: mozilla/glean + ref: v${{ env.GLEAN_SDK_VERSION }} + persist-credentials: false + + - name: Build wheel + uses: pypa/cibuildwheel@1828c10ab37f080699c7b81cea34097c684a7074 # v4.2.0 + with: + output-dir: wheelhouse/ + # bindings = "uniffi" generates ctypes bindings around a plain + # cdylib, so the wheel is py3-none (no CPython ABI); one build + # serves every interpreter. + only: cp312-manylinux_riscv64 + env: + CIBW_MANYLINUX_RISCV64_IMAGE: ${{ env.MANYLINUX_RISCV64_IMAGE }} + # glean-sdk ships no [tool.cibuildwheel], so the Rust toolchain + # its maturin backend needs is installed in-container. + CIBW_BEFORE_ALL_LINUX: >- + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y + CIBW_ENVIRONMENT_LINUX: PATH="$PATH:$HOME/.cargo/bin" + CIBW_TEST_REQUIRES: pytest pytest-localserver semver jsonschema MarkupSafe + CIBW_TEST_SOURCES: glean-core/python/tests glean.1.schema.json + CIBW_TEST_COMMAND: >- + python -c "import glean._uniffi as m, os; + so = os.path.join(os.path.dirname(m.__file__), 'libglean_ffi.so'); + assert os.path.isfile(so), so" && + python -m pytest -v glean-core/python/tests + + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: glean-sdk-${{ env.GLEAN_SDK_VERSION }}-py3-none-manylinux_riscv64 + path: wheelhouse/*.whl + if-no-files-found: error + + publish: + name: Publish glean-sdk ${{ matrix.version }} + needs: [setup, build_wheels] + 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: glean-sdk-${{ matrix.version }}-*-manylinux_riscv64 diff --git a/docs/packages/glean-sdk.yaml b/docs/packages/glean-sdk.yaml new file mode 100644 index 000000000000..88d325e8b076 --- /dev/null +++ b/docs/packages/glean-sdk.yaml @@ -0,0 +1,5 @@ +package-name: glean-sdk +source-code: https://github.com/mozilla/glean +license: MPL-2.0 +versions: +- version: 70.0.0 From 55263c10d78677ed21cfb949704da91c9ba989b0 Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Thu, 22 Oct 2026 10:56:18 +0000 Subject: [PATCH 2/3] glean-sdk: raise the ping upload wait timeouts in the test suite glean's Python SDK uploads pings from a freshly spawned interpreter that has to import glean and load libglean_ffi.so first. The test helpers wait_for_ping() and wait_for_requests() give that 2 seconds, which the riscv64 runner's cold start regularly exceeds: 12 tests failed with "No ping appeared ... within 2 seconds" while tests on the same upload path passed in the same run. Patch both defaults to 30 seconds (matching wait_for_server() in the same conftest); the helpers poll, so faster hardware pays nothing. Upstream-Status: To upstream [fixed timeout sized for fast hardware, not riscv64-specific; not yet submitted] --- .github/workflows/build-glean-sdk.yml | 9 ++++ docs/packages/glean-sdk.yaml | 1 + ...-raise-the-ping-upload-wait-timeouts.patch | 53 +++++++++++++++++++ 3 files changed, 63 insertions(+) create mode 100644 patches/glean-sdk/70.0.0/0001-tests-raise-the-ping-upload-wait-timeouts.patch diff --git a/.github/workflows/build-glean-sdk.yml b/.github/workflows/build-glean-sdk.yml index 16d1fd6731c0..bbf06b32ca25 100644 --- a/.github/workflows/build-glean-sdk.yml +++ b/.github/workflows/build-glean-sdk.yml @@ -64,6 +64,15 @@ jobs: ref: v${{ env.GLEAN_SDK_VERSION }} persist-credentials: false + - name: Checkout python-wheels + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + path: python-wheels + persist-credentials: false + + - name: Patch glean source + run: git apply python-wheels/patches/glean-sdk/${{ env.GLEAN_SDK_VERSION }}/*.patch + - name: Build wheel uses: pypa/cibuildwheel@1828c10ab37f080699c7b81cea34097c684a7074 # v4.2.0 with: diff --git a/docs/packages/glean-sdk.yaml b/docs/packages/glean-sdk.yaml index 88d325e8b076..ffab1271870b 100644 --- a/docs/packages/glean-sdk.yaml +++ b/docs/packages/glean-sdk.yaml @@ -3,3 +3,4 @@ source-code: https://github.com/mozilla/glean license: MPL-2.0 versions: - version: 70.0.0 + patched: true diff --git a/patches/glean-sdk/70.0.0/0001-tests-raise-the-ping-upload-wait-timeouts.patch b/patches/glean-sdk/70.0.0/0001-tests-raise-the-ping-upload-wait-timeouts.patch new file mode 100644 index 000000000000..601cd278e0f4 --- /dev/null +++ b/patches/glean-sdk/70.0.0/0001-tests-raise-the-ping-upload-wait-timeouts.patch @@ -0,0 +1,53 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Ludovic Henry +Date: Thu, 22 Oct 2026 00:00:00 +0000 +Subject: [PATCH] tests: raise the ping upload wait timeouts + +The Python SDK uploads pings from a separate process: every upload batch +spawns a fresh `sys.executable _process_dispatcher_helper.py`, which has to +start an interpreter, import glean and load libglean_ffi.so before the test +uploader writes its info.txt (or the test HTTP server sees a request). + +Helpers.wait_for_ping() and wait_for_requests() give that only 2 seconds. +On the riscv64 runner that cold start alone regularly takes longer, so 12 +tests fail with "No ping appeared ... within 2 seconds" / "Expected 1 +requests within 2 seconds. Got 0" while tests exercising the very same +upload path (test_sending_deletion_ping_if_disabled_outside_of_run, +test_ping_collection_must_happen_after_currently_scheduled_metrics_recordings, +...) pass in the same run. + +Raise both defaults to 30 seconds, matching wait_for_server() in the same +conftest. Both helpers poll and return as soon as the ping/request lands, so +faster hardware pays nothing. The one negative check that passes an +explicit timeout=1 (test_uploader_capabilities_in_empty_ping) is untouched. + +Upstream-Status: To upstream [fixed timeout sized for fast hardware, not riscv64-specific; not yet submitted] +--- + glean-core/python/tests/conftest.py | 2 +- + glean-core/python/tests/test_glean.py | 2 +- + 2 files changed, 2 insertions(+), 2 deletions(-) + +diff --git a/glean-core/python/tests/conftest.py b/glean-core/python/tests/conftest.py +--- a/glean-core/python/tests/conftest.py ++++ b/glean-core/python/tests/conftest.py +@@ -93,7 +93,7 @@ + + class Helpers: + @staticmethod +- def wait_for_ping(path, timeout=2) -> (str, str): ++ def wait_for_ping(path, timeout=30) -> (str, str): + """ + Wait for a ping to appear in `path` for at most `timeout` seconds. + +diff --git a/glean-core/python/tests/test_glean.py b/glean-core/python/tests/test_glean.py +--- a/glean-core/python/tests/test_glean.py ++++ b/glean-core/python/tests/test_glean.py +@@ -39,7 +39,7 @@ + ROOT = Path(__file__).parent + + +-def wait_for_requests(server, n=1, timeout=2): ++def wait_for_requests(server, n=1, timeout=30): + """ + Wait for `n` requests to be received by the server. + From 16535ae8dfd9d1cee8485e1004f8e324d1133ce3 Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Fri, 23 Oct 2026 16:48:29 +0000 Subject: [PATCH 3/3] glean-sdk: wait for the upload process in test_client_activity_api With the ping upload timeouts raised, a single test still failed on riscv64: test_client_activity_api timed out waiting 30 seconds for the "inactive" baseline ping. That is not a timeout issue. The upload process only uploads the pings it finds in pending_pings/ when it starts, and ProcessDispatcher does not start a new one while the previous one is still running. The test only sleeps 0.1 seconds after the first ping lands before submitting the next one; on riscv64 the first upload process is still exiting by then, so the trigger for the next ping is dropped and the ping is never sent. Patch the test to call ProcessDispatcher._wait_for_last_process() instead, as test_network.py already does, so every trigger starts a fresh upload process. Also add patches/glean-sdk/** to the workflow's pull_request and push paths, like the other patched packages, so a patch change rebuilds. Upstream-Status: To upstream [race in the test exposed by slow hardware, not riscv64-specific; not yet submitted] --- .github/workflows/build-glean-sdk.yml | 2 + ...-process-in-test_client_activity_api.patch | 62 +++++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 patches/glean-sdk/70.0.0/0002-tests-wait-for-the-upload-process-in-test_client_activity_api.patch diff --git a/.github/workflows/build-glean-sdk.yml b/.github/workflows/build-glean-sdk.yml index bbf06b32ca25..14688935b338 100644 --- a/.github/workflows/build-glean-sdk.yml +++ b/.github/workflows/build-glean-sdk.yml @@ -19,11 +19,13 @@ on: paths: - '.github/workflows/build-glean-sdk.yml' - 'docs/packages/glean-sdk.yaml' + - 'patches/glean-sdk/**' push: branches: [main] paths: - '.github/workflows/build-glean-sdk.yml' - 'docs/packages/glean-sdk.yaml' + - 'patches/glean-sdk/**' concurrency: group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} diff --git a/patches/glean-sdk/70.0.0/0002-tests-wait-for-the-upload-process-in-test_client_activity_api.patch b/patches/glean-sdk/70.0.0/0002-tests-wait-for-the-upload-process-in-test_client_activity_api.patch new file mode 100644 index 000000000000..788d387cd1d7 --- /dev/null +++ b/patches/glean-sdk/70.0.0/0002-tests-wait-for-the-upload-process-in-test_client_activity_api.patch @@ -0,0 +1,62 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Ludovic Henry +Date: Fri, 23 Oct 2026 00:00:00 +0000 +Subject: [PATCH] tests: wait for the upload process in test_client_activity_api + +test_client_activity_api submits a baseline ping, waits for the uploader +to record it, sleeps 0.1 seconds and submits the next baseline ping. + +The upload process only uploads the pings it finds in pending_pings/ when +it starts, and ProcessDispatcher.dispatch() does not start a new one while +the previous one is still running. On the riscv64 runner the upload +process is still winding down (interpreter teardown) 0.1 seconds after it +uploaded the first ping, so the trigger for the "inactive" baseline ping is +dropped, the ping is never uploaded and the test fails with "No ping +appeared ... within 30 seconds" whatever the timeout. + +Wait for the upload process to exit instead, like test_network.py does +with ProcessDispatcher._wait_for_last_process(), so the next trigger +always starts a fresh upload process. + +Upstream-Status: To upstream [race in the test exposed by slow hardware, not riscv64-specific; not yet submitted] +--- + glean-core/python/tests/test_glean.py | 13 +++++++------ + 1 file changed, 7 insertions(+), 6 deletions(-) + +diff --git a/glean-core/python/tests/test_glean.py b/glean-core/python/tests/test_glean.py +--- a/glean-core/python/tests/test_glean.py ++++ b/glean-core/python/tests/test_glean.py +@@ -32,6 +32,7 @@ from glean.metrics import ( + StringMetricType, + ) + from glean.net import PingUploadWorker ++from glean._process_dispatcher import ProcessDispatcher + from glean.testing import _RecordingUploader + from glean._uniffi import glean_set_test_mode + +@@ -850,9 +851,9 @@ def test_client_activity_api(tmpdir, monkeypatch, helpers): + # It's an empty ping. + assert "metrics" not in payload + +- # The upload process is fast, but not fast enough to communicate its status. +- # We give it just a blink of an eye to wind down. +- time.sleep(0.1) ++ # The upload process only sees pings already on disk when it starts, and ++ # a new one isn't spawned while it is still running. Wait for it to exit. ++ ProcessDispatcher._wait_for_last_process() + # Making it inactive + Glean.handle_client_inactive() + +@@ -861,9 +862,9 @@ def test_client_activity_api(tmpdir, monkeypatch, helpers): + assert payload["ping_info"]["reason"] == "inactive" + assert "glean.baseline.duration" in payload["metrics"]["timespan"] + +- # The upload process is fast, but not fast enough to communicate its status. +- # We give it just a blink of an eye to wind down. +- time.sleep(0.1) ++ # The upload process only sees pings already on disk when it starts, and ++ # a new one isn't spawned while it is still running. Wait for it to exit. ++ ProcessDispatcher._wait_for_last_process() + # Once more active + Glean.handle_client_active() +