diff --git a/local/failing-builds-classify.R b/local/failing-builds-classify.R index 716fab2..73922b3 100644 --- a/local/failing-builds-classify.R +++ b/local/failing-builds-classify.R @@ -385,6 +385,7 @@ build_triage_report <- function( applies_to = sig$applies_to, blocked_on = blocked_on, blocked_packages = blocked_packages, + blocked_map = if (length(blocked_map) > 0L) blocked_map else NULL, fingerprint = names(fp_tab)[[1L]], fingerprint_variants = length(fp_tab), build_count = nrow(g), diff --git a/local/proposal-tracking-lib.R b/local/proposal-tracking-lib.R index 9568d78..28f65db 100644 --- a/local/proposal-tracking-lib.R +++ b/local/proposal-tracking-lib.R @@ -160,24 +160,38 @@ unclassified_summary <- function(report, max_groups = 30L, max_pkgs = 15L) { ) } -# Groups with packages blocked on a dependency (a package-specific fix pinned -# via `applies_to`, or a data-driven cascade where the package fails building a -# dependency): report the dependency + how many dependents wait on it, so fixing -# it once is recognised as clearing the batch. -blocked_summary <- function(report, max_pkgs = 15L) { - bl <- Filter( - function(g) length(g$blocked_packages %||% character(0L)) > 0L, - report - ) - lapply(bl, function(g) { - pkgs <- g$blocked_packages +# Aggregate blocked packages across ALL groups by the dependency they wait on, +# so one dependency (RcppParallel, rstan, sf, ...) is a single line -- deduped +# and ranked by how many distinct dependents it blocks -- instead of repeating +# once per fingerprint group. Reads each group's `blocked_map` (package -> the +# dependency it is blocked on). Returns records sorted by dependent count desc, +# each with up to `max_pkgs` example dependents. +blocked_by_dependency <- function(report, max_pkgs = 15L) { + acc <- list() # dependency -> character vector of dependent packages + for (g in report) { + bm <- g$blocked_map + if (is.null(bm) || length(bm) == 0L) { + next + } + for (pkg in names(bm)) { + for (dep in as.character(unlist(bm[[pkg]]))) { + acc[[dep]] <- unique(c(acc[[dep]], pkg)) + } + } + } + if (length(acc) == 0L) { + return(list()) + } + out <- lapply(names(acc), function(dep) { + pkgs <- acc[[dep]] list( - blocked_on = g$blocked_on, + dependency = dep, n_packages = length(pkgs), packages = utils::head(pkgs, max_pkgs), packages_truncated = length(pkgs) > max_pkgs ) }) + out[order(-vapply(out, function(x) x$n_packages, integer(1L)))] } # Does a registry entry's `platforms` apply to a build on `os` (e.g. diff --git a/local/proposal-tracking.R b/local/proposal-tracking.R index 4d82a11..f8e1284 100644 --- a/local/proposal-tracking.R +++ b/local/proposal-tracking.R @@ -127,13 +127,13 @@ if (length(retire) > 0L) { # --------------------------------------------------------------------------- # Blind spots: failures the classifier could not auto-propose. # --------------------------------------------------------------------------- -blocked <- blocked_summary(report) +blocked <- blocked_by_dependency(report) unmatched <- unclassified_summary(report) cat("\nBlocked on a dependency (fix the dependency, not each dependent):\n") if (length(blocked) > 0L) { for (b in blocked) { - cat(sprintf(" %s: %d dependent(s) waiting\n", b$blocked_on, b$n_packages)) + cat(sprintf(" %-20s %5d dependent(s)\n", b$dependency, b$n_packages)) } } else { cat(" (none)\n") @@ -207,8 +207,8 @@ if (do_issue) { body_lines <- c( body_lines, sprintf( - "- **%s**: %d dependent(s) waiting (e.g. %s%s)", - b$blocked_on, + "- **%s**: %d dependent(s) (e.g. %s%s)", + b$dependency, b$n_packages, toString(b$packages), if (isTRUE(b$packages_truncated)) ", ..." else "" diff --git a/local/propose-patches.R b/local/propose-patches.R index cfce338..360df9f 100644 --- a/local/propose-patches.R +++ b/local/propose-patches.R @@ -148,21 +148,20 @@ 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) length(r$blocked_packages) > 0L, - report -) +# Packages blocked on a dependency are reported (aggregated by dependency, +# ranked by impact), not proposed: fixing the named dependency clears the batch. +blocked <- blocked_by_dependency(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$blocked_packages), - toString(r$blocked_on) - )) + n_blocked_pkgs <- length(unique(unlist( + lapply(report, function(r) r$blocked_packages) + ))) + cat(sprintf( + "\nBlocked on a dependency (%d dependencies block %d dependents; fix the dependency, not each dependent):\n", + length(blocked), + n_blocked_pkgs + )) + for (b in blocked) { + cat(sprintf(" %-20s %5d dependent(s)\n", b$dependency, b$n_packages)) } } diff --git a/local/tests/test-proposal-tracking-lib.R b/local/tests/test-proposal-tracking-lib.R index 05cd0e9..869d2b3 100644 --- a/local/tests/test-proposal-tracking-lib.R +++ b/local/tests/test-proposal-tracking-lib.R @@ -146,20 +146,35 @@ test_that("unclassified_summary ranks unknown groups and caps output", { expect_identical(capped$dropped_groups, 1L) }) -test_that("blocked_summary lists each dependency and its dependent count", { +test_that("blocked_by_dependency aggregates across groups, deduped and ranked", { + # RcppParallel dependents split across platforms/fingerprints -> separate + # groups, but one aggregated line; a dependent seen twice is counted once. failures <- data.frame( - name = c("ACEsimFit", "AovBay", "AdaptGauss"), - platform = "ubuntu-2604", + name = c("ACEsimFit", "AovBay", "AdaptGauss", "ACEsimFit", "loner"), + platform = c( + "ubuntu-2604", + "ubuntu-2604", + "alpine-324", + "alpine-324", + "ubuntu-2604" + ), arch = "amd64", - error_text = "Error: USE_TBB=Linux is not supported on this toolchain", + error_text = c( + rep("Error: USE_TBB=Linux is not supported on this toolchain", 4L), + "Failed to build source package rstan.\nfatal error: tbb/tbb_stddef.h" + ), stringsAsFactors = FALSE ) report <- build_triage_report(failures, registered_pkgs = character(0L)) - b <- blocked_summary(report, max_pkgs = 2L) - expect_length(b, 1L) - expect_identical(b[[1L]]$blocked_on, "RcppParallel") - expect_identical(b[[1L]]$n_packages, 3L) - expect_true(b[[1L]]$packages_truncated) + agg <- blocked_by_dependency(report, max_pkgs = 2L) + deps <- vapply(agg, function(b) b$dependency, character(1L)) + expect_true("RcppParallel" %in% deps && "rstan" %in% deps) + rcpp <- Filter(function(b) b$dependency == "RcppParallel", agg)[[1L]] + # ACEsimFit appears in two groups -> counted once (3 distinct dependents). + expect_identical(rcpp$n_packages, 3L) + expect_true(rcpp$packages_truncated) # capped at max_pkgs = 2 + # Ranked by dependent count: RcppParallel (3) before rstan (1). + expect_identical(deps[[1L]], "RcppParallel") }) test_that("entry_applies_to_os matches codename, family, and wildcard", {