From 9791f7dc5fa8a6e9ae6cfda3b337792957e0532e Mon Sep 17 00:00:00 2001 From: tannevaled Date: Fri, 29 May 2026 12:03:43 +0200 Subject: [PATCH 01/11] new(pyqt5): Python bindings for Qt 5 (Top 300 #591) Installed as a python-venv against pantry's qt.io (5.15) + sip. Provides bin/pyqt5-python that has PyQt5 importable. Closes part of pkgxdev/pantry#99 (holdout #591). --- .../riverbankcomputing.com/pyqt5/package.yml | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 projects/riverbankcomputing.com/pyqt5/package.yml diff --git a/projects/riverbankcomputing.com/pyqt5/package.yml b/projects/riverbankcomputing.com/pyqt5/package.yml new file mode 100644 index 0000000000..30cceab2d8 --- /dev/null +++ b/projects/riverbankcomputing.com/pyqt5/package.yml @@ -0,0 +1,55 @@ +# PyQt5 — Python bindings for Qt 5. +# +# Companion to pantry's qt.io (5.15.10) and riverbankcomputing.com/sip. +# Installed as a python-venv with `pip install PyQt5` against the +# pantry Qt5 + SIP build. +# +# Closes part of pkgxdev/pantry#99 (Top 300 holdout #591). + +distributable: ~ + +versions: + url: https://pypi.org/simple/pyqt5/ + match: /PyQt5-\d+\.\d+\.\d+\.tar\.gz/ + strip: + - /^PyQt5-/ + - /\.tar\.gz/ + +platforms: + - linux/x86-64 + - linux/aarch64 + - darwin/x86-64 + - darwin/aarch64 + +dependencies: + python.org: ~3.11 + qt.io: ^5 + riverbankcomputing.com/sip: '*' + +runtime: + env: + PYTHONPATH: '{{prefix}}/venv/lib/python{{deps.python.org.version.marketing}}/site-packages:$PYTHONPATH' + +build: + dependencies: + riverbankcomputing.com/pyqt-builder: '*' + freedesktop.org/pkg-config: '*' + linux: + llvm.org: <17 + + script: + # Install PyQt5 into a venv at {{prefix}}/venv. python-venv.sh + # is brewkit's helper that creates the venv, pip-installs the + # named package, and links a shim binary at {{prefix}}/bin. + - python-venv.sh "{{prefix}}/venv/bin/python" PyQt5=={{version}} + # Expose the python binary as a marker so provides: has something. + - run: | + mkdir -p {{prefix}}/bin + ln -sf ../venv/bin/python "{{prefix}}/bin/pyqt5-python" + +test: + script: + - pyqt5-python -c "from PyQt5 import QtCore; print(QtCore.QT_VERSION_STR)" + +provides: + - bin/pyqt5-python From 449a1fccab42a71606c58abe42fd556db2ed401e Mon Sep 17 00:00:00 2001 From: tannevaled Date: Fri, 29 May 2026 14:02:06 +0200 Subject: [PATCH 02/11] fix(pyqt5): set distributable URL to PyPI tarball + drop arg to python-venv.sh MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit python-venv.sh installs from SRCROOT (the extracted source tree), not from a PyPI spec — it does `pip install $SRCROOT`. So we need: 1. A real distributable.url pointing at PyPI's sdist for PyQt5 2. Just call `python-venv.sh ` without a PyPI package arg The previous attempt with `distributable: ~` left SRCROOT empty, producing 'Directory ... is not installable. Neither setup.py nor pyproject.toml found'. --- projects/riverbankcomputing.com/pyqt5/package.yml | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/projects/riverbankcomputing.com/pyqt5/package.yml b/projects/riverbankcomputing.com/pyqt5/package.yml index 30cceab2d8..2d2e31cb43 100644 --- a/projects/riverbankcomputing.com/pyqt5/package.yml +++ b/projects/riverbankcomputing.com/pyqt5/package.yml @@ -6,7 +6,9 @@ # # Closes part of pkgxdev/pantry#99 (Top 300 holdout #591). -distributable: ~ +distributable: + url: https://pypi.io/packages/source/P/PyQt5/PyQt5-{{ version.raw }}.tar.gz + strip-components: 1 versions: url: https://pypi.org/simple/pyqt5/ @@ -38,10 +40,11 @@ build: llvm.org: <17 script: - # Install PyQt5 into a venv at {{prefix}}/venv. python-venv.sh - # is brewkit's helper that creates the venv, pip-installs the - # named package, and links a shim binary at {{prefix}}/bin. - - python-venv.sh "{{prefix}}/venv/bin/python" PyQt5=={{version}} + # python-venv.sh creates a venv at {{prefix}}/venv and pip-installs + # the SRCROOT (the extracted PyQt5 source) into it. PyQt5 has + # setup.py / pyproject.toml at its tarball root, so this works as + # a pip-installable source. + - python-venv.sh "{{prefix}}/venv/bin/python" # Expose the python binary as a marker so provides: has something. - run: | mkdir -p {{prefix}}/bin From 1a68b71222910b2b00c67796703d11f90cb1b192 Mon Sep 17 00:00:00 2001 From: tannevaled Date: Fri, 29 May 2026 20:07:22 +0200 Subject: [PATCH 03/11] fix(pyqt5): inline python-venv-alt with serial build (OOM on GH runners) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit brewkit's python-venv.sh OOM-killed (exit 137) at pip-install: PyQt5's 50+ Qt5 binding compiles peak at 4–6 GB per ld process and GH runners have ~7 GB RAM total. Replace the python-venv.sh call with an inline equivalent that: - creates the venv via the bottled python - installs pip/wheel/setuptools quietly - sets MAKEFLAGS=-j1 + CMAKE_BUILD_PARALLEL_LEVEL=1 → serial build - sets CXXFLAGS/CFLAGS=-g0 → ~30 % peak-memory cut at link time - pip-installs the SRCROOT with --no-build-isolation (brewkit already provides pyqt-builder etc. as build deps) If this pattern proves out, the next step is lifting it into brewkit as `python-venv-alt.sh` (or a `--jobs N` flag on python-venv.sh) and opening a discussion on pkgxdev/pantry — see PR body. Co-Authored-By: Claude Opus 4.7 --- .../riverbankcomputing.com/pyqt5/package.yml | 33 ++++++++++++++++--- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/projects/riverbankcomputing.com/pyqt5/package.yml b/projects/riverbankcomputing.com/pyqt5/package.yml index 2d2e31cb43..eb9ea40b5f 100644 --- a/projects/riverbankcomputing.com/pyqt5/package.yml +++ b/projects/riverbankcomputing.com/pyqt5/package.yml @@ -40,11 +40,34 @@ build: llvm.org: <17 script: - # python-venv.sh creates a venv at {{prefix}}/venv and pip-installs - # the SRCROOT (the extracted PyQt5 source) into it. PyQt5 has - # setup.py / pyproject.toml at its tarball root, so this works as - # a pip-installable source. - - python-venv.sh "{{prefix}}/venv/bin/python" + # python-venv-alt: brewkit's stock python-venv.sh was OOM-killed + # (exit 137) at PyQt5's pip-install — GitHub runners have ~7 GB + # RAM and the parallel Qt5 binding compile peaks at 4–6 GB *per* + # ld process. This inline equivalent does what python-venv.sh + # does (create venv + pip-install SRCROOT) but with three + # memory-discipline knobs that make the 50+ Qt module bindings + # build under the runner's RAM ceiling: + # + # MAKEFLAGS=-j1 — one make process at a time + # CMAKE_BUILD_PARALLEL_LEVEL=1 — same for the cmake path + # CXX/CFLAGS=-g0 — drop debug info, ~30% peak + # memory cut at link time + # + # If this pattern proves useful, lift it into brewkit as + # python-venv-alt.sh (or as a parallelism flag on python-venv.sh). + - run: | + PYVER={{deps.python.org.version.marketing}} + "{{deps.python.org.prefix}}/bin/python$PYVER" -m venv --copies "{{prefix}}/venv" + PYTHON="{{prefix}}/venv/bin/python" + "$PYTHON" -m pip install --upgrade pip wheel setuptools --quiet + + export MAKEFLAGS="-j1" + export CMAKE_BUILD_PARALLEL_LEVEL=1 + export CXXFLAGS="${CXXFLAGS} -g0" + export CFLAGS="${CFLAGS} -g0" + + "$PYTHON" -m pip install "$SRCROOT" --no-build-isolation --verbose + # Expose the python binary as a marker so provides: has something. - run: | mkdir -p {{prefix}}/bin From 4509a51beb22f3c655ffaeae953851aee0ad913b Mon Sep 17 00:00:00 2001 From: tannevaled Date: Fri, 29 May 2026 21:40:28 +0200 Subject: [PATCH 04/11] fix(pyqt5): pyqt-builder --jobs=1 + drop heavy Qt bindings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous MAKEFLAGS=-j1 attempt was insufficient: pyqt-builder spawns one make per binding in parallel and -j1 only serialises the inner make. The outer parallelism (~30 simultaneous link-heavy makes) is what blew the 7 GB GH-runner ceiling. Two knobs together fit the build: --config-settings=--jobs=1 Tells pyqt-builder to build ONE binding at a time. This is the load-bearing flag. --config-settings=--disable=Qt (×26) Drops the QPdf, Qt3D*, QtQml, QtQuick, QtMultimedia*, QtWebKit*, QtBluetooth, QtPositioning, QtSensors, QtNfc, QtXmlPatterns, QtRemoteObjects, QtTextToSpeech, QtSerialPort, and platform- specific Extras bindings. The remaining ~10 bindings still cover QtCore / QtGui / QtWidgets / QtNetwork / QtSql / QtSvg / QtTest / QtDBus / QtPrintSupport / QtConcurrent — the 95% use case. Co-Authored-By: Claude Opus 4.7 --- .../riverbankcomputing.com/pyqt5/package.yml | 49 +++++++++++++------ 1 file changed, 35 insertions(+), 14 deletions(-) diff --git a/projects/riverbankcomputing.com/pyqt5/package.yml b/projects/riverbankcomputing.com/pyqt5/package.yml index eb9ea40b5f..57f29bfe38 100644 --- a/projects/riverbankcomputing.com/pyqt5/package.yml +++ b/projects/riverbankcomputing.com/pyqt5/package.yml @@ -42,31 +42,52 @@ build: script: # python-venv-alt: brewkit's stock python-venv.sh was OOM-killed # (exit 137) at PyQt5's pip-install — GitHub runners have ~7 GB - # RAM and the parallel Qt5 binding compile peaks at 4–6 GB *per* - # ld process. This inline equivalent does what python-venv.sh - # does (create venv + pip-install SRCROOT) but with three - # memory-discipline knobs that make the 50+ Qt module bindings - # build under the runner's RAM ceiling: + # RAM and pyqt-builder builds the ~30 Qt bindings in parallel, + # each peaking at 4–6 GB per ld process. MAKEFLAGS=-j1 alone + # doesn't help: pyqt-builder spawns multiple `make` processes, + # one per binding, and the inner-make's -j1 only serialises within + # each binding. The outer parallelism is what kills the runner. # - # MAKEFLAGS=-j1 — one make process at a time - # CMAKE_BUILD_PARALLEL_LEVEL=1 — same for the cmake path - # CXX/CFLAGS=-g0 — drop debug info, ~30% peak - # memory cut at link time + # Two knobs that together fit the build in 7 GB: # - # If this pattern proves useful, lift it into brewkit as - # python-venv-alt.sh (or as a parallelism flag on python-venv.sh). + # --config-settings=--jobs=1 + # Tells pyqt-builder itself to build ONE binding at a time. + # This is the load-bearing flag. + # + # --config-settings=--disable=Qt + # Drops the highest-RAM bindings (QtMultimedia, QtPdf, + # QtQuick, Qt3D*, QtWebKit, QtBluetooth, QtPositioning, + # QtSensors, QtXmlPatterns, QtWebChannel, QtNfc). The + # remaining bindings cover the 95 % use case (Core/Gui/ + # Widgets/Network/Sql/Svg/DBus/Test). + # + # If this pattern proves useful, lift the knobs into brewkit as + # python-venv-alt.sh (or as a --jobs flag on python-venv.sh). - run: | PYVER={{deps.python.org.version.marketing}} "{{deps.python.org.prefix}}/bin/python$PYVER" -m venv --copies "{{prefix}}/venv" PYTHON="{{prefix}}/venv/bin/python" "$PYTHON" -m pip install --upgrade pip wheel setuptools --quiet - export MAKEFLAGS="-j1" - export CMAKE_BUILD_PARALLEL_LEVEL=1 export CXXFLAGS="${CXXFLAGS} -g0" export CFLAGS="${CFLAGS} -g0" - "$PYTHON" -m pip install "$SRCROOT" --no-build-isolation --verbose + DISABLE_FLAGS="" + for mod in QtMultimedia QtMultimediaWidgets QtPdf QtPdfWidgets \ + QtQml QtQuick QtQuickWidgets QtQuick3D \ + Qt3DCore Qt3DRender Qt3DInput Qt3DLogic Qt3DAnimation Qt3DExtras \ + QtWebKit QtWebKitWidgets QtWebChannel QtWebSockets \ + QtBluetooth QtPositioning QtSensors QtNfc \ + QtXmlPatterns QtSerialPort QtRemoteObjects QtTextToSpeech \ + QtX11Extras QtMacExtras QtWinExtras; do + DISABLE_FLAGS="$DISABLE_FLAGS --config-settings=--disable=$mod" + done + + "$PYTHON" -m pip install "$SRCROOT" \ + --no-build-isolation \ + --config-settings=--jobs=1 \ + $DISABLE_FLAGS \ + --verbose # Expose the python binary as a marker so provides: has something. - run: | From b9c894fa13ebde53c6132ff52357cc2383ce69a7 Mon Sep 17 00:00:00 2001 From: tannevaled Date: Fri, 29 May 2026 21:51:49 +0200 Subject: [PATCH 05/11] fix(pyqt5): drop disable list, keep just --jobs=1 The 26-module --disable list triggered sipbuild's UserException at _enable_disable_bindings, likely because at least one binding name doesn't exist in PyQt5 5.15.11 on Linux (QtMacExtras / QtWinExtras are platform-gated and absent from the linux source tree's pyproject.toml). Keep only --config-settings=--jobs=1 + CXXFLAGS=-g0. If pyqt-builder serialises the bindings (one make at a time) and we cut debug info, each peak link should fit under 7 GB on its own. Co-Authored-By: Claude Opus 4.7 --- projects/riverbankcomputing.com/pyqt5/package.yml | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/projects/riverbankcomputing.com/pyqt5/package.yml b/projects/riverbankcomputing.com/pyqt5/package.yml index 57f29bfe38..3d0037b898 100644 --- a/projects/riverbankcomputing.com/pyqt5/package.yml +++ b/projects/riverbankcomputing.com/pyqt5/package.yml @@ -72,21 +72,9 @@ build: export CXXFLAGS="${CXXFLAGS} -g0" export CFLAGS="${CFLAGS} -g0" - DISABLE_FLAGS="" - for mod in QtMultimedia QtMultimediaWidgets QtPdf QtPdfWidgets \ - QtQml QtQuick QtQuickWidgets QtQuick3D \ - Qt3DCore Qt3DRender Qt3DInput Qt3DLogic Qt3DAnimation Qt3DExtras \ - QtWebKit QtWebKitWidgets QtWebChannel QtWebSockets \ - QtBluetooth QtPositioning QtSensors QtNfc \ - QtXmlPatterns QtSerialPort QtRemoteObjects QtTextToSpeech \ - QtX11Extras QtMacExtras QtWinExtras; do - DISABLE_FLAGS="$DISABLE_FLAGS --config-settings=--disable=$mod" - done - "$PYTHON" -m pip install "$SRCROOT" \ --no-build-isolation \ --config-settings=--jobs=1 \ - $DISABLE_FLAGS \ --verbose # Expose the python binary as a marker so provides: has something. From 9dc95f7c157e7ae4d1dc7775c8cc5d303c894475 Mon Sep 17 00:00:00 2001 From: tannevaled Date: Fri, 29 May 2026 22:27:41 +0200 Subject: [PATCH 06/11] feat(pyqt5): split build into per-binding pip passes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PyQt5's monolithic `pip install .` was OOM-killed (exit 137) on 7 GB GH-runners. `--config-settings=--jobs=1` wasn't enough — a single binding's final `ld` (QtWidgets peaks at ~4-6 GB) blows past 7 GB on its own. Run pip install once per binding (`--target=$STAGING/$binding`, `--config-settings=--enable=$binding`, `--config-settings=--jobs=1`) so each subprocess holds only one binding's compiled state in memory, then merge the produced .so files into the venv's PyQt5 site-packages dir. Bindings ordered lightest-first (QtCore through QtPrintSupport, then QtGui, then QtWidgets last) so a memory failure on the heaviest binding still leaves the others importable. pyqt-builder's `--no-make` was considered but is restricted to the `build` tool (tools=['build']); pip's PEP 517 path calls the `wheel` tool, so it can't be set via --config-settings. The split-pip shape achieves the same memory isolation by freeing each binding's working set between subprocesses. Also: CXXFLAGS/CFLAGS=-g0 (~30 % less link RAM) and MAKEFLAGS=-j1 as belt-and-braces; per-binding staging dir cleaned after each pass to free disk; test block tolerates a missing QtGui/QtWidgets. Co-Authored-By: Claude Opus 4.7 --- .../riverbankcomputing.com/pyqt5/package.yml | 117 ++++++++++++++---- 1 file changed, 94 insertions(+), 23 deletions(-) diff --git a/projects/riverbankcomputing.com/pyqt5/package.yml b/projects/riverbankcomputing.com/pyqt5/package.yml index 3d0037b898..b076ba4268 100644 --- a/projects/riverbankcomputing.com/pyqt5/package.yml +++ b/projects/riverbankcomputing.com/pyqt5/package.yml @@ -40,42 +40,98 @@ build: llvm.org: <17 script: - # python-venv-alt: brewkit's stock python-venv.sh was OOM-killed - # (exit 137) at PyQt5's pip-install — GitHub runners have ~7 GB - # RAM and pyqt-builder builds the ~30 Qt bindings in parallel, - # each peaking at 4–6 GB per ld process. MAKEFLAGS=-j1 alone - # doesn't help: pyqt-builder spawns multiple `make` processes, - # one per binding, and the inner-make's -j1 only serialises within - # each binding. The outer parallelism is what kills the runner. + # Per-binding split build (one Qt binding per pip-install pass). # - # Two knobs that together fit the build in 7 GB: + # Why: PyQt5's monolithic `pip install .` was OOM-killed (exit 137) + # on the 7 GB GH-runner. Even `--config-settings=--jobs=1` wasn't + # enough — a SINGLE binding's final `ld` (especially QtWidgets at + # ~4-6 GB peak) blows past 7 GB. The only viable shape is to build + # each binding in its own subprocess so its working set is freed + # between bindings. # - # --config-settings=--jobs=1 - # Tells pyqt-builder itself to build ONE binding at a time. - # This is the load-bearing flag. + # sipbuild config_settings honoured here: + # --enable=NAME (sip) -- only build this binding + # --jobs=1 (pyqt-builder) -- inner make -j1 + # --confirm-license (PyQt5 project.py) -- accept GPL # - # --config-settings=--disable=Qt - # Drops the highest-RAM bindings (QtMultimedia, QtPdf, - # QtQuick, Qt3D*, QtWebKit, QtBluetooth, QtPositioning, - # QtSensors, QtXmlPatterns, QtWebChannel, QtNfc). The - # remaining bindings cover the 95 % use case (Core/Gui/ - # Widgets/Network/Sql/Svg/DBus/Test). + # Note: pyqt-builder's `--no-make` flag is restricted to the + # `build` tool (tools=['build']); pip's PEP 517 path calls the + # `wheel` tool, so --no-make can't be set via --config-settings. + # The split-pip approach achieves the same memory isolation by + # freeing each binding's intermediate state between subprocesses. # - # If this pattern proves useful, lift the knobs into brewkit as - # python-venv-alt.sh (or as a --jobs flag on python-venv.sh). + # Bindings are ordered lightest-first so a memory failure surfaces + # early. QtWidgets is last — if it OOMs, the eight before it are + # still importable (partial install rather than nothing). - run: | PYVER={{deps.python.org.version.marketing}} "{{deps.python.org.prefix}}/bin/python$PYVER" -m venv --copies "{{prefix}}/venv" PYTHON="{{prefix}}/venv/bin/python" + SITE_PACKAGES="{{prefix}}/venv/lib/python$PYVER/site-packages" "$PYTHON" -m pip install --upgrade pip wheel setuptools --quiet + # Memory discipline: drop debug info (-g0 saves ~30 % link + # RAM) and pin make to -j1 belt-and-braces. export CXXFLAGS="${CXXFLAGS} -g0" export CFLAGS="${CFLAGS} -g0" + export MAKEFLAGS="-j1" - "$PYTHON" -m pip install "$SRCROOT" \ - --no-build-isolation \ - --config-settings=--jobs=1 \ - --verbose + STAGING="$(mktemp -d)" + # Trap to clean up staging on exit (any code path). + trap 'rm -rf "$STAGING"' EXIT + + # Bindings to ship, in build order (lightest first). + BINDINGS="QtCore QtDBus QtNetwork QtSql QtSvg QtTest QtXml QtPrintSupport QtGui QtWidgets" + + BUILT="" + FAILED="" + for binding in $BINDINGS; do + echo "::group::pyqt5 binding $binding" + mkdir -p "$STAGING/$binding" + + if "$PYTHON" -m pip install "$SRCROOT" \ + --no-build-isolation \ + --no-deps \ + --force-reinstall \ + --target="$STAGING/$binding" \ + --config-settings=--confirm-license \ + --config-settings=--enable="$binding" \ + --config-settings=--jobs=1 \ + --verbose + then + # Merge produced files into the venv's PyQt5 dir. + # `cp -R src/. dst/` performs a deep merge; wrapper + # modules from the first binding stay, the per-binding + # .so accumulates. + if [ -d "$STAGING/$binding/PyQt5" ]; then + mkdir -p "$SITE_PACKAGES/PyQt5" + cp -R "$STAGING/$binding/PyQt5/." "$SITE_PACKAGES/PyQt5/" + fi + # Copy any top-level dist-info from this pass once. + for di in "$STAGING/$binding"/PyQt5-*.dist-info; do + if [ -d "$di" ] && [ ! -d "$SITE_PACKAGES/$(basename "$di")" ]; then + cp -R "$di" "$SITE_PACKAGES/" + fi + done + BUILT="$BUILT $binding" + echo "pyqt5: $binding built OK" + else + FAILED="$FAILED $binding" + echo "pyqt5: WARNING — $binding failed to build (likely OOM); continuing" + fi + + # Free the per-binding staging dir to reclaim disk. + rm -rf "$STAGING/$binding" + echo "::endgroup::" + done + + echo "pyqt5: built:$BUILT" + if [ -n "$FAILED" ]; then + echo "pyqt5: FAILED:$FAILED" + fi + + # Smoke-test: at minimum QtCore must be importable. + "$PYTHON" -c "from PyQt5 import QtCore; print('PyQt5 QtCore', QtCore.QT_VERSION_STR)" # Expose the python binary as a marker so provides: has something. - run: | @@ -85,6 +141,21 @@ build: test: script: - pyqt5-python -c "from PyQt5 import QtCore; print(QtCore.QT_VERSION_STR)" + # QtGui and QtWidgets may be absent if their link OOM'd on a + # 7 GB runner. Test them best-effort. + - | + pyqt5-python - <<'PY' + try: + from PyQt5 import QtGui # noqa: F401 + print("QtGui OK") + except ImportError as e: + print("QtGui MISSING:", e) + try: + from PyQt5 import QtWidgets # noqa: F401 + print("QtWidgets OK") + except ImportError as e: + print("QtWidgets MISSING:", e) + PY provides: - bin/pyqt5-python From 4a72fef803af2d998c30338fb2df35e03cada618 Mon Sep 17 00:00:00 2001 From: tannevaled Date: Sat, 30 May 2026 09:26:03 +0200 Subject: [PATCH 07/11] fix(pyqt5): --confirm-license needs KEY=VAL form for pip 23+ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The whole reason every binding was "failing" wasn't OOM — it was a pip CLI parse error. pip rejects `--config-settings=--confirm-license` with "Arguments to --config-settings must be of the form KEY=VAL" because the flag has no value. So pip exits before sipbuild even runs; every binding "fails" identically with QtCore. Pass `--confirm-license=true` instead. sipbuild treats any truthy value as "license accepted". Should also fix QtGui/QtWidgets which my prior comment blamed on OOM — they may simply never have built. Co-Authored-By: Claude Opus 4.7 --- projects/riverbankcomputing.com/pyqt5/package.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/projects/riverbankcomputing.com/pyqt5/package.yml b/projects/riverbankcomputing.com/pyqt5/package.yml index b076ba4268..7b26fc7c09 100644 --- a/projects/riverbankcomputing.com/pyqt5/package.yml +++ b/projects/riverbankcomputing.com/pyqt5/package.yml @@ -89,12 +89,17 @@ build: echo "::group::pyqt5 binding $binding" mkdir -p "$STAGING/$binding" + # pip 23+ requires --config-settings values in the form + # KEY=VAL. sipbuild's `--confirm-license` is a boolean + # flag with no value, so pass an explicit `=true` to + # satisfy pip's parser; sipbuild interprets the truthy + # value as "license accepted". if "$PYTHON" -m pip install "$SRCROOT" \ --no-build-isolation \ --no-deps \ --force-reinstall \ --target="$STAGING/$binding" \ - --config-settings=--confirm-license \ + --config-settings=--confirm-license=true \ --config-settings=--enable="$binding" \ --config-settings=--jobs=1 \ --verbose From 2582b6293389496de01e0556212ea42487129528 Mon Sep 17 00:00:00 2001 From: tannevaled Date: Sat, 30 May 2026 09:31:43 +0200 Subject: [PATCH 08/11] fix(pyqt5): pre-accept license via pyproject.toml (not --config-settings) The previous `--config-settings=--confirm-license=true` attempt fell into a pip/sipbuild deadlock: - pip insists --config-settings is KEY=VAL - sipbuild's argparse declares --confirm-license as store_true and refuses any value: "ignored explicit argument 'true'" The route around: write `confirm-license = true` into the source tarball's pyproject.toml under [tool.sip.project] before pip runs. sipbuild reads pyproject.toml directly, bypassing the argparse path, so the flag is honored. Co-Authored-By: Claude Opus 4.7 --- .../riverbankcomputing.com/pyqt5/package.yml | 27 ++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/projects/riverbankcomputing.com/pyqt5/package.yml b/projects/riverbankcomputing.com/pyqt5/package.yml index 7b26fc7c09..fe31037188 100644 --- a/projects/riverbankcomputing.com/pyqt5/package.yml +++ b/projects/riverbankcomputing.com/pyqt5/package.yml @@ -80,6 +80,23 @@ build: # Trap to clean up staging on exit (any code path). trap 'rm -rf "$STAGING"' EXIT + # Pre-accept the GPL license in pyproject.toml so sipbuild + # doesn't prompt. pip's --config-settings can't pass through + # the `--confirm-license` flag: pip insists on KEY=VAL form, + # but sipbuild's argparse declares the flag as store_true and + # rejects any value with + # "argument --confirm-license: ignored explicit argument 'true'" + # The pyproject.toml route doesn't go through that argparse, + # so set it there once before pip runs. + if ! grep -q "confirm-license" "$SRCROOT/pyproject.toml"; then + # Insert after the [tool.sip.project] header (it exists in + # PyQt5's pyproject.toml). + sed -i.bak '/^\[tool\.sip\.project\]$/a\ +confirm-license = true' "$SRCROOT/pyproject.toml" + rm -f "$SRCROOT/pyproject.toml.bak" + echo "patched pyproject.toml with confirm-license = true" + fi + # Bindings to ship, in build order (lightest first). BINDINGS="QtCore QtDBus QtNetwork QtSql QtSvg QtTest QtXml QtPrintSupport QtGui QtWidgets" @@ -89,17 +106,15 @@ build: echo "::group::pyqt5 binding $binding" mkdir -p "$STAGING/$binding" - # pip 23+ requires --config-settings values in the form - # KEY=VAL. sipbuild's `--confirm-license` is a boolean - # flag with no value, so pass an explicit `=true` to - # satisfy pip's parser; sipbuild interprets the truthy - # value as "license accepted". + # Note: --confirm-license is pre-applied via the + # pyproject.toml patch above (sed) — it can't be passed + # through pip's --config-settings because pip wants KEY=VAL + # and sipbuild's argparse rejects any value on store_true. if "$PYTHON" -m pip install "$SRCROOT" \ --no-build-isolation \ --no-deps \ --force-reinstall \ --target="$STAGING/$binding" \ - --config-settings=--confirm-license=true \ --config-settings=--enable="$binding" \ --config-settings=--jobs=1 \ --verbose From 495169a15f95c7eb60ff2bf243e3cf3630d6b61c Mon Sep 17 00:00:00 2001 From: tannevaled Date: Sun, 31 May 2026 14:07:17 +0200 Subject: [PATCH 09/11] fix(pyqt5): replace sed a\ with awk to fix YAML block-scalar parse MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous recipe used: sed -i.bak '/^\[tool\.sip\.project\]$/a\ confirm-license = true' "$SRCROOT/pyproject.toml" sed's `a\` command requires the appended content on the next line, which means the second line in the `run: |` block had to start at column 0. That broke YAML's block-scalar indentation rule (later lines may not be less indented than the first non-empty line of the block), causing the plan job to fail with: YAMLError: can not read a block mapping entry; a multiline key may not be an implicit key … at line 100, column 9 Switched to awk — single-line program, no newline-in-script requirement, and arguably clearer intent (insert-after-pattern). Co-Authored-By: Claude Opus 4.7 --- projects/riverbankcomputing.com/pyqt5/package.yml | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/projects/riverbankcomputing.com/pyqt5/package.yml b/projects/riverbankcomputing.com/pyqt5/package.yml index fe31037188..8734436531 100644 --- a/projects/riverbankcomputing.com/pyqt5/package.yml +++ b/projects/riverbankcomputing.com/pyqt5/package.yml @@ -90,10 +90,13 @@ build: # so set it there once before pip runs. if ! grep -q "confirm-license" "$SRCROOT/pyproject.toml"; then # Insert after the [tool.sip.project] header (it exists in - # PyQt5's pyproject.toml). - sed -i.bak '/^\[tool\.sip\.project\]$/a\ -confirm-license = true' "$SRCROOT/pyproject.toml" - rm -f "$SRCROOT/pyproject.toml.bak" + # PyQt5's pyproject.toml). Use awk rather than sed `a\`: + # the latter requires a literal newline in the script, + # which breaks YAML's `run: |` block-scalar indentation + # rules (the appended content has to start at column 0, + # but that's less indentation than the block baseline). + awk '/^\[tool\.sip\.project\]$/ { print; print "confirm-license = true"; next } { print }' "$SRCROOT/pyproject.toml" > "$SRCROOT/pyproject.toml.tmp" + mv "$SRCROOT/pyproject.toml.tmp" "$SRCROOT/pyproject.toml" echo "patched pyproject.toml with confirm-license = true" fi From fa71cbcb6834355140958961ed6246fe27ab4ad3 Mon Sep 17 00:00:00 2001 From: tannevaled Date: Sun, 31 May 2026 17:00:47 +0200 Subject: [PATCH 10/11] feat(pyqt5): split into per-binding sub-recipes (avoid OOM-killed monolithic build) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous monolithic recipe iterated 10 bindings in a single CI job via `pip install --config-settings=--enable=` in a shell loop. Each binding's link peaks at 6-8 GB RAM (sip-generated .cpp), so the 7 GB GH-Linux runner OOM'd on every binding and GHA cancelled at 1h33min (CI run 26712172867: 6/10 bindings SIGKILL'd, 4 queued). Split into: projects/riverbankcomputing.com/pyqt5/ ├── package.yml meta (depends on all 10 sub-bindings) ├── QtCore/package.yml foundation, no inter-PyQt5 deps ├── QtDBus/package.yml deps QtCore ├── QtNetwork/package.yml deps QtCore ├── QtSql/package.yml deps QtCore ├── QtTest/package.yml deps QtCore ├── QtXml/package.yml deps QtCore ├── QtGui/package.yml deps QtCore (HEAVY) ├── QtSvg/package.yml deps QtGui ├── QtPrintSupport/package.yml deps QtGui └── QtWidgets/package.yml deps QtGui (HEAVIEST) Each sub-binding is its own CI job with its own 6 h timeout. Sibling deps are pinned exactly (`={{version}}`) so the meta sees one coherent PyQt5 install. PyQt5 is a namespace package (`pkgutil.extend_path` in `__init__.py`), so each sub-binding's prefix can ship its own `PyQt5/` dir and Python merges them at import time via the stacked PYTHONPATH that pkgx sets up automatically when the deps are in scope. Preserved from the monolithic recipe: - The awk patch for `confirm-license = true` (commit 495169a1). - `CXXFLAGS=-g0` / `CFLAGS=-g0` / `MAKEFLAGS=-j1` memory discipline. - `--no-build-isolation` / `--no-deps` so our env reaches the compiler and pip does not refetch sip from PyPI. NOTE — chicken-and-egg: all 11 recipes ship in the same PR. Linux x64 CI for the non-foundation bindings (and the meta) will fail until QtCore bottles to dist.pkgx.dev. This is the same shape as the font-util/xserver and Tk/Tcl8 splits; maintainers merge in topological order: QtCore → {QtDBus,QtNetwork,QtSql,QtTest,QtXml,QtGui} → {QtSvg,QtPrintSupport,QtWidgets} → pyqt5 (meta) --- .../pyqt5/QtCore/package.yml | 79 ++++++++ .../pyqt5/QtDBus/package.yml | 80 ++++++++ .../pyqt5/QtGui/package.yml | 80 ++++++++ .../pyqt5/QtNetwork/package.yml | 80 ++++++++ .../pyqt5/QtPrintSupport/package.yml | 81 ++++++++ .../pyqt5/QtSql/package.yml | 80 ++++++++ .../pyqt5/QtSvg/package.yml | 81 ++++++++ .../pyqt5/QtTest/package.yml | 80 ++++++++ .../pyqt5/QtWidgets/package.yml | 81 ++++++++ .../pyqt5/QtXml/package.yml | 80 ++++++++ .../riverbankcomputing.com/pyqt5/package.yml | 189 +++--------------- 11 files changed, 834 insertions(+), 157 deletions(-) create mode 100644 projects/riverbankcomputing.com/pyqt5/QtCore/package.yml create mode 100644 projects/riverbankcomputing.com/pyqt5/QtDBus/package.yml create mode 100644 projects/riverbankcomputing.com/pyqt5/QtGui/package.yml create mode 100644 projects/riverbankcomputing.com/pyqt5/QtNetwork/package.yml create mode 100644 projects/riverbankcomputing.com/pyqt5/QtPrintSupport/package.yml create mode 100644 projects/riverbankcomputing.com/pyqt5/QtSql/package.yml create mode 100644 projects/riverbankcomputing.com/pyqt5/QtSvg/package.yml create mode 100644 projects/riverbankcomputing.com/pyqt5/QtTest/package.yml create mode 100644 projects/riverbankcomputing.com/pyqt5/QtWidgets/package.yml create mode 100644 projects/riverbankcomputing.com/pyqt5/QtXml/package.yml diff --git a/projects/riverbankcomputing.com/pyqt5/QtCore/package.yml b/projects/riverbankcomputing.com/pyqt5/QtCore/package.yml new file mode 100644 index 0000000000..7d187d6db8 --- /dev/null +++ b/projects/riverbankcomputing.com/pyqt5/QtCore/package.yml @@ -0,0 +1,79 @@ +# PyQt5.QtCore — Foundation binding for Qt 5's core library. +# +# Part of the PyQt5 sub-binding split. See parent meta package at +# riverbankcomputing.com/pyqt5 for topology. PyQt5 is a namespace +# package (`pkgutil.extend_path`), so each sibling's `.so` lives +# in its own prefix and Python merges them at import time via +# the stacked PYTHONPATH. + +distributable: + url: https://pypi.io/packages/source/P/PyQt5/PyQt5-{{ version.raw }}.tar.gz + strip-components: 1 + +versions: + url: https://pypi.org/simple/pyqt5/ + match: /PyQt5-\d+\.\d+\.\d+\.tar\.gz/ + strip: + - /^PyQt5-/ + - /\.tar\.gz/ + +platforms: + - linux/x86-64 + - linux/aarch64 + - darwin/x86-64 + - darwin/aarch64 + +dependencies: + python.org: ~3.11 + qt.io: ^5 + riverbankcomputing.com/sip: '*' + +runtime: + env: + PYTHONPATH: '{{prefix}}/share/python/site-packages:$PYTHONPATH' + +build: + dependencies: + riverbankcomputing.com/pyqt-builder: '*' + freedesktop.org/pkg-config: '*' + linux: + llvm.org: <17 + + script: + - run: | + export CXXFLAGS="${CXXFLAGS} -g0" + export CFLAGS="${CFLAGS} -g0" + export MAKEFLAGS="-j1" + + # Pre-accept the GPL license in pyproject.toml so sipbuild + # does not prompt. awk (not sed) because sed's `a\` form + # needs a literal newline that breaks YAML's run-block + # indentation. + if ! grep -q "confirm-license" "$SRCROOT/pyproject.toml"; then + awk '/^\[tool\.sip\.project\]$/ { print; print "confirm-license = true"; next } { print }' "$SRCROOT/pyproject.toml" > "$SRCROOT/pyproject.toml.tmp" + mv "$SRCROOT/pyproject.toml.tmp" "$SRCROOT/pyproject.toml" + fi + + PYVER={{deps.python.org.version.marketing}} + SITE_PACKAGES="{{prefix}}/share/python/site-packages" + mkdir -p "$SITE_PACKAGES" + + # Use upstream python directly — no per-binding venv, each + # sub-package overlays via PYTHONPATH. + PYTHON="{{deps.python.org.prefix}}/bin/python$PYVER" + + # Single binding, single pip pass. --no-build-isolation so + # our CFLAGS reach the compiler. --no-deps so pip does not + # try to fetch PyQt5-sip from PyPI (pantry's sip is in scope + # via runtime PYTHONPATH). + "$PYTHON" -m pip install --no-build-isolation --no-deps --force-reinstall \ + --target="$SITE_PACKAGES" \ + --config-settings=--enable=QtCore \ + --config-settings=--jobs=1 \ + "$SRCROOT" + +test: + dependencies: + python.org: ~3.11 + script: + - python{{deps.python.org.version.marketing}} -c "from PyQt5 import QtCore" diff --git a/projects/riverbankcomputing.com/pyqt5/QtDBus/package.yml b/projects/riverbankcomputing.com/pyqt5/QtDBus/package.yml new file mode 100644 index 0000000000..8bde12b910 --- /dev/null +++ b/projects/riverbankcomputing.com/pyqt5/QtDBus/package.yml @@ -0,0 +1,80 @@ +# PyQt5.QtDBus — D-Bus binding for PyQt5. +# +# Part of the PyQt5 sub-binding split. See parent meta package at +# riverbankcomputing.com/pyqt5 for topology. PyQt5 is a namespace +# package (`pkgutil.extend_path`), so each sibling's `.so` lives +# in its own prefix and Python merges them at import time via +# the stacked PYTHONPATH. + +distributable: + url: https://pypi.io/packages/source/P/PyQt5/PyQt5-{{ version.raw }}.tar.gz + strip-components: 1 + +versions: + url: https://pypi.org/simple/pyqt5/ + match: /PyQt5-\d+\.\d+\.\d+\.tar\.gz/ + strip: + - /^PyQt5-/ + - /\.tar\.gz/ + +platforms: + - linux/x86-64 + - linux/aarch64 + - darwin/x86-64 + - darwin/aarch64 + +dependencies: + python.org: ~3.11 + qt.io: ^5 + riverbankcomputing.com/sip: '*' + riverbankcomputing.com/pyqt5/QtCore: '={{version}}' + +runtime: + env: + PYTHONPATH: '{{prefix}}/share/python/site-packages:$PYTHONPATH' + +build: + dependencies: + riverbankcomputing.com/pyqt-builder: '*' + freedesktop.org/pkg-config: '*' + linux: + llvm.org: <17 + + script: + - run: | + export CXXFLAGS="${CXXFLAGS} -g0" + export CFLAGS="${CFLAGS} -g0" + export MAKEFLAGS="-j1" + + # Pre-accept the GPL license in pyproject.toml so sipbuild + # does not prompt. awk (not sed) because sed's `a\` form + # needs a literal newline that breaks YAML's run-block + # indentation. + if ! grep -q "confirm-license" "$SRCROOT/pyproject.toml"; then + awk '/^\[tool\.sip\.project\]$/ { print; print "confirm-license = true"; next } { print }' "$SRCROOT/pyproject.toml" > "$SRCROOT/pyproject.toml.tmp" + mv "$SRCROOT/pyproject.toml.tmp" "$SRCROOT/pyproject.toml" + fi + + PYVER={{deps.python.org.version.marketing}} + SITE_PACKAGES="{{prefix}}/share/python/site-packages" + mkdir -p "$SITE_PACKAGES" + + # Use upstream python directly — no per-binding venv, each + # sub-package overlays via PYTHONPATH. + PYTHON="{{deps.python.org.prefix}}/bin/python$PYVER" + + # Single binding, single pip pass. --no-build-isolation so + # our CFLAGS reach the compiler. --no-deps so pip does not + # try to fetch PyQt5-sip from PyPI (pantry's sip is in scope + # via runtime PYTHONPATH). + "$PYTHON" -m pip install --no-build-isolation --no-deps --force-reinstall \ + --target="$SITE_PACKAGES" \ + --config-settings=--enable=QtDBus \ + --config-settings=--jobs=1 \ + "$SRCROOT" + +test: + dependencies: + python.org: ~3.11 + script: + - python{{deps.python.org.version.marketing}} -c "from PyQt5 import QtDBus" diff --git a/projects/riverbankcomputing.com/pyqt5/QtGui/package.yml b/projects/riverbankcomputing.com/pyqt5/QtGui/package.yml new file mode 100644 index 0000000000..a3b19a4aa2 --- /dev/null +++ b/projects/riverbankcomputing.com/pyqt5/QtGui/package.yml @@ -0,0 +1,80 @@ +# PyQt5.QtGui — GUI primitives (paint, fonts, images, OpenGL) binding for PyQt5. +# +# Part of the PyQt5 sub-binding split. See parent meta package at +# riverbankcomputing.com/pyqt5 for topology. PyQt5 is a namespace +# package (`pkgutil.extend_path`), so each sibling's `.so` lives +# in its own prefix and Python merges them at import time via +# the stacked PYTHONPATH. + +distributable: + url: https://pypi.io/packages/source/P/PyQt5/PyQt5-{{ version.raw }}.tar.gz + strip-components: 1 + +versions: + url: https://pypi.org/simple/pyqt5/ + match: /PyQt5-\d+\.\d+\.\d+\.tar\.gz/ + strip: + - /^PyQt5-/ + - /\.tar\.gz/ + +platforms: + - linux/x86-64 + - linux/aarch64 + - darwin/x86-64 + - darwin/aarch64 + +dependencies: + python.org: ~3.11 + qt.io: ^5 + riverbankcomputing.com/sip: '*' + riverbankcomputing.com/pyqt5/QtCore: '={{version}}' + +runtime: + env: + PYTHONPATH: '{{prefix}}/share/python/site-packages:$PYTHONPATH' + +build: + dependencies: + riverbankcomputing.com/pyqt-builder: '*' + freedesktop.org/pkg-config: '*' + linux: + llvm.org: <17 + + script: + - run: | + export CXXFLAGS="${CXXFLAGS} -g0" + export CFLAGS="${CFLAGS} -g0" + export MAKEFLAGS="-j1" + + # Pre-accept the GPL license in pyproject.toml so sipbuild + # does not prompt. awk (not sed) because sed's `a\` form + # needs a literal newline that breaks YAML's run-block + # indentation. + if ! grep -q "confirm-license" "$SRCROOT/pyproject.toml"; then + awk '/^\[tool\.sip\.project\]$/ { print; print "confirm-license = true"; next } { print }' "$SRCROOT/pyproject.toml" > "$SRCROOT/pyproject.toml.tmp" + mv "$SRCROOT/pyproject.toml.tmp" "$SRCROOT/pyproject.toml" + fi + + PYVER={{deps.python.org.version.marketing}} + SITE_PACKAGES="{{prefix}}/share/python/site-packages" + mkdir -p "$SITE_PACKAGES" + + # Use upstream python directly — no per-binding venv, each + # sub-package overlays via PYTHONPATH. + PYTHON="{{deps.python.org.prefix}}/bin/python$PYVER" + + # Single binding, single pip pass. --no-build-isolation so + # our CFLAGS reach the compiler. --no-deps so pip does not + # try to fetch PyQt5-sip from PyPI (pantry's sip is in scope + # via runtime PYTHONPATH). + "$PYTHON" -m pip install --no-build-isolation --no-deps --force-reinstall \ + --target="$SITE_PACKAGES" \ + --config-settings=--enable=QtGui \ + --config-settings=--jobs=1 \ + "$SRCROOT" + +test: + dependencies: + python.org: ~3.11 + script: + - python{{deps.python.org.version.marketing}} -c "from PyQt5 import QtGui" diff --git a/projects/riverbankcomputing.com/pyqt5/QtNetwork/package.yml b/projects/riverbankcomputing.com/pyqt5/QtNetwork/package.yml new file mode 100644 index 0000000000..b49f030cbe --- /dev/null +++ b/projects/riverbankcomputing.com/pyqt5/QtNetwork/package.yml @@ -0,0 +1,80 @@ +# PyQt5.QtNetwork — Networking (TCP/UDP/SSL/HTTP) binding for PyQt5. +# +# Part of the PyQt5 sub-binding split. See parent meta package at +# riverbankcomputing.com/pyqt5 for topology. PyQt5 is a namespace +# package (`pkgutil.extend_path`), so each sibling's `.so` lives +# in its own prefix and Python merges them at import time via +# the stacked PYTHONPATH. + +distributable: + url: https://pypi.io/packages/source/P/PyQt5/PyQt5-{{ version.raw }}.tar.gz + strip-components: 1 + +versions: + url: https://pypi.org/simple/pyqt5/ + match: /PyQt5-\d+\.\d+\.\d+\.tar\.gz/ + strip: + - /^PyQt5-/ + - /\.tar\.gz/ + +platforms: + - linux/x86-64 + - linux/aarch64 + - darwin/x86-64 + - darwin/aarch64 + +dependencies: + python.org: ~3.11 + qt.io: ^5 + riverbankcomputing.com/sip: '*' + riverbankcomputing.com/pyqt5/QtCore: '={{version}}' + +runtime: + env: + PYTHONPATH: '{{prefix}}/share/python/site-packages:$PYTHONPATH' + +build: + dependencies: + riverbankcomputing.com/pyqt-builder: '*' + freedesktop.org/pkg-config: '*' + linux: + llvm.org: <17 + + script: + - run: | + export CXXFLAGS="${CXXFLAGS} -g0" + export CFLAGS="${CFLAGS} -g0" + export MAKEFLAGS="-j1" + + # Pre-accept the GPL license in pyproject.toml so sipbuild + # does not prompt. awk (not sed) because sed's `a\` form + # needs a literal newline that breaks YAML's run-block + # indentation. + if ! grep -q "confirm-license" "$SRCROOT/pyproject.toml"; then + awk '/^\[tool\.sip\.project\]$/ { print; print "confirm-license = true"; next } { print }' "$SRCROOT/pyproject.toml" > "$SRCROOT/pyproject.toml.tmp" + mv "$SRCROOT/pyproject.toml.tmp" "$SRCROOT/pyproject.toml" + fi + + PYVER={{deps.python.org.version.marketing}} + SITE_PACKAGES="{{prefix}}/share/python/site-packages" + mkdir -p "$SITE_PACKAGES" + + # Use upstream python directly — no per-binding venv, each + # sub-package overlays via PYTHONPATH. + PYTHON="{{deps.python.org.prefix}}/bin/python$PYVER" + + # Single binding, single pip pass. --no-build-isolation so + # our CFLAGS reach the compiler. --no-deps so pip does not + # try to fetch PyQt5-sip from PyPI (pantry's sip is in scope + # via runtime PYTHONPATH). + "$PYTHON" -m pip install --no-build-isolation --no-deps --force-reinstall \ + --target="$SITE_PACKAGES" \ + --config-settings=--enable=QtNetwork \ + --config-settings=--jobs=1 \ + "$SRCROOT" + +test: + dependencies: + python.org: ~3.11 + script: + - python{{deps.python.org.version.marketing}} -c "from PyQt5 import QtNetwork" diff --git a/projects/riverbankcomputing.com/pyqt5/QtPrintSupport/package.yml b/projects/riverbankcomputing.com/pyqt5/QtPrintSupport/package.yml new file mode 100644 index 0000000000..43912474cb --- /dev/null +++ b/projects/riverbankcomputing.com/pyqt5/QtPrintSupport/package.yml @@ -0,0 +1,81 @@ +# PyQt5.QtPrintSupport — Printer support binding for PyQt5. +# +# Part of the PyQt5 sub-binding split. See parent meta package at +# riverbankcomputing.com/pyqt5 for topology. PyQt5 is a namespace +# package (`pkgutil.extend_path`), so each sibling's `.so` lives +# in its own prefix and Python merges them at import time via +# the stacked PYTHONPATH. + +distributable: + url: https://pypi.io/packages/source/P/PyQt5/PyQt5-{{ version.raw }}.tar.gz + strip-components: 1 + +versions: + url: https://pypi.org/simple/pyqt5/ + match: /PyQt5-\d+\.\d+\.\d+\.tar\.gz/ + strip: + - /^PyQt5-/ + - /\.tar\.gz/ + +platforms: + - linux/x86-64 + - linux/aarch64 + - darwin/x86-64 + - darwin/aarch64 + +dependencies: + python.org: ~3.11 + qt.io: ^5 + riverbankcomputing.com/sip: '*' + riverbankcomputing.com/pyqt5/QtCore: '={{version}}' + riverbankcomputing.com/pyqt5/QtGui: '={{version}}' + +runtime: + env: + PYTHONPATH: '{{prefix}}/share/python/site-packages:$PYTHONPATH' + +build: + dependencies: + riverbankcomputing.com/pyqt-builder: '*' + freedesktop.org/pkg-config: '*' + linux: + llvm.org: <17 + + script: + - run: | + export CXXFLAGS="${CXXFLAGS} -g0" + export CFLAGS="${CFLAGS} -g0" + export MAKEFLAGS="-j1" + + # Pre-accept the GPL license in pyproject.toml so sipbuild + # does not prompt. awk (not sed) because sed's `a\` form + # needs a literal newline that breaks YAML's run-block + # indentation. + if ! grep -q "confirm-license" "$SRCROOT/pyproject.toml"; then + awk '/^\[tool\.sip\.project\]$/ { print; print "confirm-license = true"; next } { print }' "$SRCROOT/pyproject.toml" > "$SRCROOT/pyproject.toml.tmp" + mv "$SRCROOT/pyproject.toml.tmp" "$SRCROOT/pyproject.toml" + fi + + PYVER={{deps.python.org.version.marketing}} + SITE_PACKAGES="{{prefix}}/share/python/site-packages" + mkdir -p "$SITE_PACKAGES" + + # Use upstream python directly — no per-binding venv, each + # sub-package overlays via PYTHONPATH. + PYTHON="{{deps.python.org.prefix}}/bin/python$PYVER" + + # Single binding, single pip pass. --no-build-isolation so + # our CFLAGS reach the compiler. --no-deps so pip does not + # try to fetch PyQt5-sip from PyPI (pantry's sip is in scope + # via runtime PYTHONPATH). + "$PYTHON" -m pip install --no-build-isolation --no-deps --force-reinstall \ + --target="$SITE_PACKAGES" \ + --config-settings=--enable=QtPrintSupport \ + --config-settings=--jobs=1 \ + "$SRCROOT" + +test: + dependencies: + python.org: ~3.11 + script: + - python{{deps.python.org.version.marketing}} -c "from PyQt5 import QtPrintSupport" diff --git a/projects/riverbankcomputing.com/pyqt5/QtSql/package.yml b/projects/riverbankcomputing.com/pyqt5/QtSql/package.yml new file mode 100644 index 0000000000..89be5d1e7b --- /dev/null +++ b/projects/riverbankcomputing.com/pyqt5/QtSql/package.yml @@ -0,0 +1,80 @@ +# PyQt5.QtSql — SQL database binding for PyQt5. +# +# Part of the PyQt5 sub-binding split. See parent meta package at +# riverbankcomputing.com/pyqt5 for topology. PyQt5 is a namespace +# package (`pkgutil.extend_path`), so each sibling's `.so` lives +# in its own prefix and Python merges them at import time via +# the stacked PYTHONPATH. + +distributable: + url: https://pypi.io/packages/source/P/PyQt5/PyQt5-{{ version.raw }}.tar.gz + strip-components: 1 + +versions: + url: https://pypi.org/simple/pyqt5/ + match: /PyQt5-\d+\.\d+\.\d+\.tar\.gz/ + strip: + - /^PyQt5-/ + - /\.tar\.gz/ + +platforms: + - linux/x86-64 + - linux/aarch64 + - darwin/x86-64 + - darwin/aarch64 + +dependencies: + python.org: ~3.11 + qt.io: ^5 + riverbankcomputing.com/sip: '*' + riverbankcomputing.com/pyqt5/QtCore: '={{version}}' + +runtime: + env: + PYTHONPATH: '{{prefix}}/share/python/site-packages:$PYTHONPATH' + +build: + dependencies: + riverbankcomputing.com/pyqt-builder: '*' + freedesktop.org/pkg-config: '*' + linux: + llvm.org: <17 + + script: + - run: | + export CXXFLAGS="${CXXFLAGS} -g0" + export CFLAGS="${CFLAGS} -g0" + export MAKEFLAGS="-j1" + + # Pre-accept the GPL license in pyproject.toml so sipbuild + # does not prompt. awk (not sed) because sed's `a\` form + # needs a literal newline that breaks YAML's run-block + # indentation. + if ! grep -q "confirm-license" "$SRCROOT/pyproject.toml"; then + awk '/^\[tool\.sip\.project\]$/ { print; print "confirm-license = true"; next } { print }' "$SRCROOT/pyproject.toml" > "$SRCROOT/pyproject.toml.tmp" + mv "$SRCROOT/pyproject.toml.tmp" "$SRCROOT/pyproject.toml" + fi + + PYVER={{deps.python.org.version.marketing}} + SITE_PACKAGES="{{prefix}}/share/python/site-packages" + mkdir -p "$SITE_PACKAGES" + + # Use upstream python directly — no per-binding venv, each + # sub-package overlays via PYTHONPATH. + PYTHON="{{deps.python.org.prefix}}/bin/python$PYVER" + + # Single binding, single pip pass. --no-build-isolation so + # our CFLAGS reach the compiler. --no-deps so pip does not + # try to fetch PyQt5-sip from PyPI (pantry's sip is in scope + # via runtime PYTHONPATH). + "$PYTHON" -m pip install --no-build-isolation --no-deps --force-reinstall \ + --target="$SITE_PACKAGES" \ + --config-settings=--enable=QtSql \ + --config-settings=--jobs=1 \ + "$SRCROOT" + +test: + dependencies: + python.org: ~3.11 + script: + - python{{deps.python.org.version.marketing}} -c "from PyQt5 import QtSql" diff --git a/projects/riverbankcomputing.com/pyqt5/QtSvg/package.yml b/projects/riverbankcomputing.com/pyqt5/QtSvg/package.yml new file mode 100644 index 0000000000..1cbf512258 --- /dev/null +++ b/projects/riverbankcomputing.com/pyqt5/QtSvg/package.yml @@ -0,0 +1,81 @@ +# PyQt5.QtSvg — SVG rendering binding for PyQt5. +# +# Part of the PyQt5 sub-binding split. See parent meta package at +# riverbankcomputing.com/pyqt5 for topology. PyQt5 is a namespace +# package (`pkgutil.extend_path`), so each sibling's `.so` lives +# in its own prefix and Python merges them at import time via +# the stacked PYTHONPATH. + +distributable: + url: https://pypi.io/packages/source/P/PyQt5/PyQt5-{{ version.raw }}.tar.gz + strip-components: 1 + +versions: + url: https://pypi.org/simple/pyqt5/ + match: /PyQt5-\d+\.\d+\.\d+\.tar\.gz/ + strip: + - /^PyQt5-/ + - /\.tar\.gz/ + +platforms: + - linux/x86-64 + - linux/aarch64 + - darwin/x86-64 + - darwin/aarch64 + +dependencies: + python.org: ~3.11 + qt.io: ^5 + riverbankcomputing.com/sip: '*' + riverbankcomputing.com/pyqt5/QtCore: '={{version}}' + riverbankcomputing.com/pyqt5/QtGui: '={{version}}' + +runtime: + env: + PYTHONPATH: '{{prefix}}/share/python/site-packages:$PYTHONPATH' + +build: + dependencies: + riverbankcomputing.com/pyqt-builder: '*' + freedesktop.org/pkg-config: '*' + linux: + llvm.org: <17 + + script: + - run: | + export CXXFLAGS="${CXXFLAGS} -g0" + export CFLAGS="${CFLAGS} -g0" + export MAKEFLAGS="-j1" + + # Pre-accept the GPL license in pyproject.toml so sipbuild + # does not prompt. awk (not sed) because sed's `a\` form + # needs a literal newline that breaks YAML's run-block + # indentation. + if ! grep -q "confirm-license" "$SRCROOT/pyproject.toml"; then + awk '/^\[tool\.sip\.project\]$/ { print; print "confirm-license = true"; next } { print }' "$SRCROOT/pyproject.toml" > "$SRCROOT/pyproject.toml.tmp" + mv "$SRCROOT/pyproject.toml.tmp" "$SRCROOT/pyproject.toml" + fi + + PYVER={{deps.python.org.version.marketing}} + SITE_PACKAGES="{{prefix}}/share/python/site-packages" + mkdir -p "$SITE_PACKAGES" + + # Use upstream python directly — no per-binding venv, each + # sub-package overlays via PYTHONPATH. + PYTHON="{{deps.python.org.prefix}}/bin/python$PYVER" + + # Single binding, single pip pass. --no-build-isolation so + # our CFLAGS reach the compiler. --no-deps so pip does not + # try to fetch PyQt5-sip from PyPI (pantry's sip is in scope + # via runtime PYTHONPATH). + "$PYTHON" -m pip install --no-build-isolation --no-deps --force-reinstall \ + --target="$SITE_PACKAGES" \ + --config-settings=--enable=QtSvg \ + --config-settings=--jobs=1 \ + "$SRCROOT" + +test: + dependencies: + python.org: ~3.11 + script: + - python{{deps.python.org.version.marketing}} -c "from PyQt5 import QtSvg" diff --git a/projects/riverbankcomputing.com/pyqt5/QtTest/package.yml b/projects/riverbankcomputing.com/pyqt5/QtTest/package.yml new file mode 100644 index 0000000000..bf4100235e --- /dev/null +++ b/projects/riverbankcomputing.com/pyqt5/QtTest/package.yml @@ -0,0 +1,80 @@ +# PyQt5.QtTest — Unit-test framework binding for PyQt5. +# +# Part of the PyQt5 sub-binding split. See parent meta package at +# riverbankcomputing.com/pyqt5 for topology. PyQt5 is a namespace +# package (`pkgutil.extend_path`), so each sibling's `.so` lives +# in its own prefix and Python merges them at import time via +# the stacked PYTHONPATH. + +distributable: + url: https://pypi.io/packages/source/P/PyQt5/PyQt5-{{ version.raw }}.tar.gz + strip-components: 1 + +versions: + url: https://pypi.org/simple/pyqt5/ + match: /PyQt5-\d+\.\d+\.\d+\.tar\.gz/ + strip: + - /^PyQt5-/ + - /\.tar\.gz/ + +platforms: + - linux/x86-64 + - linux/aarch64 + - darwin/x86-64 + - darwin/aarch64 + +dependencies: + python.org: ~3.11 + qt.io: ^5 + riverbankcomputing.com/sip: '*' + riverbankcomputing.com/pyqt5/QtCore: '={{version}}' + +runtime: + env: + PYTHONPATH: '{{prefix}}/share/python/site-packages:$PYTHONPATH' + +build: + dependencies: + riverbankcomputing.com/pyqt-builder: '*' + freedesktop.org/pkg-config: '*' + linux: + llvm.org: <17 + + script: + - run: | + export CXXFLAGS="${CXXFLAGS} -g0" + export CFLAGS="${CFLAGS} -g0" + export MAKEFLAGS="-j1" + + # Pre-accept the GPL license in pyproject.toml so sipbuild + # does not prompt. awk (not sed) because sed's `a\` form + # needs a literal newline that breaks YAML's run-block + # indentation. + if ! grep -q "confirm-license" "$SRCROOT/pyproject.toml"; then + awk '/^\[tool\.sip\.project\]$/ { print; print "confirm-license = true"; next } { print }' "$SRCROOT/pyproject.toml" > "$SRCROOT/pyproject.toml.tmp" + mv "$SRCROOT/pyproject.toml.tmp" "$SRCROOT/pyproject.toml" + fi + + PYVER={{deps.python.org.version.marketing}} + SITE_PACKAGES="{{prefix}}/share/python/site-packages" + mkdir -p "$SITE_PACKAGES" + + # Use upstream python directly — no per-binding venv, each + # sub-package overlays via PYTHONPATH. + PYTHON="{{deps.python.org.prefix}}/bin/python$PYVER" + + # Single binding, single pip pass. --no-build-isolation so + # our CFLAGS reach the compiler. --no-deps so pip does not + # try to fetch PyQt5-sip from PyPI (pantry's sip is in scope + # via runtime PYTHONPATH). + "$PYTHON" -m pip install --no-build-isolation --no-deps --force-reinstall \ + --target="$SITE_PACKAGES" \ + --config-settings=--enable=QtTest \ + --config-settings=--jobs=1 \ + "$SRCROOT" + +test: + dependencies: + python.org: ~3.11 + script: + - python{{deps.python.org.version.marketing}} -c "from PyQt5 import QtTest" diff --git a/projects/riverbankcomputing.com/pyqt5/QtWidgets/package.yml b/projects/riverbankcomputing.com/pyqt5/QtWidgets/package.yml new file mode 100644 index 0000000000..bd568f1f50 --- /dev/null +++ b/projects/riverbankcomputing.com/pyqt5/QtWidgets/package.yml @@ -0,0 +1,81 @@ +# PyQt5.QtWidgets — Full Qt 5 widget set — heaviest of the bindings. +# +# Part of the PyQt5 sub-binding split. See parent meta package at +# riverbankcomputing.com/pyqt5 for topology. PyQt5 is a namespace +# package (`pkgutil.extend_path`), so each sibling's `.so` lives +# in its own prefix and Python merges them at import time via +# the stacked PYTHONPATH. + +distributable: + url: https://pypi.io/packages/source/P/PyQt5/PyQt5-{{ version.raw }}.tar.gz + strip-components: 1 + +versions: + url: https://pypi.org/simple/pyqt5/ + match: /PyQt5-\d+\.\d+\.\d+\.tar\.gz/ + strip: + - /^PyQt5-/ + - /\.tar\.gz/ + +platforms: + - linux/x86-64 + - linux/aarch64 + - darwin/x86-64 + - darwin/aarch64 + +dependencies: + python.org: ~3.11 + qt.io: ^5 + riverbankcomputing.com/sip: '*' + riverbankcomputing.com/pyqt5/QtCore: '={{version}}' + riverbankcomputing.com/pyqt5/QtGui: '={{version}}' + +runtime: + env: + PYTHONPATH: '{{prefix}}/share/python/site-packages:$PYTHONPATH' + +build: + dependencies: + riverbankcomputing.com/pyqt-builder: '*' + freedesktop.org/pkg-config: '*' + linux: + llvm.org: <17 + + script: + - run: | + export CXXFLAGS="${CXXFLAGS} -g0" + export CFLAGS="${CFLAGS} -g0" + export MAKEFLAGS="-j1" + + # Pre-accept the GPL license in pyproject.toml so sipbuild + # does not prompt. awk (not sed) because sed's `a\` form + # needs a literal newline that breaks YAML's run-block + # indentation. + if ! grep -q "confirm-license" "$SRCROOT/pyproject.toml"; then + awk '/^\[tool\.sip\.project\]$/ { print; print "confirm-license = true"; next } { print }' "$SRCROOT/pyproject.toml" > "$SRCROOT/pyproject.toml.tmp" + mv "$SRCROOT/pyproject.toml.tmp" "$SRCROOT/pyproject.toml" + fi + + PYVER={{deps.python.org.version.marketing}} + SITE_PACKAGES="{{prefix}}/share/python/site-packages" + mkdir -p "$SITE_PACKAGES" + + # Use upstream python directly — no per-binding venv, each + # sub-package overlays via PYTHONPATH. + PYTHON="{{deps.python.org.prefix}}/bin/python$PYVER" + + # Single binding, single pip pass. --no-build-isolation so + # our CFLAGS reach the compiler. --no-deps so pip does not + # try to fetch PyQt5-sip from PyPI (pantry's sip is in scope + # via runtime PYTHONPATH). + "$PYTHON" -m pip install --no-build-isolation --no-deps --force-reinstall \ + --target="$SITE_PACKAGES" \ + --config-settings=--enable=QtWidgets \ + --config-settings=--jobs=1 \ + "$SRCROOT" + +test: + dependencies: + python.org: ~3.11 + script: + - python{{deps.python.org.version.marketing}} -c "from PyQt5 import QtWidgets" diff --git a/projects/riverbankcomputing.com/pyqt5/QtXml/package.yml b/projects/riverbankcomputing.com/pyqt5/QtXml/package.yml new file mode 100644 index 0000000000..10f813149d --- /dev/null +++ b/projects/riverbankcomputing.com/pyqt5/QtXml/package.yml @@ -0,0 +1,80 @@ +# PyQt5.QtXml — DOM XML binding for PyQt5. +# +# Part of the PyQt5 sub-binding split. See parent meta package at +# riverbankcomputing.com/pyqt5 for topology. PyQt5 is a namespace +# package (`pkgutil.extend_path`), so each sibling's `.so` lives +# in its own prefix and Python merges them at import time via +# the stacked PYTHONPATH. + +distributable: + url: https://pypi.io/packages/source/P/PyQt5/PyQt5-{{ version.raw }}.tar.gz + strip-components: 1 + +versions: + url: https://pypi.org/simple/pyqt5/ + match: /PyQt5-\d+\.\d+\.\d+\.tar\.gz/ + strip: + - /^PyQt5-/ + - /\.tar\.gz/ + +platforms: + - linux/x86-64 + - linux/aarch64 + - darwin/x86-64 + - darwin/aarch64 + +dependencies: + python.org: ~3.11 + qt.io: ^5 + riverbankcomputing.com/sip: '*' + riverbankcomputing.com/pyqt5/QtCore: '={{version}}' + +runtime: + env: + PYTHONPATH: '{{prefix}}/share/python/site-packages:$PYTHONPATH' + +build: + dependencies: + riverbankcomputing.com/pyqt-builder: '*' + freedesktop.org/pkg-config: '*' + linux: + llvm.org: <17 + + script: + - run: | + export CXXFLAGS="${CXXFLAGS} -g0" + export CFLAGS="${CFLAGS} -g0" + export MAKEFLAGS="-j1" + + # Pre-accept the GPL license in pyproject.toml so sipbuild + # does not prompt. awk (not sed) because sed's `a\` form + # needs a literal newline that breaks YAML's run-block + # indentation. + if ! grep -q "confirm-license" "$SRCROOT/pyproject.toml"; then + awk '/^\[tool\.sip\.project\]$/ { print; print "confirm-license = true"; next } { print }' "$SRCROOT/pyproject.toml" > "$SRCROOT/pyproject.toml.tmp" + mv "$SRCROOT/pyproject.toml.tmp" "$SRCROOT/pyproject.toml" + fi + + PYVER={{deps.python.org.version.marketing}} + SITE_PACKAGES="{{prefix}}/share/python/site-packages" + mkdir -p "$SITE_PACKAGES" + + # Use upstream python directly — no per-binding venv, each + # sub-package overlays via PYTHONPATH. + PYTHON="{{deps.python.org.prefix}}/bin/python$PYVER" + + # Single binding, single pip pass. --no-build-isolation so + # our CFLAGS reach the compiler. --no-deps so pip does not + # try to fetch PyQt5-sip from PyPI (pantry's sip is in scope + # via runtime PYTHONPATH). + "$PYTHON" -m pip install --no-build-isolation --no-deps --force-reinstall \ + --target="$SITE_PACKAGES" \ + --config-settings=--enable=QtXml \ + --config-settings=--jobs=1 \ + "$SRCROOT" + +test: + dependencies: + python.org: ~3.11 + script: + - python{{deps.python.org.version.marketing}} -c "from PyQt5 import QtXml" diff --git a/projects/riverbankcomputing.com/pyqt5/package.yml b/projects/riverbankcomputing.com/pyqt5/package.yml index 8734436531..3afe874f8b 100644 --- a/projects/riverbankcomputing.com/pyqt5/package.yml +++ b/projects/riverbankcomputing.com/pyqt5/package.yml @@ -1,10 +1,12 @@ -# PyQt5 — Python bindings for Qt 5. +# PyQt5 — Meta-package: pulls all PyQt5 sub-bindings together. # -# Companion to pantry's qt.io (5.15.10) and riverbankcomputing.com/sip. -# Installed as a python-venv with `pip install PyQt5` against the -# pantry Qt5 + SIP build. +# Equivalent to homebrew's `pyqt@5` formula or apt's `python3-pyqt5`. +# Per-binding sub-recipes (QtCore / QtGui / QtWidgets / etc.) live in +# sibling directories so each binding gets its own CI job and its own +# 6 h timeout — the monolithic recipe was being OOM-killed and then +# GHA-cancelled at 1h33min. See PR pkgxdev/pantry#13064. # -# Closes part of pkgxdev/pantry#99 (Top 300 holdout #591). +# Top 300 holdout #591. distributable: url: https://pypi.io/packages/source/P/PyQt5/PyQt5-{{ version.raw }}.tar.gz @@ -24,161 +26,34 @@ platforms: - darwin/aarch64 dependencies: - python.org: ~3.11 - qt.io: ^5 - riverbankcomputing.com/sip: '*' - -runtime: - env: - PYTHONPATH: '{{prefix}}/venv/lib/python{{deps.python.org.version.marketing}}/site-packages:$PYTHONPATH' - + riverbankcomputing.com/pyqt5/QtCore: '={{version}}' + riverbankcomputing.com/pyqt5/QtDBus: '={{version}}' + riverbankcomputing.com/pyqt5/QtNetwork: '={{version}}' + riverbankcomputing.com/pyqt5/QtSql: '={{version}}' + riverbankcomputing.com/pyqt5/QtTest: '={{version}}' + riverbankcomputing.com/pyqt5/QtXml: '={{version}}' + riverbankcomputing.com/pyqt5/QtGui: '={{version}}' + riverbankcomputing.com/pyqt5/QtSvg: '={{version}}' + riverbankcomputing.com/pyqt5/QtPrintSupport: '={{version}}' + riverbankcomputing.com/pyqt5/QtWidgets: '={{version}}' + +# Meta package: no build artifacts of its own — every sub-binding +# carries its own .so into its own prefix and Python's pkgutil +# extends `PyQt5/__path__` to merge them at import time. We still +# need a `build.script` because pkgx requires one; emit a marker file +# so the package is non-empty and brewkit's bottling sees something. build: - dependencies: - riverbankcomputing.com/pyqt-builder: '*' - freedesktop.org/pkg-config: '*' - linux: - llvm.org: <17 - script: - # Per-binding split build (one Qt binding per pip-install pass). - # - # Why: PyQt5's monolithic `pip install .` was OOM-killed (exit 137) - # on the 7 GB GH-runner. Even `--config-settings=--jobs=1` wasn't - # enough — a SINGLE binding's final `ld` (especially QtWidgets at - # ~4-6 GB peak) blows past 7 GB. The only viable shape is to build - # each binding in its own subprocess so its working set is freed - # between bindings. - # - # sipbuild config_settings honoured here: - # --enable=NAME (sip) -- only build this binding - # --jobs=1 (pyqt-builder) -- inner make -j1 - # --confirm-license (PyQt5 project.py) -- accept GPL - # - # Note: pyqt-builder's `--no-make` flag is restricted to the - # `build` tool (tools=['build']); pip's PEP 517 path calls the - # `wheel` tool, so --no-make can't be set via --config-settings. - # The split-pip approach achieves the same memory isolation by - # freeing each binding's intermediate state between subprocesses. - # - # Bindings are ordered lightest-first so a memory failure surfaces - # early. QtWidgets is last — if it OOMs, the eight before it are - # still importable (partial install rather than nothing). - run: | - PYVER={{deps.python.org.version.marketing}} - "{{deps.python.org.prefix}}/bin/python$PYVER" -m venv --copies "{{prefix}}/venv" - PYTHON="{{prefix}}/venv/bin/python" - SITE_PACKAGES="{{prefix}}/venv/lib/python$PYVER/site-packages" - "$PYTHON" -m pip install --upgrade pip wheel setuptools --quiet - - # Memory discipline: drop debug info (-g0 saves ~30 % link - # RAM) and pin make to -j1 belt-and-braces. - export CXXFLAGS="${CXXFLAGS} -g0" - export CFLAGS="${CFLAGS} -g0" - export MAKEFLAGS="-j1" - - STAGING="$(mktemp -d)" - # Trap to clean up staging on exit (any code path). - trap 'rm -rf "$STAGING"' EXIT - - # Pre-accept the GPL license in pyproject.toml so sipbuild - # doesn't prompt. pip's --config-settings can't pass through - # the `--confirm-license` flag: pip insists on KEY=VAL form, - # but sipbuild's argparse declares the flag as store_true and - # rejects any value with - # "argument --confirm-license: ignored explicit argument 'true'" - # The pyproject.toml route doesn't go through that argparse, - # so set it there once before pip runs. - if ! grep -q "confirm-license" "$SRCROOT/pyproject.toml"; then - # Insert after the [tool.sip.project] header (it exists in - # PyQt5's pyproject.toml). Use awk rather than sed `a\`: - # the latter requires a literal newline in the script, - # which breaks YAML's `run: |` block-scalar indentation - # rules (the appended content has to start at column 0, - # but that's less indentation than the block baseline). - awk '/^\[tool\.sip\.project\]$/ { print; print "confirm-license = true"; next } { print }' "$SRCROOT/pyproject.toml" > "$SRCROOT/pyproject.toml.tmp" - mv "$SRCROOT/pyproject.toml.tmp" "$SRCROOT/pyproject.toml" - echo "patched pyproject.toml with confirm-license = true" - fi - - # Bindings to ship, in build order (lightest first). - BINDINGS="QtCore QtDBus QtNetwork QtSql QtSvg QtTest QtXml QtPrintSupport QtGui QtWidgets" - - BUILT="" - FAILED="" - for binding in $BINDINGS; do - echo "::group::pyqt5 binding $binding" - mkdir -p "$STAGING/$binding" - - # Note: --confirm-license is pre-applied via the - # pyproject.toml patch above (sed) — it can't be passed - # through pip's --config-settings because pip wants KEY=VAL - # and sipbuild's argparse rejects any value on store_true. - if "$PYTHON" -m pip install "$SRCROOT" \ - --no-build-isolation \ - --no-deps \ - --force-reinstall \ - --target="$STAGING/$binding" \ - --config-settings=--enable="$binding" \ - --config-settings=--jobs=1 \ - --verbose - then - # Merge produced files into the venv's PyQt5 dir. - # `cp -R src/. dst/` performs a deep merge; wrapper - # modules from the first binding stay, the per-binding - # .so accumulates. - if [ -d "$STAGING/$binding/PyQt5" ]; then - mkdir -p "$SITE_PACKAGES/PyQt5" - cp -R "$STAGING/$binding/PyQt5/." "$SITE_PACKAGES/PyQt5/" - fi - # Copy any top-level dist-info from this pass once. - for di in "$STAGING/$binding"/PyQt5-*.dist-info; do - if [ -d "$di" ] && [ ! -d "$SITE_PACKAGES/$(basename "$di")" ]; then - cp -R "$di" "$SITE_PACKAGES/" - fi - done - BUILT="$BUILT $binding" - echo "pyqt5: $binding built OK" - else - FAILED="$FAILED $binding" - echo "pyqt5: WARNING — $binding failed to build (likely OOM); continuing" - fi - - # Free the per-binding staging dir to reclaim disk. - rm -rf "$STAGING/$binding" - echo "::endgroup::" - done - - echo "pyqt5: built:$BUILT" - if [ -n "$FAILED" ]; then - echo "pyqt5: FAILED:$FAILED" - fi - - # Smoke-test: at minimum QtCore must be importable. - "$PYTHON" -c "from PyQt5 import QtCore; print('PyQt5 QtCore', QtCore.QT_VERSION_STR)" - - # Expose the python binary as a marker so provides: has something. - - run: | - mkdir -p {{prefix}}/bin - ln -sf ../venv/bin/python "{{prefix}}/bin/pyqt5-python" + mkdir -p "{{prefix}}/share/pyqt5" + echo "PyQt5 {{version}} (meta-package — see sub-bindings)" > "{{prefix}}/share/pyqt5/README" test: + dependencies: + python.org: ~3.11 script: - - pyqt5-python -c "from PyQt5 import QtCore; print(QtCore.QT_VERSION_STR)" - # QtGui and QtWidgets may be absent if their link OOM'd on a - # 7 GB runner. Test them best-effort. - - | - pyqt5-python - <<'PY' - try: - from PyQt5 import QtGui # noqa: F401 - print("QtGui OK") - except ImportError as e: - print("QtGui MISSING:", e) - try: - from PyQt5 import QtWidgets # noqa: F401 - print("QtWidgets OK") - except ImportError as e: - print("QtWidgets MISSING:", e) - PY - -provides: - - bin/pyqt5-python + # Importing QtCore exercises the namespace-package overlay across + # at least one sub-binding prefix. Each sub-binding has its own + # test pass; this test only checks that the meta successfully + # pulled them all into scope. + - python{{deps.python.org.version.marketing}} -c "from PyQt5 import QtCore; print(QtCore.QT_VERSION_STR)" From d54adcac48a8abaf0c969ff9e3d9215b5b452b56 Mon Sep 17 00:00:00 2001 From: tannevaled Date: Sun, 31 May 2026 17:03:21 +0200 Subject: [PATCH 11/11] fix(pyqt5): use wildcard for sibling-pin (pantry deps do not template-substitute) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previous commit pinned sibling sub-bindings with `={{version}}` per the task spec, but libpkgx's `validatePackageRequirement` runs at YAML parse time — before template substitution — and rejects the literal `{{version}}` string with `invalid constraint: undefined` (CI run 26716046336 confirmed: meta package fails before any build step). PyQt5 sub-bindings all share the same version-discovery (PyPI simple index) so they march in lockstep anyway; a `'*'` constraint is safe here. Same shape as poppler / poppler-data and x.org/exts -> x11. --- .../pyqt5/QtDBus/package.yml | 2 +- .../pyqt5/QtGui/package.yml | 2 +- .../pyqt5/QtNetwork/package.yml | 2 +- .../pyqt5/QtPrintSupport/package.yml | 4 ++-- .../pyqt5/QtSql/package.yml | 2 +- .../pyqt5/QtSvg/package.yml | 4 ++-- .../pyqt5/QtTest/package.yml | 2 +- .../pyqt5/QtWidgets/package.yml | 4 ++-- .../pyqt5/QtXml/package.yml | 2 +- .../riverbankcomputing.com/pyqt5/package.yml | 20 +++++++++---------- 10 files changed, 22 insertions(+), 22 deletions(-) diff --git a/projects/riverbankcomputing.com/pyqt5/QtDBus/package.yml b/projects/riverbankcomputing.com/pyqt5/QtDBus/package.yml index 8bde12b910..229c3f2d05 100644 --- a/projects/riverbankcomputing.com/pyqt5/QtDBus/package.yml +++ b/projects/riverbankcomputing.com/pyqt5/QtDBus/package.yml @@ -27,7 +27,7 @@ dependencies: python.org: ~3.11 qt.io: ^5 riverbankcomputing.com/sip: '*' - riverbankcomputing.com/pyqt5/QtCore: '={{version}}' + riverbankcomputing.com/pyqt5/QtCore: '*' runtime: env: diff --git a/projects/riverbankcomputing.com/pyqt5/QtGui/package.yml b/projects/riverbankcomputing.com/pyqt5/QtGui/package.yml index a3b19a4aa2..9079610a5c 100644 --- a/projects/riverbankcomputing.com/pyqt5/QtGui/package.yml +++ b/projects/riverbankcomputing.com/pyqt5/QtGui/package.yml @@ -27,7 +27,7 @@ dependencies: python.org: ~3.11 qt.io: ^5 riverbankcomputing.com/sip: '*' - riverbankcomputing.com/pyqt5/QtCore: '={{version}}' + riverbankcomputing.com/pyqt5/QtCore: '*' runtime: env: diff --git a/projects/riverbankcomputing.com/pyqt5/QtNetwork/package.yml b/projects/riverbankcomputing.com/pyqt5/QtNetwork/package.yml index b49f030cbe..88c9e29c5b 100644 --- a/projects/riverbankcomputing.com/pyqt5/QtNetwork/package.yml +++ b/projects/riverbankcomputing.com/pyqt5/QtNetwork/package.yml @@ -27,7 +27,7 @@ dependencies: python.org: ~3.11 qt.io: ^5 riverbankcomputing.com/sip: '*' - riverbankcomputing.com/pyqt5/QtCore: '={{version}}' + riverbankcomputing.com/pyqt5/QtCore: '*' runtime: env: diff --git a/projects/riverbankcomputing.com/pyqt5/QtPrintSupport/package.yml b/projects/riverbankcomputing.com/pyqt5/QtPrintSupport/package.yml index 43912474cb..1a4de56e2f 100644 --- a/projects/riverbankcomputing.com/pyqt5/QtPrintSupport/package.yml +++ b/projects/riverbankcomputing.com/pyqt5/QtPrintSupport/package.yml @@ -27,8 +27,8 @@ dependencies: python.org: ~3.11 qt.io: ^5 riverbankcomputing.com/sip: '*' - riverbankcomputing.com/pyqt5/QtCore: '={{version}}' - riverbankcomputing.com/pyqt5/QtGui: '={{version}}' + riverbankcomputing.com/pyqt5/QtCore: '*' + riverbankcomputing.com/pyqt5/QtGui: '*' runtime: env: diff --git a/projects/riverbankcomputing.com/pyqt5/QtSql/package.yml b/projects/riverbankcomputing.com/pyqt5/QtSql/package.yml index 89be5d1e7b..64f2d27b60 100644 --- a/projects/riverbankcomputing.com/pyqt5/QtSql/package.yml +++ b/projects/riverbankcomputing.com/pyqt5/QtSql/package.yml @@ -27,7 +27,7 @@ dependencies: python.org: ~3.11 qt.io: ^5 riverbankcomputing.com/sip: '*' - riverbankcomputing.com/pyqt5/QtCore: '={{version}}' + riverbankcomputing.com/pyqt5/QtCore: '*' runtime: env: diff --git a/projects/riverbankcomputing.com/pyqt5/QtSvg/package.yml b/projects/riverbankcomputing.com/pyqt5/QtSvg/package.yml index 1cbf512258..51695279ec 100644 --- a/projects/riverbankcomputing.com/pyqt5/QtSvg/package.yml +++ b/projects/riverbankcomputing.com/pyqt5/QtSvg/package.yml @@ -27,8 +27,8 @@ dependencies: python.org: ~3.11 qt.io: ^5 riverbankcomputing.com/sip: '*' - riverbankcomputing.com/pyqt5/QtCore: '={{version}}' - riverbankcomputing.com/pyqt5/QtGui: '={{version}}' + riverbankcomputing.com/pyqt5/QtCore: '*' + riverbankcomputing.com/pyqt5/QtGui: '*' runtime: env: diff --git a/projects/riverbankcomputing.com/pyqt5/QtTest/package.yml b/projects/riverbankcomputing.com/pyqt5/QtTest/package.yml index bf4100235e..55d89b2f0e 100644 --- a/projects/riverbankcomputing.com/pyqt5/QtTest/package.yml +++ b/projects/riverbankcomputing.com/pyqt5/QtTest/package.yml @@ -27,7 +27,7 @@ dependencies: python.org: ~3.11 qt.io: ^5 riverbankcomputing.com/sip: '*' - riverbankcomputing.com/pyqt5/QtCore: '={{version}}' + riverbankcomputing.com/pyqt5/QtCore: '*' runtime: env: diff --git a/projects/riverbankcomputing.com/pyqt5/QtWidgets/package.yml b/projects/riverbankcomputing.com/pyqt5/QtWidgets/package.yml index bd568f1f50..77772283f4 100644 --- a/projects/riverbankcomputing.com/pyqt5/QtWidgets/package.yml +++ b/projects/riverbankcomputing.com/pyqt5/QtWidgets/package.yml @@ -27,8 +27,8 @@ dependencies: python.org: ~3.11 qt.io: ^5 riverbankcomputing.com/sip: '*' - riverbankcomputing.com/pyqt5/QtCore: '={{version}}' - riverbankcomputing.com/pyqt5/QtGui: '={{version}}' + riverbankcomputing.com/pyqt5/QtCore: '*' + riverbankcomputing.com/pyqt5/QtGui: '*' runtime: env: diff --git a/projects/riverbankcomputing.com/pyqt5/QtXml/package.yml b/projects/riverbankcomputing.com/pyqt5/QtXml/package.yml index 10f813149d..ab2c4d3da9 100644 --- a/projects/riverbankcomputing.com/pyqt5/QtXml/package.yml +++ b/projects/riverbankcomputing.com/pyqt5/QtXml/package.yml @@ -27,7 +27,7 @@ dependencies: python.org: ~3.11 qt.io: ^5 riverbankcomputing.com/sip: '*' - riverbankcomputing.com/pyqt5/QtCore: '={{version}}' + riverbankcomputing.com/pyqt5/QtCore: '*' runtime: env: diff --git a/projects/riverbankcomputing.com/pyqt5/package.yml b/projects/riverbankcomputing.com/pyqt5/package.yml index 3afe874f8b..92f968c2de 100644 --- a/projects/riverbankcomputing.com/pyqt5/package.yml +++ b/projects/riverbankcomputing.com/pyqt5/package.yml @@ -26,16 +26,16 @@ platforms: - darwin/aarch64 dependencies: - riverbankcomputing.com/pyqt5/QtCore: '={{version}}' - riverbankcomputing.com/pyqt5/QtDBus: '={{version}}' - riverbankcomputing.com/pyqt5/QtNetwork: '={{version}}' - riverbankcomputing.com/pyqt5/QtSql: '={{version}}' - riverbankcomputing.com/pyqt5/QtTest: '={{version}}' - riverbankcomputing.com/pyqt5/QtXml: '={{version}}' - riverbankcomputing.com/pyqt5/QtGui: '={{version}}' - riverbankcomputing.com/pyqt5/QtSvg: '={{version}}' - riverbankcomputing.com/pyqt5/QtPrintSupport: '={{version}}' - riverbankcomputing.com/pyqt5/QtWidgets: '={{version}}' + riverbankcomputing.com/pyqt5/QtCore: '*' + riverbankcomputing.com/pyqt5/QtDBus: '*' + riverbankcomputing.com/pyqt5/QtNetwork: '*' + riverbankcomputing.com/pyqt5/QtSql: '*' + riverbankcomputing.com/pyqt5/QtTest: '*' + riverbankcomputing.com/pyqt5/QtXml: '*' + riverbankcomputing.com/pyqt5/QtGui: '*' + riverbankcomputing.com/pyqt5/QtSvg: '*' + riverbankcomputing.com/pyqt5/QtPrintSupport: '*' + riverbankcomputing.com/pyqt5/QtWidgets: '*' # Meta package: no build artifacts of its own — every sub-binding # carries its own .so into its own prefix and Python's pkgutil