From 3c0459006aa4b86239333f336c47b3538f980dfa Mon Sep 17 00:00:00 2001 From: pat-s Date: Fri, 31 Jul 2026 06:28:39 +0000 Subject: [PATCH] fix(patches): replace the RcppParallel system-TBB patch with a link-order fix RcppParallel 6.x rewrote its build: src/Makevars.in no longer has the `USE_TBB=Linux` block the registry patch edited, so the patch could never apply and every build logged "Patch for RcppParallel did not apply cleanly". The workaround it implemented is also obsolete. 6.x bundles oneTBB 2022 and builds it with cmake, which works on musl and with g++ 8-15, so there is no longer a reason to link a system TBB -- and linking one publishes a binary that needs a TBB the consumer does not have. What is still broken upstream is the link order. configure.R names the TBB directory with `-Wl,-L`, and gcc expands its own search dirs into `-L` options ahead of anything forwarded verbatim with `-Wl,`, so on a build host with a distro TBB installed `-ltbb` resolves to that library and RcppParallel.so records its SONAME instead of the bundled `libtbb.so`. The new patch passes a plain `-L` instead. Verified on alpine 3.24, redhat 8 and ubuntu noble: RcppParallel gets NEEDED libtbb.so with RPATH $ORIGIN/../lib and loads with every system libtbb moved away, and rstan 2.32.7 compiles and loads against it with no makevars override -- RcppParallel::CxxFlags() already emits -DTBB_INTERFACE_NEW -- so the rstan entry is dropped too. Summary: - replace RcppParallel/system-tbb.patch with bundled-tbb-link-order.patch - scope the RcppParallel entry to >=6.0.0 and widen its platforms to "*" - drop the rstan entry, made redundant by the bundled oneTBB - exclude local/patches/*.patch from the whitespace-mutating hooks, which rewrote context lines in every diff --- .pre-commit-config.yaml | 9 ++++ README.md | 2 +- .../RcppParallel/bundled-tbb-link-order.patch | 46 +++++++++++++++++++ local/patches/RcppParallel/system-tbb.patch | 19 -------- local/patches/registry.json | 30 ++---------- 5 files changed, 61 insertions(+), 45 deletions(-) create mode 100644 local/patches/RcppParallel/bundled-tbb-link-order.patch delete mode 100644 local/patches/RcppParallel/system-tbb.patch diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 140ae88..80650cc 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -7,12 +7,20 @@ exclude: | benchmark/| docker/reprex/ ) +# The `^local/patches/.*\.patch$` excludes below keep unified diffs byte-exact: +# a context line for a blank line is a single space, and stripping it (or +# appending a newline) makes `git apply` reject the patch, which surfaces as +# "patch did not apply cleanly" at build time rather than as a lint failure +# here. The exclusions are per-hook, not global, so `validate patch registry` +# still runs when a patch changes. repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v6.0.0 hooks: - id: end-of-file-fixer + exclude: ^local/patches/.*\.patch$ - id: trailing-whitespace + exclude: ^local/patches/.*\.patch$ args: - --markdown-linebreak-ext=md - repo: https://github.com/DavidAnson/markdownlint-cli2 @@ -31,6 +39,7 @@ repos: rev: v3.8.0 hooks: - id: editorconfig-checker + exclude: ^local/patches/.*\.patch$ - repo: https://github.com/adrienverge/yamllint.git rev: v1.38.0 hooks: diff --git a/README.md b/README.md index 28541a1..538df34 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ For every package+tag combination: ## Patching packages Some CRAN packages fail to compile on specific platforms due to compiler- or OS-specific issues unrelated to the package itself. -The canonical example is `RcppParallel`, whose bundled TBB sources fail on musl (Alpine) and newer compiler/OS combinations. +The canonical example is `RcppParallel`, whose bundled TBB is linked in a way that lets a system TBB on the build host shadow it, so the published binary depends on a library the consumer does not have. Because such packages are often transitive dependencies of many others, a single failure cascades: all dependents fail even though nothing is wrong with the dependent itself. To address this, frequently-failing packages can be "patched" before they are installed — whether as a direct build target or a transitive dependency pulled in by `pak`. diff --git a/local/patches/RcppParallel/bundled-tbb-link-order.patch b/local/patches/RcppParallel/bundled-tbb-link-order.patch new file mode 100644 index 0000000..45114a8 --- /dev/null +++ b/local/patches/RcppParallel/bundled-tbb-link-order.patch @@ -0,0 +1,46 @@ +diff --git a/tools/config/configure.R b/tools/config/configure.R +index 6293fe1..2f84337 100644 +--- a/tools/config/configure.R ++++ b/tools/config/configure.R +@@ -186,12 +186,23 @@ define( + ) + + # set PKG_LIBS ++# ++# bincraft patch: the library directories below are passed as plain '-L', not ++# '-Wl,-L'. gcc expands its own search dirs (/usr/lib64, /usr/lib/) ++# into explicit '-L' options ahead of anything forwarded verbatim with '-Wl,', ++# so with '-Wl,-L' a system libtbb.so wins over the one named here: on a build ++# host with a distro TBB installed, '-ltbb' resolves to that library and ++# RcppParallel.so records its SONAME (libtbb.so.12, or libtbb.so.2 for the ++# classic Intel TBB) instead of the bundled 'libtbb.so'. The binary then loads ++# the system TBB rather than the copy shipped in RcppParallel/lib, and fails ++# outright on a machine that has no system TBB. gcc places a plain '-L' before ++# its built-in dirs, so the intended library is found first. + pkgLibs <- if (!is.na(tbbLib)) { + + # a TBB supplied via TBB_LIB / TBB_ROOT. an rpath is meaningless on Windows, + # where the loader has no equivalent -- see R/zzz.R for how we resolve there + c( +- "-Wl,-L\"$(TBB_LIB)\"", ++ "-L\"$(TBB_LIB)\"", + if (.Platform$OS.type != "windows") + sprintf("-Wl,-rpath,%s", shQuote(tbbLib)), + "-l$(TBB_NAME)", +@@ -201,14 +212,14 @@ pkgLibs <- if (!is.na(tbbLib)) { + } else if (R.version$os == "emscripten") { + + c( +- "-Wl,-Ltbb/build/lib_release", ++ "-Ltbb/build/lib_release", + "-l$(TBB_NAME)" + ) + + } else { + + c( +- "-Wl,-Ltbb/build/lib_release", ++ "-Ltbb/build/lib_release", + "-l$(TBB_NAME)", + "-l$(TBB_MALLOC_NAME)" + ) diff --git a/local/patches/RcppParallel/system-tbb.patch b/local/patches/RcppParallel/system-tbb.patch deleted file mode 100644 index 263a593..0000000 --- a/local/patches/RcppParallel/system-tbb.patch +++ /dev/null @@ -1,19 +0,0 @@ -diff --git a/src/Makevars.in b/src/Makevars.in -index be8445f..7cb6c9e 100644 ---- a/src/Makevars.in -+++ b/src/Makevars.in -@@ -60,7 +60,13 @@ else - endif - - ifeq ($(UNAME), Linux) -- USE_TBB=Linux -+ # bincraft patch: link the system oneTBB (installed in the build-env images) -+ # instead of building the bundled Intel TBB, which fails on musl (Alpine) and -+ # newer toolchains (g++ 15). Leaving USE_TBB unset skips the bundled build; -+ # -DRCPP_PARALLEL_USE_TBB=1 keeps the TBB backend so dependents (rstan, ...) -+ # link TBB, and -ltbb/-ltbbmalloc pull the system library from default paths. -+ PKG_CXXFLAGS += -DRCPP_PARALLEL_USE_TBB=1 -DTBB_SUPPRESS_DEPRECATED_MESSAGES=1 -DTBB_INTERFACE_NEW -+ PKG_LIBS += -ltbb -ltbbmalloc - endif - - ifeq ($(UNAME), SunOS) diff --git a/local/patches/registry.json b/local/patches/registry.json index 99b80e3..fe0d387 100644 --- a/local/patches/registry.json +++ b/local/patches/registry.json @@ -1,42 +1,22 @@ [ { "package": "RcppParallel", - "versions": "*", - "platforms": [ - "alpine", - "ubuntu", - "redhat" - ], + "versions": ">=6.0.0", + "platforms": ["*"], "env": {}, "configure_args": [], "makevars": {}, - "patch": "RcppParallel/system-tbb.patch", - "reason": "RcppParallel bundles an old Intel TBB whose build fails on musl and modern toolchains (g++ 8-15 + modern make); link the system oneTBB (2021+) instead, keeping the TBB backend so dependents (rstan, ...) link TBB. All build-env platforms now provide oneTBB 2021+: native on alpine/ubuntu/el10, built from source into /usr/local on el8/el9 (their stock TBB is classic 2018/2020, too old)." + "patch": "RcppParallel/bundled-tbb-link-order.patch", + "reason": "RcppParallel 6.x bundles oneTBB 2022 and builds it with cmake, which works on musl and g++ 8-15, so the system-TBB workaround needed for 5.x is gone. What remains broken is the link order: configure.R passes the TBB directory as '-Wl,-L', and gcc expands its own search dirs (/usr/lib64, /usr/lib/) into '-L' options ahead of anything forwarded with '-Wl,'. On a build host with a distro TBB installed, '-ltbb' therefore resolves to the system library and RcppParallel.so records its SONAME (libtbb.so.12, or libtbb.so.2 for the classic Intel TBB on el8/el9) instead of the bundled 'libtbb.so'. The published binary then loads the system TBB rather than the copy in RcppParallel/lib and fails to dyn.load on a consumer machine without one, the same way fs did with libuv. Passing a plain '-L' puts the bundled build dir ahead of gcc's defaults; verified on alpine 3.24, el8 and ubuntu noble to produce NEEDED libtbb.so + RPATH $ORIGIN/../lib, loading with every system libtbb removed." }, { "package": "fs", "versions": "*", - "platforms": [ - "*" - ], + "platforms": ["*"], "env": {}, "configure_args": [], "makevars": {}, "patch": "fs/force-vendored-libuv.patch", "reason": "fs 2.x configure links system libuv whenever pkg-config finds libuv-devel (installed as a build-time sysreq), producing an fs.so with NEEDED libuv.so.1. That binary fails to dyn.load on consumer machines lacking runtime libuv, because install.packages/renv do not install SystemRequirements (only pak does, and only in the build container). The patch short-circuits configure to copy src/Makevars.vendor and build the bundled static libuv (needs cmake) so the binary is self-contained on every platform. An env/pkg-config override was tried first but the rebuilt binary still linked libuv.so.1, so a source patch is used instead." - }, - { - "package": "rstan", - "versions": "*", - "platforms": [ - "*" - ], - "env": {}, - "configure_args": [], - "makevars": { - "CPPFLAGS": "-DTBB_INTERFACE_NEW -I/usr/local/include" - }, - "patch": null, - "reason": "StanHeaders' init_threadpool_tbb.hpp unconditionally includes the legacy (removed in oneTBB 2021+) for version detection, breaking compilation of Module.cpp against the bundled oneTBB. Pre-defining TBB_INTERFACE_NEW skips that include and selects the modern tbb/global_control.h + tbb/task_arena.h path that the bundled TBB provides (-I/usr/local/include preserves the default CPPFLAGS the override replaces)" } ] -- 2.54.0