From 9bc49731591c5d506ecf894364f61e3853723742 Mon Sep 17 00:00:00 2001 From: pat-s Date: Tue, 14 Jul 2026 18:40:41 +0000 Subject: [PATCH] fix(local): stop proposing per-dependent patches for dependency-cascade failures (#121) ## Problem The scheduled `--open-issue` run (issue #120) posted **800+** proposed entries, all `rcppparallel-bundled-tbb` on `ubuntu-2604`, each pre-filling `RcppParallel/disable-tbb.patch` for an unrelated package (`ACEsimFit`, `AovBay`, `AdaptGauss`, ...). Root cause: those packages fail on ubuntu-2604 only because their **RcppParallel dependency** does not build there. bincraft records RcppParallel's TBB error text against each dependent, so every one of them matches the `rcppparallel-bundled-tbb` signature. Proposing a RcppParallel source patch for `ACEsimFit` is not just noise -- it is **broken**: the diff targets RcppParallel's `Makevars.in`, so it cannot apply to a dependent's source. ## Fix Add an optional `applies_to` field that pins a package-specific fix to the package it targets: - A signature whose fix is a curated per-package source patch (`rcppparallel-bundled-tbb` -> `RcppParallel`) is only ever proposed **for that package**. - Other packages matching the signature are downstream failures, reported as **"blocked on ``"** and never proposed a bogus entry. Fix the dependency once and the whole cascade clears. Generic levers (the `tbb-stddef-removed` makevars fix) are unaffected -- they carry no `applies_to`, so they still apply to any matching package. ## Changes - gate proposal generation in `build_triage_report()` on `applies_to`; add a `blocked_on` field to group records - surface blocked groups in both `failing-builds-report.R` and `propose-patches.R` instead of the misleading "already registered" note - document `applies_to` in the signature-table header ## Verification - New tests: RcppParallel dependents are blocked (no `proposed_entries`, `blocked_on = "RcppParallel"`), while RcppParallel *itself* is still proposed when it is the failing package. - Reproduced the avalanche end-to-end with a stubbed DB: 5 dependents now report "Blocked on RcppParallel: 5 package(s) fail because RcppParallel does not build" and **zero** candidates are emitted; a genuine `tbb-stddef` proposal in the same run is unaffected. - Full suite: 84 tests pass; all pre-commit hooks pass. Refs #120. Note: the underlying build problem (RcppParallel failing on ubuntu-2604 despite its registry entry) is real and separate -- this PR stops the classifier from spamming broken per-dependent proposals about it. Reviewed-on: https://git.devxy.io/devxy/build-cran-binaries/pulls/121 --- local/failing-builds-classify.R | 31 ++++++++++++++++++- local/failing-builds-report.R | 7 +++++ local/propose-patches.R | 15 ++++++++++ local/tests/test-failing-builds-classify.R | 35 ++++++++++++++++++++++ 4 files changed, 87 insertions(+), 1 deletion(-) diff --git a/local/failing-builds-classify.R b/local/failing-builds-classify.R index 30c168d..4ce30cf 100644 --- a/local/failing-builds-classify.R +++ b/local/failing-builds-classify.R @@ -15,6 +15,11 @@ # would require a brand-new source diff for a previously-unseen package stay # `auto = FALSE` -> classified, but always routed to human triage, per the # issue's guardrail against shipping autonomous novel source diffs. +# `applies_to` (optional) pins a package-specific fix to the package it targets: +# a signature whose registry patch belongs to one package (e.g. RcppParallel's +# disable-tbb patch) is only proposed for that package. Other packages matching +# the signature are downstream failures blocked on that dependency, not +# individually patchable, so they are reported but never proposed an entry. # # Seeded from the existing registry entries and known recurring failures; add # a row here as new signatures are confirmed. Order matters: the first match @@ -45,6 +50,14 @@ build_signatures <- function() { tier = "patch", confidence = "high", auto = TRUE, + # The fix is a RcppParallel-specific source patch, so it is only ever + # proposed for RcppParallel itself. The hundreds of packages that fail + # merely because their RcppParallel *dependency* did not build carry + # RcppParallel's error text and match this signature too -- they are + # blocked on RcppParallel, not individually patchable (applying this diff + # to their source is meaningless). `applies_to` pins the proposal so those + # downstream failures are never proposed a bogus per-package entry. + applies_to = "RcppParallel", fix = "apply the curated RcppParallel/disable-tbb.patch so the bundled TBB build is skipped and the TinyThread backend is used", example = "RcppParallel", registry = list( @@ -278,9 +291,23 @@ build_triage_report <- function( unregistered <- setdiff(pkgs, registered_pkgs) auto_proposable <- isTRUE(sig$auto) && sig$matched + # A signature whose fix is package-specific (`applies_to`) may only be + # proposed for that package. Everything else matching it is a downstream + # failure blocked on that package (e.g. RcppParallel dependents carrying + # RcppParallel's own error text) -- never propose those a bogus entry. + proposable <- unregistered + blocked_on <- NULL + if (!is.null(sig$applies_to)) { + proposable <- intersect(unregistered, sig$applies_to) + downstream <- setdiff(pkgs, sig$applies_to) + if (length(downstream) > 0L) { + blocked_on <- sig$applies_to + } + } + proposed <- list() if (auto_proposable) { - for (p in unregistered) { + for (p in proposable) { proposed[[p]] <- propose_registry_entry_list( sig, p, @@ -297,6 +324,8 @@ build_triage_report <- function( tier = sig$tier, confidence = sig$confidence, suggested_fix = sig$fix, + applies_to = sig$applies_to, + blocked_on = blocked_on, fingerprint = names(fp_tab)[[1L]], fingerprint_variants = length(fp_tab), build_count = nrow(g), diff --git a/local/failing-builds-report.R b/local/failing-builds-report.R index 60f65ea..51f5bc7 100644 --- a/local/failing-builds-report.R +++ b/local/failing-builds-report.R @@ -194,6 +194,13 @@ for (r in report) { ) cat(paste0(" ", gsub("\n", "\n ", j)), "\n", sep = "") } + } else if (!is.null(r$blocked_on)) { + cat(sprintf( + " (blocked on %s -- these %d package(s) fail because that dependency does not build; fix %s, do not patch each dependent)\n", + toString(r$blocked_on), + length(r$packages), + toString(r$blocked_on) + )) } else if (r$auto_proposable) { cat(" (all affected packages already have a registry entry)\n") } diff --git a/local/propose-patches.R b/local/propose-patches.R index d43668a..7ff195b 100644 --- a/local/propose-patches.R +++ b/local/propose-patches.R @@ -135,6 +135,21 @@ for (r in report) { } } +# Groups blocked on a dependency (e.g. RcppParallel dependents) are reported, +# not proposed: fixing the named dependency clears them all at once. +blocked <- Filter(function(r) !is.null(r$blocked_on), report) +if (length(blocked) > 0L) { + cat("\nBlocked on a dependency (fix the dependency, not each dependent):\n") + for (r in blocked) { + cat(sprintf( + " %s: %d package(s) fail because %s does not build\n", + toString(r$blocked_on), + length(r$packages), + toString(r$blocked_on) + )) + } +} + # A package that maps to more than one auto-proposable signature is ambiguous # (conflicting fix tiers) and would collide on the same registry key; route it # to human triage instead of emitting both. diff --git a/local/tests/test-failing-builds-classify.R b/local/tests/test-failing-builds-classify.R index 66cd26b..a365e14 100644 --- a/local/tests/test-failing-builds-classify.R +++ b/local/tests/test-failing-builds-classify.R @@ -85,3 +85,38 @@ test_that("propose_registry_entry fills a schema-valid entry for a known lever", # No template -> no proposal (unclassified path). expect_null(propose_registry_entry(classify_error("weird"), "x", "alpine")) }) + +test_that("RcppParallel dependents are blocked, not proposed a per-package patch", { + # Hundreds of packages fail on ubuntu-2604 only because their RcppParallel + # dependency fails to build, so they carry RcppParallel's TBB error text. + failures <- data.frame( + name = c("ACEsimFit", "AovBay", "AdaptGauss"), + platform = "ubuntu-2604", + arch = "amd64", + error_text = "Error: USE_TBB=Linux is not supported on this toolchain", + stringsAsFactors = FALSE + ) + report <- build_triage_report(failures, registered_pkgs = character(0L)) + grp <- Filter(function(g) g$signature == "rcppparallel-bundled-tbb", report)[[ + 1L + ]] + # No bogus per-dependent entries (the patch targets RcppParallel's source). + expect_null(grp$proposed_entries) + expect_identical(grp$blocked_on, "RcppParallel") +}) + +test_that("RcppParallel itself is still proposed when it is the failing package", { + failures <- data.frame( + name = "RcppParallel", + platform = "ubuntu-2604", + arch = "amd64", + error_text = "Error: USE_TBB=Linux is not supported on this toolchain", + stringsAsFactors = FALSE + ) + report <- build_triage_report(failures, registered_pkgs = character(0L)) + grp <- report[[1L]] + expect_identical(grp$signature, "rcppparallel-bundled-tbb") + expect_false(is.null(grp$proposed_entries)) + expect_true("RcppParallel" %in% names(grp$proposed_entries)) + expect_null(grp$blocked_on) # RcppParallel is the target, not a dependent +})