diff --git a/.github/workflows/build-scylla-driver.yml b/.github/workflows/build-scylla-driver.yml new file mode 100644 index 0000000000..de35b4cd8e --- /dev/null +++ b/.github/workflows/build-scylla-driver.yml @@ -0,0 +1,138 @@ +# SPDX-FileCopyrightText: 2026 The RISE Project +# SPDX-License-Identifier: MIT +--- +# This workflow is based on: https://github.com/scylladb/python-driver/blob/master/.github/workflows/lib-build.yml +name: Build scylla-driver wheels (riscv64) + +on: + workflow_dispatch: + inputs: + version: + description: 'Version glob to (re)build; empty builds every version of docs/packages/scylla-driver.yaml not released yet' + required: false + default: '' + pull_request: + branches: [main] + paths: + - '.github/workflows/build-scylla-driver.yml' + - 'docs/packages/scylla-driver.yaml' + - 'patches/scylla-driver/**' + push: + branches: [main] + paths: + - '.github/workflows/build-scylla-driver.yml' + - 'docs/packages/scylla-driver.yaml' + - 'patches/scylla-driver/**' + +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: scylla-driver + version: ${{ inputs.version }} + + build_wheels: + needs: [setup] + if: needs.setup.outputs.versions != '[]' + name: Build scylla-driver ${{ matrix.version }} ${{ matrix.python }}-manylinux_riscv64 + runs-on: ubuntu-24.04-riscv + timeout-minutes: 240 + strategy: + fail-fast: false + matrix: + version: ${{ fromJSON(needs.setup.outputs.versions) }} + python: + - "cp312" + - "cp313" + - "cp314" + - "cp314t" + + env: + SCYLLA_DRIVER_VERSION: ${{ matrix.version }} + + steps: + - name: Checkout scylla-driver ${{ env.SCYLLA_DRIVER_VERSION }} + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + repository: scylladb/python-driver + ref: ${{ env.SCYLLA_DRIVER_VERSION }}-scylla + persist-credentials: false + + - name: Checkout python-wheels + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + path: python-wheels + persist-credentials: false + + # auditwheel bundles the image's libev, whose notice the wheel would otherwise drop. + - name: Patch scylla-driver source + run: git apply python-wheels/patches/scylla-driver/${{ env.SCYLLA_DRIVER_VERSION }}/00*.patch + + - name: Build wheels + uses: pypa/cibuildwheel@1828c10ab37f080699c7b81cea34097c684a7074 # v4.2.0 + with: + output-dir: wheelhouse/ + only: ${{ matrix.python }}-manylinux_riscv64 + env: + CIBW_MANYLINUX_RISCV64_IMAGE: ${{ env.MANYLINUX_RISCV64_IMAGE }} + CIBW_BUILD_FRONTEND: build # upstream's build[uv] needs a host uv the runner has none of (gotcha 13) + # Upstream's before-build, minus the AlmaLinux key and the packages Rocky 10 preinstalls. + CIBW_BEFORE_BUILD: rm -rf ~/.pyxbld && yum install -y libev libev-devel + # The three CASS_DRIVER/CFLAGS values are upstream's [tool.cibuildwheel] environment, + # which this replaces; EXTENSIONS_ARE_MUST is what fails a degraded pure-Python build. + CIBW_ENVIRONMENT: >- + CASS_DRIVER_BUILD_CONCURRENCY=2 + CASS_DRIVER_BUILD_EXTENSIONS_ARE_MUST=yes + CFLAGS='-g0 -O3' + PIP_EXTRA_INDEX_URL=https://pypi.riseproject.dev/simple/ + # Upstream's dev group, minus ccm (a git dependency only the integration tests use) + # and minus gevent on cp314t, which it has no build for. setuptools is what gives + # pyximport a distutils on 3.12+ (gotcha 211). + CIBW_TEST_GROUPS: '' + CIBW_TEST_REQUIRES: >- + pytest~=8.0 PyYAML pure-sasl twisted[tls] eventlet>=0.33.3 cython>=3.2 + packaging>=25.0 futurist numpy objgraph setuptools + ${{ matrix.python == 'cp314t' && '' || 'gevent' }} + # PIP_PREFER_BINARY keeps pip on our registry's gevent instead of compiling + # PyPI's newer release. + CIBW_TEST_ENVIRONMENT: PIP_PREFER_BINARY=1 + # cassandra/ would shadow the installed wheel, so only the two .pyx files + # tests/unit/cython/types_testhelper.pyx includes by relative path are staged. + CIBW_TEST_SOURCES: tests cassandra/ioutils.pyx cassandra/cython_marshal.pyx + CIBW_TEST_COMMAND: >- + python -c "import importlib.metadata as m; f=[str(p) for p in m.files('scylla-driver')]; assert any(s.endswith('licenses/LICENSE.libev') for s in f), f" && + python -m pytest tests/unit + ${{ matrix.python == 'cp314t' && '' || '&& EVENT_LOOP_MANAGER=gevent python -m pytest tests/unit/io/test_geventreactor.py' }} + + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: scylla-driver-${{ env.SCYLLA_DRIVER_VERSION }}-${{ matrix.python }}-manylinux_riscv64 + path: wheelhouse/*.whl + if-no-files-found: error + + publish: + name: Publish scylla-driver ${{ 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: scylla-driver-${{ matrix.version }}-*-manylinux_riscv64 diff --git a/docs/packages/scylla-driver.yaml b/docs/packages/scylla-driver.yaml new file mode 100644 index 0000000000..dc359283b6 --- /dev/null +++ b/docs/packages/scylla-driver.yaml @@ -0,0 +1,6 @@ +package-name: scylla-driver +source-code: https://github.com/scylladb/python-driver/ +license: Apache-2.0 +versions: +- version: 3.29.11 + patched: true diff --git a/patches/scylla-driver/3.29.11/0001-Ship-the-licence-of-the-bundled-libev-in-the-wheel.patch b/patches/scylla-driver/3.29.11/0001-Ship-the-licence-of-the-bundled-libev-in-the-wheel.patch new file mode 100644 index 0000000000..6e2b34b6eb --- /dev/null +++ b/patches/scylla-driver/3.29.11/0001-Ship-the-licence-of-the-bundled-libev-in-the-wheel.patch @@ -0,0 +1,73 @@ +From ca306846abdb1396e55893b56e5d368975218bf9 Mon Sep 17 00:00:00 2001 +From: Ludovic Henry +Date: Mon, 21 Sep 2026 11:47:03 +0000 +Subject: [PATCH] Ship the licence of the bundled libev in the wheel + +cassandra.io.libevwrapper links libev dynamically, so auditwheel copies the +system libev.so.4 into scylla_driver.libs/ and rewrites the extension's RPATH +to it: every manylinux wheel redistributes libev in binary form. libev is +dual-licensed BSD-2-Clause OR GPL-2.0-or-later and its BSD terms require the +copyright notice to travel with the binary, but the wheel carries only the +driver's own Apache-2.0 LICENSE. + +pyproject.toml declares no license-files, so setuptools' default +LICEN[CS]E* root glob picks the new file up and no packaging change is +needed. The text is the notice from libev 4.33, the version the manylinux +images package. + +Upstream-Status: To upstream [not yet submitted; this port may only touch riseproject-dev/python-wheels] +--- + LICENSE.libev | 41 +++++++++++++++++++++++++++++++++++++++++ + 1 file changed, 41 insertions(+) + create mode 100644 LICENSE.libev + +diff --git a/LICENSE.libev b/LICENSE.libev +new file mode 100644 +index 0000000..04b35e7 +--- /dev/null ++++ b/LICENSE.libev +@@ -0,0 +1,41 @@ ++The libev shared library the cassandra.io.libevwrapper extension links against ++is redistributed inside the binary wheel. libev is not part of this project and ++carries the licence below; the driver's own licence is in LICENSE. ++ ++All files in libev are ++Copyright (c)2007,2008,2009,2010,2011,2012,2013 Marc Alexander Lehmann. ++ ++Redistribution and use in source and binary forms, with or without ++modification, are permitted provided that the following conditions are ++met: ++ ++ * Redistributions of source code must retain the above copyright ++ notice, this list of conditions and the following disclaimer. ++ ++ * Redistributions in binary form must reproduce the above ++ copyright notice, this list of conditions and the following ++ disclaimer in the documentation and/or other materials provided ++ with the distribution. ++ ++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ++"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT ++LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR ++A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT ++OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, ++SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT ++LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, ++DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY ++THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ++(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE ++OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ++ ++Alternatively, the contents of this package may be used under the terms ++of the GNU General Public License ("GPL") version 2 or any later version, ++in which case the provisions of the GPL are applicable instead of the ++above. If you wish to allow the use of your version of this package only ++under the terms of the GPL and not to allow others to use your version of ++this file under the BSD license, indicate your decision by deleting the ++provisions above and replace them with the notice and other provisions ++required by the GPL in this and the other files of this package. If you do ++not delete the provisions above, a recipient may use your version of this ++file under either the BSD or the GPL. +-- +2.43.0 + diff --git a/patches/scylla-driver/3.29.11/0002-Restart-the-libev-prepare-watcher-after-the-cleanup-t.patch b/patches/scylla-driver/3.29.11/0002-Restart-the-libev-prepare-watcher-after-the-cleanup-t.patch new file mode 100644 index 0000000000..2b2fa0bd75 --- /dev/null +++ b/patches/scylla-driver/3.29.11/0002-Restart-the-libev-prepare-watcher-after-the-cleanup-t.patch @@ -0,0 +1,41 @@ +From ad54ddf761910e54d651b3398cd69abbce3bfb14 Mon Sep 17 00:00:00 2001 +From: Ludovic Henry +Date: Mon, 21 Sep 2026 16:46:54 +0000 +Subject: [PATCH] Restart the libev prepare watcher after the cleanup test + +LibevLoop._cleanup() stops the shared prepare watcher ("Stop the prepare +watcher first to prevent race conditions"), and nothing ever starts it again. +test_watchers_are_finished calls that cleanup on the process-wide _global_loop +and then only resets _shutdown, so the loop it hands to the rest of the session +has no prepare watcher: LibevLoop._loop_will_run - and with it _update_timer, +the only caller that arms _loop_timer - never runs again, and TimerManager +timeouts are never serviced. + +Every timer created afterwards therefore stays pending forever, and the next +test, LibevTimerTest.test_multi_timer_validation, spins in +submit_and_wait_for_completion's unbounded wait loop until the job is killed. +Restart the watcher alongside the _shutdown reset so the loop is whole again. + +Upstream-Status: To upstream [not yet submitted; this port may only touch riseproject-dev/python-wheels] +--- + tests/unit/io/test_libevreactor.py | 4 ++++ + 1 file changed, 4 insertions(+) + +diff --git a/tests/unit/io/test_libevreactor.py b/tests/unit/io/test_libevreactor.py +index cf7e7ca..6804290 100644 +--- a/tests/unit/io/test_libevreactor.py ++++ b/tests/unit/io/test_libevreactor.py +@@ -87,6 +87,10 @@ class LibevConnectionTest(ReactorTestMixin, unittest.TestCase): + assert conn._read_watcher.stop.mock_calls + + _global_loop._shutdown = False ++ # _cleanup() stops the loop's prepare watcher and nothing restarts it, so ++ # LibevLoop._update_timer never runs again and no timer created later in ++ # this process is ever serviced. The timer tests below then wait forever. ++ _global_loop._preparer.start() + + + class LibevTimerPatcher(unittest.TestCase): +-- +2.43.0 + diff --git a/patches/scylla-driver/3.29.11/0003-Fix-Session._set_keyspace_for_all_pools-to-report-al.patch b/patches/scylla-driver/3.29.11/0003-Fix-Session._set_keyspace_for_all_pools-to-report-al.patch new file mode 100644 index 0000000000..52fba34d12 --- /dev/null +++ b/patches/scylla-driver/3.29.11/0003-Fix-Session._set_keyspace_for_all_pools-to-report-al.patch @@ -0,0 +1,37 @@ +From 853f22b277e6602a6125f82086b087f711447a5d Mon Sep 17 00:00:00 2001 +From: sylwiaszunejko +Date: Thu, 18 Jun 2026 12:05:14 +0200 +Subject: [PATCH] Fix Session._set_keyspace_for_all_pools to report all + pools' errors + +The final callback was invoked with host_errors (the errors from only +the last pool to finish) instead of the accumulated errors dict. If the +last pool succeeded, failures from other pools were silently lost. Pass +the aggregated errors dict, matching the method's docstring. + +tests/unit/test_cluster.py::SessionTest::test_set_keyspace_for_all_pools_reports_all_errors ships in the +3.29.11 tag and fails on every architecture; the fix landed on master three days after the +release was cut. + +Upstream-Status: Backport [https://github.com/scylladb/python-driver/commit/26c201a74c21d038c2bb4b45f1c074a5f40cad2a] +(cherry picked from commit 26c201a74c21d038c2bb4b45f1c074a5f40cad2a) +--- + cassandra/cluster.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/cassandra/cluster.py b/cassandra/cluster.py +index 1181c6f..fdbdfb8 100644 +--- a/cassandra/cluster.py ++++ b/cassandra/cluster.py +@@ -3439,7 +3439,7 @@ class Session(object): + errors[pool.host] = host_errors + + if not remaining_callbacks: +- callback(host_errors) ++ callback(errors) + + for pool in tuple(self._pools.values()): + pool._set_keyspace_for_all_conns(keyspace, pool_finished_setting_keyspace) +-- +2.43.0 + diff --git a/patches/scylla-driver/3.29.11/0004-fix-add-missing-scope-validation-in-Session.wait_for.patch b/patches/scylla-driver/3.29.11/0004-fix-add-missing-scope-validation-in-Session.wait_for.patch new file mode 100644 index 0000000000..697c3eae9a --- /dev/null +++ b/patches/scylla-driver/3.29.11/0004-fix-add-missing-scope-validation-in-Session.wait_for.patch @@ -0,0 +1,42 @@ +From 3e6b32bb6dded5b4f5bdf1f4975c98f15ec16037 Mon Sep 17 00:00:00 2001 +From: Yaniv Michael Kaul +Date: Mon, 29 Jun 2026 23:32:46 +0300 +Subject: [PATCH] fix: add missing scope validation in + Session.wait_for_schema_agreement + +The docstring and test promised ValueError for invalid scope values +(e.g. 'planet'), but the validation was never implemented in the method +body. Add an explicit check against the three SchemaAgreementScope +members. + +Introduced in commit 0d215f45b (cluster: add Session.wait_for_schema_agreement). + +tests/unit/test_cluster.py::SessionTest::test_wait_for_schema_agreement_rejects_unknown_scope ships in +the 3.29.11 tag and fails on every architecture; the fix landed on master two weeks after the +release was cut. + +Upstream-Status: Backport [https://github.com/scylladb/python-driver/commit/03c4c9601580bf433217b54d39d1e804ad3e959a] +(cherry picked from commit 03c4c9601580bf433217b54d39d1e804ad3e959a) +--- + cassandra/cluster.py | 5 +++++ + 1 file changed, 5 insertions(+) + +diff --git a/cassandra/cluster.py b/cassandra/cluster.py +index fdbdfb8..795aafe 100644 +--- a/cassandra/cluster.py ++++ b/cassandra/cluster.py +@@ -3482,6 +3482,11 @@ class Session(object): + if wait_time is not None and wait_time <= 0: + raise ValueError("wait_time must be greater than 0") + ++ if scope not in (SchemaAgreementScope.RACK, SchemaAgreementScope.DC, SchemaAgreementScope.CLUSTER): ++ raise ValueError( ++ "scope must be SchemaAgreementScope.RACK, .DC, or .CLUSTER" ++ ) ++ + total_timeout = wait_time if wait_time is not None else self.cluster.max_schema_agreement_wait + if total_timeout <= 0: + raise ValueError("total_timeout must be greater than 0") +-- +2.43.0 +