feat: patch registry + wiring for per-package patching (#103)
Some checks failed
ci/crow/cron/process-updates/15 Pipeline failed
ci/crow/cron/process-updates/14 Pipeline failed
ci/crow/cron/process-updates/16 Pipeline failed
ci/crow/cron/process-updates/18 Pipeline failed
ci/crow/cron/process-updates/11 Pipeline failed
ci/crow/cron/process-updates/12 Pipeline failed
ci/crow/cron/process-updates/17 Pipeline failed
ci/crow/cron/process-updates/6 Pipeline failed
ci/crow/cron/process-updates/5 Pipeline failed
Some checks failed
ci/crow/cron/process-updates/15 Pipeline failed
ci/crow/cron/process-updates/14 Pipeline failed
ci/crow/cron/process-updates/16 Pipeline failed
ci/crow/cron/process-updates/18 Pipeline failed
ci/crow/cron/process-updates/11 Pipeline failed
ci/crow/cron/process-updates/12 Pipeline failed
ci/crow/cron/process-updates/17 Pipeline failed
ci/crow/cron/process-updates/6 Pipeline failed
ci/crow/cron/process-updates/5 Pipeline failed
## Summary Adds the curated **patch registry** and wiring that drives bincraft's new package-patching mechanism (see bincraft PR `feat/package-patching`). Lets specific packages be patched (env/configure/Makevars overrides or source diffs) before pak installs them — including as transitive dependencies — so compiler-/OS-specific failures like RcppParallel's bundled TBB stop cascading. ## What's included - `local/patches/registry.json` — initial entry: RcppParallel with `RCPP_PARALLEL_USE_TBB=0` for alpine / ubuntu-2604, plus `local/patches/README.md` schema docs. - `local/validate-patches.R` — validates schema, referenced patch files, and ambiguous overlaps; clean failure + exit 1 (no stacktrace). - `.pre-commit-config.yaml` — a `validate-patches` hook (re-runs when the registry or the validator changes). - `local/build-one.R` / `local/build-all.R` — pass `patches = "local/patches"` to `bincraft::build_binary_package()`. - `specs/2026-06-30-package-patching-design.md` and `plans/2026-06-30-package-patching-implementation.md`. ## ⚠️ Merge ordering (blocker) This PR adds a `patches = ...` argument to `build_binary_package()` calls. The `.crow/*.yaml` workflows currently pin bincraft **v4.2.3**, which does not accept that argument — CI will error with `unused argument (patches=...)` until: 1. bincraft **v4.3.0** is released (PR `feat/package-patching`), and 2. the pin is bumped in `.crow/build-all-versions-install-deps.yaml`, `.crow/build-all-versions.yaml`, and `.crow/process-updates.yaml`. The `.crow` pin bump will be added to this PR once bincraft v4.3.0 is tagged. Do not merge before then. Reviewed-on: #103
This commit is contained in:
parent
1e910bc703
commit
55a18fd87d
5 changed files with 1791 additions and 10 deletions
|
|
@ -140,6 +140,7 @@ mapply(
|
|||
metadata_db_sslmode = "require",
|
||||
metadata_db_port = 15432,
|
||||
archive = TRUE,
|
||||
patches = "local/patches",
|
||||
upload = TRUE,
|
||||
store_build_metadata = TRUE
|
||||
)
|
||||
|
|
|
|||
|
|
@ -103,6 +103,7 @@ for (ver in versions) {
|
|||
force = TRUE,
|
||||
upload = TRUE,
|
||||
archive = TRUE,
|
||||
patches = "local/patches",
|
||||
store_build_metadata = TRUE,
|
||||
s3_endpoint = s3$s3_endpoint,
|
||||
s3_region = s3$s3_region,
|
||||
|
|
|
|||
43
local/patches/README.md
Normal file
43
local/patches/README.md
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
# Patch Registry
|
||||
|
||||
This directory contains the curated registry of per-package build-time patches consumed by bincraft's `patches` argument.
|
||||
|
||||
## Schema
|
||||
|
||||
The registry is defined in `registry.json` as an array of patch entries. Each entry specifies lightweight build-time overrides (environment variables, configure arguments, Makevars) and optionally a source diff to apply before building.
|
||||
|
||||
### Field semantics
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `package` | string | yes | CRAN package name. |
|
||||
| `versions` | string | yes | `"*"` for any, a constraint such as `">=5.1.0"`, or an exact version `"5.1.11-2"`. Env-tier fixes are typically `"*"`; source diffs are normally exact or lower-bounded because a diff is pinned to the source it was generated against. |
|
||||
| `platforms` | array of strings | yes | Matched against the running build's platform tokens — distro family (`alpine`, `ubuntu`, `redhat`), codename (`ubuntu-2604`, `alpine-324`), and arch (`amd64`, `arm64`). An entry matches if any listed token matches any build token. `["*"]` matches all platforms. |
|
||||
| `env` | object | no | Environment variables exported only for this package's isolated build. |
|
||||
| `configure_args` | array | no | Arguments passed as `--configure-args` to the isolated build. |
|
||||
| `makevars` | object | no | Key/value pairs written into a package-local Makevars for the isolated build. |
|
||||
| `patch` | string or null | no | Path (relative to `local/patches/`) to a unified diff applied to the unpacked CRAN source before building. |
|
||||
| `reason` | string | yes | Human explanation, surfaced in logs and metadata. |
|
||||
|
||||
## Adding an entry
|
||||
|
||||
To add a new patch entry:
|
||||
|
||||
1. Add an object to the array in `registry.json` with the fields documented above.
|
||||
Start with lightweight overrides (environment variables, configure arguments, Makevars) before resorting to source diffs.
|
||||
|
||||
2. If a source diff is needed, place it in `local/patches/<package>/<file>.patch` and reference its path in the `patch` field.
|
||||
For example, a diff for `RcppParallel` would go in `local/patches/RcppParallel/fix.patch` and be referenced as `"patch": "RcppParallel/fix.patch"`.
|
||||
|
||||
3. The `reason` field should clearly explain why the patch is needed and what problem it solves.
|
||||
|
||||
## Validation
|
||||
|
||||
The registry is validated and applied by bincraft during the build process.
|
||||
For manual validation, run the validator from the repo root:
|
||||
|
||||
```bash
|
||||
Rscript local/validate-patches.R
|
||||
```
|
||||
|
||||
This validates the schema, referenced patch-file existence, and checks for duplicate entries across platforms and versions.
|
||||
12
local/patches/registry.json
Normal file
12
local/patches/registry.json
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
[
|
||||
{
|
||||
"package": "RcppParallel",
|
||||
"versions": "*",
|
||||
"platforms": ["alpine", "ubuntu-2604"],
|
||||
"env": { "RCPP_PARALLEL_USE_TBB": "0" },
|
||||
"configure_args": [],
|
||||
"makevars": {},
|
||||
"patch": null,
|
||||
"reason": "bundled Intel TBB fails to build on musl and on newer toolchains (e.g. g++ 15 on ubuntu-2604); disabling TBB falls back to TinyThread"
|
||||
}
|
||||
]
|
||||
56
local/validate-patches.R
Normal file
56
local/validate-patches.R
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
#!/usr/bin/env Rscript
|
||||
# Validate local/patches/registry.json: schema, referenced patch files, and
|
||||
# ambiguous overlaps. Exits 1 on any problem. Used by pre-commit and CI.
|
||||
|
||||
dir <- "local/patches"
|
||||
registry_file <- file.path(dir, "registry.json")
|
||||
if (!file.exists(registry_file)) {
|
||||
cat("No registry.json found; nothing to validate.\n")
|
||||
quit(status = 0L)
|
||||
}
|
||||
|
||||
or_q <- function(x) if (is.null(x)) "?" else x
|
||||
|
||||
reg <- jsonlite::fromJSON(registry_file, simplifyVector = FALSE)
|
||||
required <- c("package", "versions", "platforms", "reason")
|
||||
errs <- character(0L)
|
||||
|
||||
for (i in seq_along(reg)) {
|
||||
e <- reg[[i]]
|
||||
missing <- setdiff(required, names(e))
|
||||
if (length(missing) > 0L) {
|
||||
errs <- c(errs, sprintf(
|
||||
"entry %d (%s): missing %s", i,
|
||||
if (is.null(e$package)) "?" else e$package, toString(missing)
|
||||
))
|
||||
}
|
||||
if (!is.null(e$patch)) {
|
||||
p <- file.path(dir, e$patch)
|
||||
if (!file.exists(p)) {
|
||||
errs <- c(errs, sprintf("entry %d (%s): patch file '%s' missing",
|
||||
i, e$package, p))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Ambiguous overlap: two entries for the same package with identical platforms
|
||||
# and versions.
|
||||
keys <- vapply(reg, function(e) {
|
||||
sprintf(
|
||||
"%s|%s|%s",
|
||||
or_q(e$package),
|
||||
paste(sort(as.character(unlist(e$platforms))), collapse = ","),
|
||||
or_q(e$versions)
|
||||
)
|
||||
}, character(1L))
|
||||
dups <- keys[duplicated(keys)]
|
||||
if (length(dups) > 0L) {
|
||||
errs <- c(errs, sprintf("ambiguous duplicate entries: %s", toString(unique(dups))))
|
||||
}
|
||||
|
||||
if (length(errs) > 0L) {
|
||||
cat("Patch registry validation FAILED:\n")
|
||||
cat(paste0(" - ", errs, "\n"))
|
||||
quit(status = 1L)
|
||||
}
|
||||
cat(sprintf("Patch registry OK (%d %s).\n", length(reg), if (length(reg) == 1L) "entry" else "entries"))
|
||||
Loading…
Reference in a new issue