feat(local): detect dependency-cascade failures generally, not just RcppParallel (#128)

## Why (from the #127 trial-build gate)

The gate did its job: 0/3 passed, merge blocked. The log showed *why* -- BFpack, BayesERtools, GMLTM all fail while building their shared dependency **`rstan`**, not in their own code:

```
Failed to build source package rstan.
  .../StanHeaders/include/stan/math/prim/core/init_threadpool_tbb.hpp:9:10:
  fatal error: tbb/tbb_stddef.h: No such file or directory
```

So the per-package `-DTBB_INTERFACE_NEW` makevars entries the classifier proposed are useless for these packages -- they're blocked on `rstan` (which already has a registry entry). This is the **same dependency cascade** the RcppParallel `applies_to` guard catches, but `tbb-stddef-removed` is a generic signature with no such pin, so ~73 Stan packages kept getting proposed.

## What

Generalise cascade detection beyond the RcppParallel special case:

- `failing_dependency(error_text, package)` -- when the log names a **different** package as the one that failed to compile (`Failed to build source package X`, `compilation failed for package 'X'`, `dependency 'X' ... not available`), that package is the real cause.
- `build_triage_report()` now blocks any package whose **every** failing build is such a cascade: reported as `blocked_on` that dependency, never proposed a bogus per-package entry. A package that fails in its **own** compilation is still proposed.
- The `applies_to` (RcppParallel) and data-driven (rstan) cases are unified into one `blocked_packages` / `blocked_on` model; the report, proposer, and `blocked_summary` count the actually-blocked packages, and the blocked note shows even when a group also has genuine proposals.

## Effect

Next auto-apply run will stop proposing the rstan-blocked Stan packages (and any future dependency cascade) and surface them as "blocked on rstan" instead. Fixing `rstan` once clears the whole cluster.

## Verification

- New tests: `failing_dependency` (cascade vs own-compile vs none), and an end-to-end split where BFpack/GMLTM (blocked on rstan) are not proposed while an own-compile package still is.
- Full suite: 112 tests pass; all pre-commit hooks pass.

Refs #120, #127. (Separate follow-ups: fixing rstan's build itself, and quieting the gate's metadata-DB retry storm -- both root-caused to bincraft.)

Reviewed-on: #128
This commit is contained in:
Patrick Schratz 2026-07-16 08:23:54 +00:00 committed by Patrick Schratz
commit f9d399fac0

View file

@ -105,6 +105,49 @@ test_that("RcppParallel dependents are blocked, not proposed a per-package patch
expect_identical(grp$blocked_on, "RcppParallel")
})
test_that("failing_dependency names the dependency that actually failed", {
# A leaf package (BFpack) that fails building its rstan dependency.
txt <- paste(
"Error in installing dependencies for package BFpack with tag 1.6.1",
"Failed to build source package rstan.",
"ERROR: compilation failed for package rstan",
sep = "\n"
)
expect_identical(failing_dependency(txt, "BFpack"), "rstan")
# A package failing in its OWN compilation is not a cascade.
own <- "ERROR: compilation failed for package BFpack"
expect_true(is.na(failing_dependency(own, "BFpack")))
expect_true(is.na(failing_dependency(NA_character_, "x")))
expect_true(is.na(failing_dependency("some unrelated error", "x")))
})
test_that("Stan packages blocked on rstan are not proposed a per-package entry", {
# BFpack/GMLTM fail building rstan; the tbb error is in rstan's compile.
cascade <- paste(
"Failed to build source package rstan.",
"fatal error: tbb/tbb_stddef.h: No such file or directory",
sep = "\n"
)
failures <- data.frame(
name = c("BFpack", "GMLTM", "someOwnPkg"),
platform = "ubuntu-2604",
arch = "amd64",
error_text = c(
cascade,
cascade,
# someOwnPkg fails in its OWN compile on the same header -> fixable.
"someOwnPkg.cpp: fatal error: tbb/tbb_stddef.h: No such file or directory"
),
stringsAsFactors = FALSE
)
report <- build_triage_report(failures, registered_pkgs = character(0L))
grp <- Filter(function(g) g$signature == "tbb-stddef-removed", report)[[1L]]
# Only the own-compile package is proposed; the rstan cascades are blocked.
expect_identical(names(grp$proposed_entries), "someOwnPkg")
expect_setequal(grp$blocked_packages, c("BFpack", "GMLTM"))
expect_identical(grp$blocked_on, "rstan")
})
test_that("RcppParallel itself is still proposed when it is the failing package", {
failures <- data.frame(
name = "RcppParallel",