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)" } ]