From b47378c2317287c02938ab9b31531d6d683a950e Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 10 Nov 2026 00:48:03 +0000 Subject: [PATCH 1/9] Add propagation status for every package type and target propagation_checks() applies the rules get_candidates() uses and reports the rules each package fails; get_candidates() now keeps the packages that pass, with the same results as before. BBS commits come from bbs_status() for every package type instead of only software. propagation_status() combines the source and binary targets for a branch, with binaries only for software as Bioconductor distributes, and write_comparisons() saves it. propagation_table() and propagation_summary_table() show it. Co-Authored-By: Claude Opus 5.5 --- NAMESPACE | 5 + R/compare.R | 25 +++- R/propagation.R | 180 +++++++++++++++++++++++++++++ R/utils.R | 186 +++++++++++++++++------------- man/propagation_checks.Rd | 66 +++++++++++ man/propagation_status.Rd | 45 ++++++++ man/propagation_summary_table.Rd | 23 ++++ man/propagation_table.Rd | 23 ++++ man/propagation_targets.Rd | 21 ++++ man/write_comparisons.Rd | 5 +- tests/testthat/test-propagation.R | 51 ++++++++ 11 files changed, 544 insertions(+), 86 deletions(-) create mode 100644 R/propagation.R create mode 100644 man/propagation_checks.Rd create mode 100644 man/propagation_status.Rd create mode 100644 man/propagation_summary_table.Rd create mode 100644 man/propagation_table.Rd create mode 100644 man/propagation_targets.Rd create mode 100644 tests/testthat/test-propagation.R diff --git a/NAMESPACE b/NAMESPACE index 9e47f66..c9675bb 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -30,6 +30,11 @@ export(is_unsupported_platform) export(manifest_url) export(normalize_result) export(parse_uni_log) +export(propagation_checks) +export(propagation_status) +export(propagation_summary_table) +export(propagation_table) +export(propagation_targets) export(r_xy_ver) export(remove_old_binaries) export(summary_table) diff --git a/R/compare.R b/R/compare.R index 11d4855..4c2380b 100644 --- a/R/compare.R +++ b/R/compare.R @@ -143,7 +143,8 @@ compare_summary <- function(cmp) { #' @param file character path to a YAML file with `branches`, `types`, and #' `data_dir`. Optional: `platform` (BBS), `os` and `arch` (R Universe), #' `errors` (default `true`), and `max_log_downloads`, the number of R -#' Universe logs to download per run across branches (default 400). +#' Universe logs to download per run across branches (default 400), and +#' `propagation` (default `true`). #' #' @returns list #' @@ -151,7 +152,8 @@ compare_summary <- function(cmp) { .read_compare_config <- function(file) { config <- yaml::read_yaml(file) defaults <- list(platform = "x86_64-linux-gnu", os = "linux", - arch = "x86_64", errors = TRUE, max_log_downloads = 400) + arch = "x86_64", errors = TRUE, max_log_downloads = 400, + propagation = TRUE) utils::modifyList(defaults, config) } @@ -161,8 +163,9 @@ compare_summary <- function(cmp) { #' Saves one `compare--.rds` per branch and type and a #' `summary.rds` in `data_dir`. If the configuration has `errors: true`, also #' saves `errors-.rds` from `uni_errors()`, with R Universe logs cached -#' in `data_dir/logs`. Reports read these files, so the data is downloaded -#' once. +#' in `data_dir/logs`. If it has `propagation: true`, also saves +#' `propagation-.rds` from `propagation_status()`. Reports read these +#' files, so the data is downloaded once. #' #' @param config character path to a YAML configuration. See #' `reports/config.yml` on the `reports` branch of the biocUniTools @@ -199,6 +202,20 @@ write_comparisons <- function(config) { summaries[[paste(branch, type)]] <- compare_summary(cmp) cmps[[type]] <- cmp } + if (isTRUE(config$propagation)) { + # BBS commits are in the comparisons, so they are not read again + bbs <- do.call(rbind, lapply(cmps, function(cmp) { + cmp <- cmp[!is.na(cmp$bbs_node), ] + data.frame(Package = cmp$Package, Version = cmp$bbs_version, + BBS_commit = substr(cmp$bbs_commit, 1, 7), + Deprecated = cmp$bbs_deprecated) + })) + status <- propagation_status(branch, types = config$types, + jobs = jobs, bbs = bbs) + attr(status, "created") <- Sys.time() + saveRDS(status, file.path(data_dir, + sprintf("propagation-%s.rds", branch))) + } if (isTRUE(config$errors)) { errors <- uni_errors( compare_failing(cmps), cache_dir = log_dir, diff --git a/R/propagation.R b/R/propagation.R new file mode 100644 index 0000000..d41c0ea --- /dev/null +++ b/R/propagation.R @@ -0,0 +1,180 @@ +#' Targets packages propagate to +#' +#' @description The source package and the binaries Bioconductor distributes. +#' Bioconductor only distributes binaries of software packages, so `types` +#' lists the package types each target applies to. +#' +#' @returns data.frame with columns id, label, type, os, arch, and types (a +#' list column) +#' +#' @examples +#' propagation_targets() +#' +#' @export +propagation_targets <- function() { + data.frame( + id = c("source", "windows_x86_64", "windows_arm64", "macosx_arm64", + "macosx_x86_64"), + label = c("Source", "Windows x86_64", "Windows arm64", "macOS arm64", + "macOS x86_64"), + type = c("source", "binary", "binary", "binary", "binary"), + os = c(NA, "windows", "windows", "macosx", "macosx"), + arch = c(NA, "x86_64", "arm64", "arm64", "x86_64"), + types = I(c(list(bioc_pkg_types()$type), rep(list("software"), 4)))) +} + +#' Which Bioconductor packages propagate from R Universe +#' +#' @description +#' Applies `propagation_checks()` with the rules `harvest.R` uses, requiring +#' the same commit as BBS, for each of `targets` and every package of `types` +#' in the branch's manifest. +#' +#' @param branch character "devel" or "release" +#' @param types character package types, from `bioc_pkg_types()$type` +#' @param jobs data.frame from `uni_jobs()`. If `NULL` (default), downloaded. +#' @param bbs data.frame of BBS commits with columns Package, Version, +#' BBS_commit, and Deprecated. If `NULL` (default), read with +#' `bbs_status()`. +#' @param targets data.frame from `propagation_targets()` +#' +#' @returns data.frame with one row per package and columns Package, type, +#' Version, deprecated, and, for each target id, a logical column whether +#' it propagates and `_reason`, why not. A package with no job for a +#' target does not propagate, with reason "not built". Targets that do not +#' apply to a package's type are `NA`. +#' +#' @examples +#' \dontrun{ +#' propagation_status("devel", types = "books") +#' } +#' +#' @export +propagation_status <- function(branch, types = bioc_pkg_types()$type, + jobs = NULL, bbs = NULL, + targets = propagation_targets()) { + bu <- uni_for_bioc(branch) + if (is.null(jobs)) + jobs <- uni_jobs(branch) + if (is.null(bbs)) + bbs <- .bbs_commits(bu) + + pkgs <- do.call(rbind, lapply(types, function(type) + data.frame(Package = bioc_manifest(branch, type), type = type))) + pkgs$Version <- jobs$Version[match(pkgs$Package, jobs$Package)] + pkgs$deprecated <- bbs$Deprecated[match(pkgs$Package, bbs$Package)] %in% + TRUE + + for (i in seq_len(nrow(targets))) { + checks <- propagation_checks(bu, os = targets$os[i], + arch = targets$arch[i], + type = targets$type[i], commit = TRUE, + jobs = jobs, bbs = bbs) + j <- match(pkgs$Package, checks$Package) + id <- targets$id[i] + applies <- pkgs$type %in% targets$types[[i]] + pkgs[[id]] <- ifelse(applies, !is.na(j) & checks$propagates[j] %in% TRUE, + NA) + pkgs[[paste0(id, "_reason")]] <- ifelse( + !applies, NA_character_, + ifelse(is.na(j), "not built", checks$reason[j])) + } + pkgs <- pkgs[order(pkgs$Package), , drop = FALSE] + rownames(pkgs) <- NULL + structure(pkgs, targets = targets, branch = branch) +} + +#' Number of packages that propagate to each target +#' +#' @param status data.frame from `propagation_status()` +#' +#' @returns htmltools table with a row per target +#' +#' @examples +#' \dontrun{ +#' propagation_summary_table(propagation_status("devel", types = "books")) +#' } +#' +#' @export +propagation_summary_table <- function(status) { + targets <- attr(status, "targets") + fmt <- function(x) format(x, big.mark = ",") + rows <- lapply(seq_len(nrow(targets)), function(i) { + x <- status[[targets$id[i]]] + htmltools::tags$tr( + htmltools::tags$td(targets$label[i]), + htmltools::tags$td(class = "text-end", fmt(sum(x, na.rm = TRUE))), + htmltools::tags$td(class = "text-end", + fmt(sum(!x, na.rm = TRUE)))) + }) + htmltools::tags$table( + class = "table table-sm propagation-summary", + htmltools::tags$thead(htmltools::tags$tr( + htmltools::tags$th("Target"), + htmltools::tags$th(class = "text-end", "Propagate"), + htmltools::tags$th(class = "text-end", "Don't propagate"))), + htmltools::tags$tbody(rows)) +} + +#' Interactive table of which packages propagate +#' +#' @param status data.frame from `propagation_status()` +#' +#' @returns reactable htmlwidget +#' +#' @examples +#' \dontrun{ +#' propagation_table(propagation_status("devel", types = "books")) +#' } +#' +#' @export +propagation_table <- function(status) { + if (!requireNamespace("reactable", quietly = TRUE)) + stop("Install 'reactable' to use propagation_table()") + targets <- attr(status, "targets") + types <- bioc_pkg_types() + branch <- attr(status, "branch") + df <- data.frame( + Package = sprintf('%s', + mapply(.landing_url, status$Package, status$type, + branch), + status$Package), + type = types$label[match(status$type, types$type)], + Version = status$Version, + deprecated = ifelse(status$deprecated, + paste0('', + 'Deprecated'), + "")) + # "Yes", or "No" and the reasons + answer <- function(yes, reason) + dplyr::case_when( + is.na(yes) ~ '—', + yes ~ 'Yes', + .default = sprintf( + paste0('No', + '
%s'), + htmltools::htmlEscape(reason))) + columns <- list( + Package = reactable::colDef(name = "Package", html = TRUE, + minWidth = 160), + type = reactable::colDef(name = "Type", minWidth = 120), + Version = reactable::colDef(name = "Version", minWidth = 90), + deprecated = reactable::colDef(name = "Deprecated", html = TRUE, + minWidth = 110)) + for (i in seq_len(nrow(targets))) { + id <- targets$id[i] + df[[id]] <- answer(status[[id]], status[[paste0(id, "_reason")]]) + columns[[id]] <- reactable::colDef(name = targets$label[i], + html = TRUE, minWidth = 130) + } + reactable::reactable( + df, columns = columns, + columnGroups = list(reactable::colGroup("Propagates to", + targets$id)), + searchable = TRUE, filterable = TRUE, compact = TRUE, striped = TRUE, + highlight = TRUE, showPageSizeOptions = TRUE, defaultPageSize = 50, + pageSizeOptions = c(25, 50, 100, 500), + class = "compare-table") +} diff --git a/R/utils.R b/R/utils.R index 6ece544..b13289a 100644 --- a/R/utils.R +++ b/R/utils.R @@ -607,62 +607,120 @@ get_candidates <- function(bu, os = NA_character_, arch = NA_character_, type = c("binary", "source"), vignettes = TRUE, commit = FALSE, unsupported_platforms = TRUE, check = c("NOTE", "WARNING", "OK"), jobs = NULL) { + propagation_checks(bu, os = os, arch = arch, type = type, + vignettes = vignettes, commit = commit, + unsupported_platforms = unsupported_platforms, + check = check, jobs = jobs) |> + dplyr::filter(propagates) |> + dplyr::select(-propagates, -reason) +} + +#' Check which R Universe packages can propagate to Bioconductor +#' +#' @description +#' Applies the rules of `get_candidates()` to every package and reports which +#' rules each one fails. For binaries, a package propagates when its binary +#' for `os` and `arch` passed `R CMD check` and built (`check_ok`), the source +#' build passed (`source_ok`, if `vignettes`), R Universe built the same +#' commit as BBS (`commit_ok`, if `commit`), and the platform is supported +#' (`platform_ok`, if `unsupported_platforms`). For source packages, the +#' source build passed (`source_ok`), `R CMD check` on Linux x86_64 passed +#' (`check_ok`), and, if `commit`, the commit is the same as BBS +#' (`commit_ok`). +#' +#' @inheritParams get_candidates +#' @param bbs data.frame of BBS commits with columns Package, Version, +#' BBS_commit, and Deprecated, used when `commit` is `TRUE`. If `NULL` +#' (default), read with `bbs_status()` for every package type. +#' +#' @returns data.frame of jobs with logical columns for each rule that applies, +#' `propagates`, and `reason`, the rules a package fails +#' +#' @examples +#' \dontrun{ +#' bu <- uni_for_bioc("devel") +#' propagation_checks(bu, type = "source", commit = TRUE) +#' } +#' +#' @export +propagation_checks <- function(bu, os = NA_character_, arch = NA_character_, + type = c("binary", "source"), vignettes = TRUE, + commit = FALSE, unsupported_platforms = TRUE, + check = c("NOTE", "WARNING", "OK"), jobs = NULL, + bbs = NULL) { type <- match.arg(type) if (is.null(jobs)) { raw_universe_df <- get_raw_uni_df(bu$universe) universe_df <- get_uni_df(raw_universe_df) jobs <- get_jobs(universe_df, bu$r_version, bu$universe) } - if (type == "source") - return(.source_candidates(bu, jobs, commit, check)) - - os <- get_binary_os(os) - candidates <- jobs |> - dplyr::filter(job_type == "binary", job_os == os) - - if (!is.na(arch)) - candidates <- candidates |> - filter_by_arch(os, arch) - - if (vignettes) { - vignette_jobs <- jobs |> - dplyr::filter(job_type == "source") |> - dplyr::select(Package, Version, jobs_check) |> - dplyr::rename(vignettes_check = jobs_check) - - candidates <- merge(candidates, vignette_jobs, - by = c("Package", "Version")) + if (commit && is.null(bbs)) + bbs <- .bbs_commits(bu) + key <- function(df) paste(df$Package, df$Version) + one_per_package <- function(df) + dplyr::distinct(df, Package, Version, .keep_all = TRUE) - candidates <- candidates |> - dplyr::filter(vignettes_check %in% check) + source_jobs <- jobs |> + dplyr::filter(job_type == "source") |> + one_per_package() + + if (type == "source") { + rows <- source_jobs + linux <- jobs |> + dplyr::filter(job_type == "binary", job_os == "linux") |> + filter_by_arch("linux", "x86_64") |> + one_per_package() + rules <- list( + source_ok = rows$jobs_check %in% check & !is.na(rows$artifact), + check_ok = linux$jobs_check[match(key(rows), key(linux))] %in% check) + } else { + os <- get_binary_os(os) + rows <- jobs |> + dplyr::filter(job_type == "binary", job_os == os) + if (!is.na(arch)) + rows <- filter_by_arch(rows, os, arch) + rules <- list() + if (vignettes) + rules$source_ok <- source_jobs$jobs_check[ + match(key(rows), key(source_jobs))] %in% check + if (commit) + rules$check_ok <- rows$binaries_check %in% check & + rows$jobs_check %in% check & + rows$binaries_status %in% "success" + if (unsupported_platforms) + rules$platform_ok <- !purrr::pmap_lgl( + list(rows$`Config/Bioconductor/UnsupportedPlatforms`, + rows$job_os, rows$job_arch), + is_unsupported_platform) } - if (commit) { - candidates <- merge(candidates, .bbs_commits(bu), - by = c("Package", "Version")) - - candidates <- candidates |> - dplyr::filter(binaries_check %in% check, - jobs_check %in% check, - binaries_status == "success") |> - dplyr::filter(BBS_commit == substr(RemoteSha, 1, 7)) - } - - if (unsupported_platforms) { - candidates <- candidates |> - dplyr::filter( - !purrr::pmap_lgl( - list(`Config/Bioconductor/UnsupportedPlatforms`, - job_os, - job_arch), - is_unsupported_platform)) + i <- match(key(rows), key(bbs)) + rows$BBS_commit <- bbs$BBS_commit[i] + rows$Deprecated <- bbs$Deprecated[i] + same <- substr(rows$RemoteSha, 1, 7) == rows$BBS_commit + # Packages not in BBS have no commit to compare, so they fail + rules$commit_ok <- !is.na(same) & same } - candidates |> + labels <- c(source_ok = "source build failed", + check_ok = if (type == "source") "linux check failed" else + "check failed", + platform_ok = "unsupported platform", + commit_ok = "commit differs from BBS") + for (rule in names(rules)) + rows[[rule]] <- rules[[rule]] + passed <- if (length(rules)) Reduce(`&`, rules) else + rep(TRUE, nrow(rows)) + rows$propagates <- passed + rows$reason <- vapply(seq_len(nrow(rows)), function(i) { + failed <- names(rules)[!vapply(rules, `[`, logical(1), i)] + paste(labels[failed], collapse = "; ") + }, character(1)) + rows |> dplyr::arrange(Package) } -#' Commits BBS built for software packages +#' Commits BBS built, for every package type #' #' @param bu result of uni_for_bioc #' @@ -670,44 +728,12 @@ get_candidates <- function(bu, os = NA_character_, arch = NA_character_, #' #' @noRd .bbs_commits <- function(bu) { - BiocPkgTools::biocBuildReport(version = bu$bioc_version, - pkgType = "software") |> - dplyr::rename(Package = pkg, Version = version, - BBS_commit = git_last_commit) |> - dplyr::select(Package, Version, BBS_commit, Deprecated) |> - dplyr::distinct() -} - -#' Source packages that pass, for `get_candidates()` -#' -#' @param bu result of uni_for_bioc -#' @param jobs data.frame from `get_jobs()` -#' @param commit logical whether to require the same commit as BBS -#' @param check character results that pass -#' -#' @returns data.frame -#' -#' @noRd -.source_candidates <- function(bu, jobs, commit, check) { - linux_checks <- jobs |> - dplyr::filter(job_type == "binary", job_os == "linux") |> - filter_by_arch("linux", "x86_64") |> - dplyr::distinct(Package, Version, .keep_all = TRUE) |> - dplyr::select(Package, Version, linux_check = jobs_check) - - candidates <- jobs |> - dplyr::filter(job_type == "source", jobs_check %in% check, - !is.na(artifact)) |> - dplyr::inner_join(linux_checks, by = c("Package", "Version")) |> - dplyr::filter(linux_check %in% check) - - if (commit) - candidates <- candidates |> - dplyr::inner_join(.bbs_commits(bu), by = c("Package", "Version")) |> - dplyr::filter(BBS_commit == substr(RemoteSha, 1, 7)) - - candidates |> - dplyr::arrange(Package) + do.call(rbind, lapply(bioc_pkg_types()$type, function(type) { + bbs <- bbs_status(bu$bioc_branch, type) + data.frame(Package = bbs$Package, Version = bbs$Version, + BBS_commit = substr(bbs$commit, 1, 7), + Deprecated = bbs$deprecated) + })) } #' Get information about R Universe building a Bioconductor version diff --git a/man/propagation_checks.Rd b/man/propagation_checks.Rd new file mode 100644 index 0000000..c751b68 --- /dev/null +++ b/man/propagation_checks.Rd @@ -0,0 +1,66 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/utils.R +\name{propagation_checks} +\alias{propagation_checks} +\title{Check which R Universe packages can propagate to Bioconductor} +\usage{ +propagation_checks( + bu, + os = NA_character_, + arch = NA_character_, + type = c("binary", "source"), + vignettes = TRUE, + commit = FALSE, + unsupported_platforms = TRUE, + check = c("NOTE", "WARNING", "OK"), + jobs = NULL, + bbs = NULL +) +} +\arguments{ +\item{bu}{result of uni_for_bioc} + +\item{os}{character, for binaries} + +\item{arch}{character (default NA_character_) x86_64 or arm64, for binaries} + +\item{type}{character "binary" (default) or "source"} + +\item{vignettes}{logical (default TRUE) Check status of source / vignettes, +for binaries} + +\item{commit}{logical (default FALSE) Check commit hash in RU and BBS?} + +\item{unsupported_platforms}{logical (default TRUE) Check for unsupported +platforms, for binaries} + +\item{check}{character results that pass} + +\item{jobs}{data.frame from \code{get_jobs()}. If \code{NULL} (default), downloaded.} + +\item{bbs}{data.frame of BBS commits with columns Package, Version, +BBS_commit, and Deprecated, used when \code{commit} is \code{TRUE}. If \code{NULL} +(default), read with \code{bbs_status()} for every package type.} +} +\value{ +data.frame of jobs with logical columns for each rule that applies, +\code{propagates}, and \code{reason}, the rules a package fails +} +\description{ +Applies the rules of \code{get_candidates()} to every package and reports which +rules each one fails. For binaries, a package propagates when its binary +for \code{os} and \code{arch} passed \verb{R CMD check} and built (\code{check_ok}), the source +build passed (\code{source_ok}, if \code{vignettes}), R Universe built the same +commit as BBS (\code{commit_ok}, if \code{commit}), and the platform is supported +(\code{platform_ok}, if \code{unsupported_platforms}). For source packages, the +source build passed (\code{source_ok}), \verb{R CMD check} on Linux x86_64 passed +(\code{check_ok}), and, if \code{commit}, the commit is the same as BBS +(\code{commit_ok}). +} +\examples{ +\dontrun{ +bu <- uni_for_bioc("devel") +propagation_checks(bu, type = "source", commit = TRUE) +} + +} diff --git a/man/propagation_status.Rd b/man/propagation_status.Rd new file mode 100644 index 0000000..e90ba8b --- /dev/null +++ b/man/propagation_status.Rd @@ -0,0 +1,45 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/propagation.R +\name{propagation_status} +\alias{propagation_status} +\title{Which Bioconductor packages propagate from R Universe} +\usage{ +propagation_status( + branch, + types = bioc_pkg_types()$type, + jobs = NULL, + bbs = NULL, + targets = propagation_targets() +) +} +\arguments{ +\item{branch}{character "devel" or "release"} + +\item{types}{character package types, from \code{bioc_pkg_types()$type}} + +\item{jobs}{data.frame from \code{uni_jobs()}. If \code{NULL} (default), downloaded.} + +\item{bbs}{data.frame of BBS commits with columns Package, Version, +BBS_commit, and Deprecated. If \code{NULL} (default), read with +\code{bbs_status()}.} + +\item{targets}{data.frame from \code{propagation_targets()}} +} +\value{ +data.frame with one row per package and columns Package, type, +Version, deprecated, and, for each target id, a logical column whether +it propagates and \verb{_reason}, why not. A package with no job for a +target does not propagate, with reason "not built". Targets that do not +apply to a package's type are \code{NA}. +} +\description{ +Applies \code{propagation_checks()} with the rules \code{harvest.R} uses, requiring +the same commit as BBS, for each of \code{targets} and every package of \code{types} +in the branch's manifest. +} +\examples{ +\dontrun{ +propagation_status("devel", types = "books") +} + +} diff --git a/man/propagation_summary_table.Rd b/man/propagation_summary_table.Rd new file mode 100644 index 0000000..2929a95 --- /dev/null +++ b/man/propagation_summary_table.Rd @@ -0,0 +1,23 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/propagation.R +\name{propagation_summary_table} +\alias{propagation_summary_table} +\title{Number of packages that propagate to each target} +\usage{ +propagation_summary_table(status) +} +\arguments{ +\item{status}{data.frame from \code{propagation_status()}} +} +\value{ +htmltools table with a row per target +} +\description{ +Number of packages that propagate to each target +} +\examples{ +\dontrun{ +propagation_summary_table(propagation_status("devel", types = "books")) +} + +} diff --git a/man/propagation_table.Rd b/man/propagation_table.Rd new file mode 100644 index 0000000..437633c --- /dev/null +++ b/man/propagation_table.Rd @@ -0,0 +1,23 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/propagation.R +\name{propagation_table} +\alias{propagation_table} +\title{Interactive table of which packages propagate} +\usage{ +propagation_table(status) +} +\arguments{ +\item{status}{data.frame from \code{propagation_status()}} +} +\value{ +reactable htmlwidget +} +\description{ +Interactive table of which packages propagate +} +\examples{ +\dontrun{ +propagation_table(propagation_status("devel", types = "books")) +} + +} diff --git a/man/propagation_targets.Rd b/man/propagation_targets.Rd new file mode 100644 index 0000000..211706a --- /dev/null +++ b/man/propagation_targets.Rd @@ -0,0 +1,21 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/propagation.R +\name{propagation_targets} +\alias{propagation_targets} +\title{Targets packages propagate to} +\usage{ +propagation_targets() +} +\value{ +data.frame with columns id, label, type, os, arch, and types (a +list column) +} +\description{ +The source package and the binaries Bioconductor distributes. +Bioconductor only distributes binaries of software packages, so \code{types} +lists the package types each target applies to. +} +\examples{ +propagation_targets() + +} diff --git a/man/write_comparisons.Rd b/man/write_comparisons.Rd index b5904a8..ef6e313 100644 --- a/man/write_comparisons.Rd +++ b/man/write_comparisons.Rd @@ -18,8 +18,9 @@ data.frame summary, invisibly Saves one \verb{compare--.rds} per branch and type and a \code{summary.rds} in \code{data_dir}. If the configuration has \code{errors: true}, also saves \verb{errors-.rds} from \code{uni_errors()}, with R Universe logs cached -in \code{data_dir/logs}. Reports read these files, so the data is downloaded -once. +in \code{data_dir/logs}. If it has \code{propagation: true}, also saves +\verb{propagation-.rds} from \code{propagation_status()}. Reports read these +files, so the data is downloaded once. } \examples{ \dontrun{ diff --git a/tests/testthat/test-propagation.R b/tests/testthat/test-propagation.R new file mode 100644 index 0000000..b4e3332 --- /dev/null +++ b/tests/testthat/test-propagation.R @@ -0,0 +1,51 @@ +# "a" passes everywhere; "b" fails the linux check; "d" is a data package +uni_df <- data.frame( + Package = c(rep(c("a", "b"), each = 3), "d"), + Version = c(rep(c("1.0.0", "2.0.0"), each = 3), "3.0.0"), + RemoteSha = c(rep(c("aaaaaaa1", "bbbbbbb1"), each = 3), "ddddddd1"), + job_type = c(rep(c("source", "binary", "binary"), 2), "source"), + job_os = c(rep(c("linux", "linux", "win"), 2), "linux"), + job_arch = c(rep(c(NA, "x86_64", "x86_64"), 2), NA), + job_r_xy = "4.6", + jobs_check = c("OK", "NOTE", "OK", "OK", "ERROR", "OK", "OK"), + binary_os = c(rep(c(NA, "linux", "win"), 2), NA), + binary_r_xy = c(rep(c(NA, "4.6", "4.6"), 2), NA), + binaries_version = c(NA, "1.0.0", "1.0.0", NA, "2.0.0", "2.0.0", NA), + binaries_arch = NA_character_, + binaries_distro = c(rep(c(NA, "resolute", NA), 2), NA), + binaries_check = c(rep(c(NA, "OK", "OK"), 2), NA), + binaries_status = c(rep(c(NA, "success", "success"), 2), NA), + job_id = 1:7, + `_buildurl` = "https://github.com/r-universe/bioc/actions/runs/1", + `Config/Bioconductor/UnsupportedPlatforms` = NA_character_, + check.names = FALSE) +jobs <- get_jobs(uni_df, "4.6.1", "bioc") +bbs <- data.frame(Package = c("a", "b", "d"), + Version = c("1.0.0", "2.0.0", "3.0.0"), + BBS_commit = c("aaaaaaa", "bbbbbbb", "zzzzzzz"), + Deprecated = c(FALSE, TRUE, FALSE)) + +test_that("propagation_checks reports the rules a package fails", { + x <- propagation_checks(list(), type = "source", commit = TRUE, + jobs = jobs, bbs = bbs) + expect_equal(x$Package, c("a", "b", "d")) + expect_equal(x$propagates, c(TRUE, FALSE, FALSE)) + expect_equal(x$reason, c("", "linux check failed", + "linux check failed; commit differs from BBS")) +}) + +test_that("propagation_status marks binaries of data packages not applicable", { + local_mocked_bindings( + uni_for_bioc = function(branch) list(bioc_branch = branch), + bioc_manifest = function(branch, type) + switch(type, software = c("a", "b"), "data-experiment" = "d")) + targets <- propagation_targets()[1:2, ] + st <- propagation_status("devel", types = c("software", "data-experiment"), + jobs = jobs, bbs = bbs, targets = targets) + expect_equal(st$Package, c("a", "b", "d")) + expect_equal(st$source, c(TRUE, FALSE, FALSE)) + expect_equal(st$windows_x86_64, c(TRUE, TRUE, NA)) + expect_equal(st$deprecated, c(FALSE, TRUE, FALSE)) + skip_if_not_installed("reactable") + expect_s3_class(propagation_table(st), "reactable") +}) From ca8e256d8c10b549dfcbeaad6a14427c623f30c3 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 10 Nov 2026 00:59:40 +0000 Subject: [PATCH 2/9] Show commits and linked Yes/No badges on propagation tables Remove the old propagation vignette, which the propagation pages replace, and BiocPkgTools and kableExtra, which nothing uses anymore. Drop Windows arm64, which Bioconductor does not distribute. Yes links to the file that would be harvested and No to the R Universe job that failed, from propagation_checks()'s new failure_url. Co-Authored-By: Claude Opus 5.5 --- DESCRIPTION | 2 - R/propagation.R | 64 ++++--- R/utils.R | 22 ++- man/propagation_checks.Rd | 3 +- man/propagation_status.Rd | 7 +- tests/testthat/test-propagation.R | 9 + vignettes/index.qmd | 286 ------------------------------ 7 files changed, 77 insertions(+), 316 deletions(-) delete mode 100644 vignettes/index.qmd diff --git a/DESCRIPTION b/DESCRIPTION index c25de42..1dc39a4 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -9,7 +9,6 @@ Description: A package of tools for working Bioconductor universes. License: Artistic-2.0 Depends: R (>= 4.6.0) Imports: - BiocPkgTools, dplyr, htmltools, httr2, @@ -21,7 +20,6 @@ Imports: Suggests: curl, knitr, - kableExtra, logger, quarto, reactable, diff --git a/R/propagation.R b/R/propagation.R index d41c0ea..0617438 100644 --- a/R/propagation.R +++ b/R/propagation.R @@ -13,14 +13,12 @@ #' @export propagation_targets <- function() { data.frame( - id = c("source", "windows_x86_64", "windows_arm64", "macosx_arm64", - "macosx_x86_64"), - label = c("Source", "Windows x86_64", "Windows arm64", "macOS arm64", - "macOS x86_64"), - type = c("source", "binary", "binary", "binary", "binary"), - os = c(NA, "windows", "windows", "macosx", "macosx"), - arch = c(NA, "x86_64", "arm64", "arm64", "x86_64"), - types = I(c(list(bioc_pkg_types()$type), rep(list("software"), 4)))) + id = c("source", "windows_x86_64", "macosx_arm64", "macosx_x86_64"), + label = c("Source", "Windows x86_64", "macOS arm64", "macOS x86_64"), + type = c("source", "binary", "binary", "binary"), + os = c(NA, "windows", "macosx", "macosx"), + arch = c(NA, "x86_64", "arm64", "x86_64"), + types = I(c(list(bioc_pkg_types()$type), rep(list("software"), 3)))) } #' Which Bioconductor packages propagate from R Universe @@ -39,8 +37,11 @@ propagation_targets <- function() { #' @param targets data.frame from `propagation_targets()` #' #' @returns data.frame with one row per package and columns Package, type, -#' Version, deprecated, and, for each target id, a logical column whether -#' it propagates and `_reason`, why not. A package with no job for a +#' Version, deprecated, uni_commit and bbs_commit (the commits R Universe +#' and BBS built), and, for each target id, a logical column whether it +#' propagates, `_reason`, why not, `_artifact`, the file to +#' download if it propagates, and `_failure_url`, the R Universe job +#' that failed if it does not. A package with no job for a #' target does not propagate, with reason "not built". Targets that do not #' apply to a package's type are `NA`. #' @@ -64,6 +65,9 @@ propagation_status <- function(branch, types = bioc_pkg_types()$type, pkgs$Version <- jobs$Version[match(pkgs$Package, jobs$Package)] pkgs$deprecated <- bbs$Deprecated[match(pkgs$Package, bbs$Package)] %in% TRUE + pkgs$uni_commit <- substr(jobs$RemoteSha[match(pkgs$Package, jobs$Package)], + 1, 7) + pkgs$bbs_commit <- bbs$BBS_commit[match(pkgs$Package, bbs$Package)] for (i in seq_len(nrow(targets))) { checks <- propagation_checks(bu, os = targets$os[i], @@ -78,6 +82,12 @@ propagation_status <- function(branch, types = bioc_pkg_types()$type, pkgs[[paste0(id, "_reason")]] <- ifelse( !applies, NA_character_, ifelse(is.na(j), "not built", checks$reason[j])) + pkgs[[paste0(id, "_artifact")]] <- ifelse(pkgs[[id]] %in% TRUE, + checks$artifact[j], + NA_character_) + pkgs[[paste0(id, "_failure_url")]] <- ifelse(pkgs[[id]] %in% FALSE, + checks$failure_url[j], + NA_character_) } pkgs <- pkgs[order(pkgs$Package), , drop = FALSE] rownames(pkgs) <- NULL @@ -141,38 +151,52 @@ propagation_table <- function(status) { status$Package), type = types$label[match(status$type, types$type)], Version = status$Version, + uni_commit = status$uni_commit, + bbs_commit = status$bbs_commit, deprecated = ifelse(status$deprecated, paste0('', 'Deprecated'), "")) - # "Yes", or "No" and the reasons - answer <- function(yes, reason) + pill <- function(level, text) + sprintf(paste0('%2$s'), level, text) + link <- function(url, html) + ifelse(is.na(url), html, + sprintf('%s', url, html)) + # "Yes" linked to the file, or "No" linked to the failing job, with the + # reasons + answer <- function(yes, reason, artifact, failure_url) dplyr::case_when( is.na(yes) ~ '—', - yes ~ 'Yes', - .default = sprintf( - paste0('No', - '
%s'), - htmltools::htmlEscape(reason))) + yes ~ link(artifact, pill("success", "Yes")), + .default = paste0(link(failure_url, pill("danger", "No")), + '
', + htmltools::htmlEscape(reason), '')) columns <- list( Package = reactable::colDef(name = "Package", html = TRUE, minWidth = 160), type = reactable::colDef(name = "Type", minWidth = 120), Version = reactable::colDef(name = "Version", minWidth = 90), + uni_commit = reactable::colDef(name = "R Universe", minWidth = 100), + bbs_commit = reactable::colDef(name = "BBS", minWidth = 90), deprecated = reactable::colDef(name = "Deprecated", html = TRUE, minWidth = 110)) for (i in seq_len(nrow(targets))) { id <- targets$id[i] - df[[id]] <- answer(status[[id]], status[[paste0(id, "_reason")]]) + df[[id]] <- answer(status[[id]], status[[paste0(id, "_reason")]], + status[[paste0(id, "_artifact")]], + status[[paste0(id, "_failure_url")]]) columns[[id]] <- reactable::colDef(name = targets$label[i], html = TRUE, minWidth = 130) } reactable::reactable( df, columns = columns, - columnGroups = list(reactable::colGroup("Propagates to", - targets$id)), + columnGroups = list( + reactable::colGroup("Commit", c("uni_commit", "bbs_commit")), + reactable::colGroup("Propagates to", targets$id)), searchable = TRUE, filterable = TRUE, compact = TRUE, striped = TRUE, highlight = TRUE, showPageSizeOptions = TRUE, defaultPageSize = 50, pageSizeOptions = c(25, 50, 100, 500), diff --git a/R/utils.R b/R/utils.R index b13289a..955acc4 100644 --- a/R/utils.R +++ b/R/utils.R @@ -612,7 +612,7 @@ get_candidates <- function(bu, os = NA_character_, arch = NA_character_, unsupported_platforms = unsupported_platforms, check = check, jobs = jobs) |> dplyr::filter(propagates) |> - dplyr::select(-propagates, -reason) + dplyr::select(-propagates, -reason, -failure_url) } #' Check which R Universe packages can propagate to Bioconductor @@ -634,7 +634,8 @@ get_candidates <- function(bu, os = NA_character_, arch = NA_character_, #' (default), read with `bbs_status()` for every package type. #' #' @returns data.frame of jobs with logical columns for each rule that applies, -#' `propagates`, and `reason`, the rules a package fails +#' `propagates`, `reason`, the rules a package fails, and `failure_url`, +#' the R Universe job of the first failing build or check #' #' @examples #' \dontrun{ @@ -670,9 +671,12 @@ propagation_checks <- function(bu, os = NA_character_, arch = NA_character_, dplyr::filter(job_type == "binary", job_os == "linux") |> filter_by_arch("linux", "x86_64") |> one_per_package() + linux_job <- match(key(rows), key(linux)) rules <- list( source_ok = rows$jobs_check %in% check & !is.na(rows$artifact), - check_ok = linux$jobs_check[match(key(rows), key(linux))] %in% check) + check_ok = linux$jobs_check[linux_job] %in% check) + job_urls <- list(source_ok = rows$job_url, + check_ok = linux$job_url[linux_job]) } else { os <- get_binary_os(os) rows <- jobs |> @@ -680,9 +684,11 @@ propagation_checks <- function(bu, os = NA_character_, arch = NA_character_, if (!is.na(arch)) rows <- filter_by_arch(rows, os, arch) rules <- list() + source_job <- match(key(rows), key(source_jobs)) + job_urls <- list(source_ok = source_jobs$job_url[source_job], + check_ok = rows$job_url) if (vignettes) - rules$source_ok <- source_jobs$jobs_check[ - match(key(rows), key(source_jobs))] %in% check + rules$source_ok <- source_jobs$jobs_check[source_job] %in% check if (commit) rules$check_ok <- rows$binaries_check %in% check & rows$jobs_check %in% check & @@ -716,6 +722,12 @@ propagation_checks <- function(bu, os = NA_character_, arch = NA_character_, failed <- names(rules)[!vapply(rules, `[`, logical(1), i)] paste(labels[failed], collapse = "; ") }, character(1)) + # Link to the first failing job; the commit and platform rules have none + rows$failure_url <- NA_character_ + for (rule in rev(intersect(c("source_ok", "check_ok"), names(rules)))) { + failed <- !rules[[rule]] + rows$failure_url[failed] <- job_urls[[rule]][failed] + } rows |> dplyr::arrange(Package) } diff --git a/man/propagation_checks.Rd b/man/propagation_checks.Rd index c751b68..988cbf0 100644 --- a/man/propagation_checks.Rd +++ b/man/propagation_checks.Rd @@ -44,7 +44,8 @@ BBS_commit, and Deprecated, used when \code{commit} is \code{TRUE}. If \code{NUL } \value{ data.frame of jobs with logical columns for each rule that applies, -\code{propagates}, and \code{reason}, the rules a package fails +\code{propagates}, \code{reason}, the rules a package fails, and \code{failure_url}, +the R Universe job of the first failing build or check } \description{ Applies the rules of \code{get_candidates()} to every package and reports which diff --git a/man/propagation_status.Rd b/man/propagation_status.Rd index e90ba8b..e5fd4a4 100644 --- a/man/propagation_status.Rd +++ b/man/propagation_status.Rd @@ -27,8 +27,11 @@ BBS_commit, and Deprecated. If \code{NULL} (default), read with } \value{ data.frame with one row per package and columns Package, type, -Version, deprecated, and, for each target id, a logical column whether -it propagates and \verb{_reason}, why not. A package with no job for a +Version, deprecated, uni_commit and bbs_commit (the commits R Universe +and BBS built), and, for each target id, a logical column whether it +propagates, \verb{_reason}, why not, \verb{_artifact}, the file to +download if it propagates, and \verb{_failure_url}, the R Universe job +that failed if it does not. A package with no job for a target does not propagate, with reason "not built". Targets that do not apply to a package's type are \code{NA}. } diff --git a/tests/testthat/test-propagation.R b/tests/testthat/test-propagation.R index b4e3332..85aab0f 100644 --- a/tests/testthat/test-propagation.R +++ b/tests/testthat/test-propagation.R @@ -32,6 +32,10 @@ test_that("propagation_checks reports the rules a package fails", { expect_equal(x$propagates, c(TRUE, FALSE, FALSE)) expect_equal(x$reason, c("", "linux check failed", "linux check failed; commit differs from BBS")) + # "b" links to its failing linux job; "d" has no linux job + expect_equal(x$failure_url, + c(NA, "https://github.com/r-universe/bioc/actions/runs/1/job/5", + NA)) }) test_that("propagation_status marks binaries of data packages not applicable", { @@ -46,6 +50,11 @@ test_that("propagation_status marks binaries of data packages not applicable", { expect_equal(st$source, c(TRUE, FALSE, FALSE)) expect_equal(st$windows_x86_64, c(TRUE, TRUE, NA)) expect_equal(st$deprecated, c(FALSE, TRUE, FALSE)) + expect_equal(st$uni_commit, c("aaaaaaa", "bbbbbbb", "ddddddd")) + expect_equal(st$bbs_commit, c("aaaaaaa", "bbbbbbb", "zzzzzzz")) + expect_equal(st$source_artifact, + c("https://bioc.r-universe.dev/src/contrib/a_1.0.0.tar.gz", + NA, NA)) skip_if_not_installed("reactable") expect_s3_class(propagation_table(st), "reactable") }) diff --git a/vignettes/index.qmd b/vignettes/index.qmd deleted file mode 100644 index 3b0cb08..0000000 --- a/vignettes/index.qmd +++ /dev/null @@ -1,286 +0,0 @@ ---- -title: "Bioconductor in R Universe: Linux with Criteria on Commits and Vignettes" -format: - html: - code-fold: true - toc: false -sidebar: false -editor: visual -engine: quarto -vignette: > - %\VignetteIndexEntry{Bioconductor in R Universe: Linux with Criteria on Commits and Vignettes} - %\VignetteEngine{quarto::html} - %\VignetteEncoding{UTF-8} ---- - -```{r install_universe, echo = FALSE, message = FALSE} -library(BiocPkgTools) -library(dplyr) -library(biocUniTools) -library(httr2) -library(reactable) -library(purrr) -``` - -```{r get_buildurl, echo = FALSE} -# When a package fails early in the build, we don't get a _buildurl but we can -# construct it -get_buildurl <- function(pkg, uni) { - u <-paste0("https://", uni, ".r-universe.dev/api/packages/", pkg) - buildurl <- tryCatch({ - pkglist <- httr2::request(u) |> - httr2::req_timeout(20) |> - httr2::req_perform() |> - httr2::resp_body_json() - pkglist$`_buildurl` - - }, error = function(e) { - if (is.null(e$resp)) return(NA_character_) - body <- httr2::resp_body_string(e$resp) - buildurl <- stringr::str_extract(body, - paste0("https://github.com/r-universe/", - uni, "/actions/runs/[0-9]+")) - buildurl - }) - buildurl -} -``` - -```{r propagation-criteria, echo = FALSE} -propagate <- function(commit_match, deprecated, job_check, binary_check, - binary_status, vignettes_check) { - !is.na(commit_match) & commit_match & - !is.na(deprecated) & !deprecated & - !is.na(job_check) & (!job_check %in% c("CANCELLED", "ERROR", "FAIL")) & - !is.na(binary_check) & (!binary_check %in% c("CANCELLED", "ERROR", "FAIL")) & - !is.na(binary_status) & (binary_status == "success") & - !is.na(vignettes_check) & (!vignettes_check %in% c("CANCELLED", "ERROR", "FAIL")) -} -``` - -The following tables attempt to describe the potential propagation status of a package in R universe. Propagation is based on - -- the same commit in both build systems -- the job building in R Universe -- the binary building in R Universe -- the vignettes building in R Universe -- package is not deprecated - -Possible failure states are `CANCELLED`, `ERROR`, or `FAIL`. - -## R Universe and Bioconductor devel - -https://bioc.r-universe.dev - -```{r devel-settings, echo = FALSE, message = FALSE} -bu <- uni_for_bioc("devel") -os <- "linux" -nde <- "nebbiolo2" -raw_universe_df <- biocUniTools::get_raw_uni_df(bu$universe) -universe_df <- biocUniTools::get_uni_df(raw_universe_df) -ru_builds <- biocUniTools::get_jobs(universe_df, bu$r_version, bu$universe) -bbs_pkgs <- BiocPkgTools::biocBuildReport(version = bu$bioc_version, - pkgType = "software") -``` - -::: column-screen-inset -```{r devel-data, echo = FALSE} -bbs_builds <- bbs_pkgs |> - dplyr::filter(node == nde, stage == "checksrc") |> - dplyr::select(pkg, version, Deprecated, git_last_commit, result) |> - dplyr::rename(Package = pkg, Version = version, - `BBS Commit` = git_last_commit, Check = result) |> - dplyr::mutate(`BBS Commit` = substr(`BBS Commit`, 1, 7)) |> - dplyr::distinct() - -filtered_ru_builds <- ru_builds |> - dplyr::filter(job_os == os) |> - biocUniTools::filter_by_arch(os, arch = "x86_64") -vignettes_ru_builds <- ru_builds |> - dplyr::filter(job_type == "source") |> - dplyr::select(Package, Version, jobs_check, `_buildurl`) |> - dplyr::rename(`Vignettes Check` = jobs_check, - vignettes_buildurl = `_buildurl`) - -builds <- merge(bbs_builds, filtered_ru_builds, by = c("Package", "Version"), - all.x = TRUE) -builds <- merge(builds, vignettes_ru_builds, by = c("Package", "Version"), - all.x = TRUE) - -# Get _buildurl if not available -needs_url <- builds |> - dplyr::filter(!Deprecated & is.na(`_buildurl`)) |> - dplyr::distinct(Package, .keep_all = TRUE) - -needs_url$`_buildurl` <- purrr::map_chr(needs_url$Package, get_buildurl, bu$universe) - -builds <- builds |> - dplyr::rows_update(needs_url |> dplyr::select(Package, `_buildurl`), - by = "Package", - unmatched = "ignore") - -condensed_builds <- builds |> - dplyr::rename(`Job Check` = jobs_check, `Binary Check` = binaries_check, - `Build Url` = `_buildurl`, `RU Commit` = RemoteSha, - `Job Url` = job_url, `Binary Status` = binaries_status) |> - dplyr::mutate(`RU Commit` = substr(`RU Commit`, 1, 7)) |> - dplyr::mutate(`Commit Match` = `BBS Commit` == `RU Commit`, - Propagate = propagate(`Commit Match`, Deprecated, - `Job Check`, `Binary Check`, - `Binary Status`, `Vignettes Check`)) |> - dplyr::select(Package, Version, `BBS Commit`, Deprecated, `Commit Match`, - Check, `Job Check`, `Binary Check`, `Binary Status`, - `Job Url`, `Build Url`, `Vignettes Check`, vignettes_buildurl, - Propagate) - -condensed_builds_display <- condensed_builds |> - dplyr::mutate( - Check = ifelse(!is.na(Check), - paste0('', Check, ''), - as.character(Check)), - `Job Check` = ifelse(!is.na(`Job Url`) & !is.na(`Job Check`), - paste0('', `Job Check`, ''), - as.character(`Job Check`)), - `Binary Check` = ifelse(!is.na(`Build Url`) & !is.na(`Binary Check`), - paste0('', `Binary Check`, - ''), - as.character(`Binary Check`)), - `Vignettes Check` = ifelse(!is.na(vignettes_buildurl) & !is.na(`Vignettes Check`), - paste0('', - `Vignettes Check`, ''), - as.character(`Vignettes Check`)), - Propagate = ifelse(Propagate, - '✅', - '\U0001F6AB') - ) |> - dplyr::select(-`Job Url`, -`Build Url`, -vignettes_buildurl) - -reactable::reactable(condensed_builds_display, searchable = TRUE, #wrap = FALSE, - showPageSizeOptions = TRUE, compact = TRUE, - columns = list( - Check = reactable::colDef(html = TRUE), - `Job Check` = reactable::colDef(html = TRUE), - `Binary Check` = reactable::colDef(html = TRUE), - `Vignettes Check` = reactable::colDef(html = TRUE), - Propagate = reactable::colDef(html = TRUE) - - ), - defaultColDef = reactable::colDef( - minWidth = 150, - style = list(whiteSpace = "nowrap"), - headerStyle = list(whiteSpace = "nowrap") - ) -) -``` -::: - -## R Universe and Bioconductor release - -https://bioc-release.r-universe.dev - -```{r release-settings, echo = FALSE, message = FALSE} -bu <- uni_for_bioc("release") -nde <- "nebbiolo1" -raw_universe_df <- biocUniTools::get_raw_uni_df(bu$universe) -universe_df <- biocUniTools::get_uni_df(raw_universe_df) -ru_builds <- biocUniTools::get_jobs(universe_df, bu$r_version, bu$universe) -bbs_pkgs <- BiocPkgTools::biocBuildReport(version = bu$bioc_version, - pkgType = "software") -``` - -::: column-screen-inset -```{r release-data, echo = FALSE} -bbs_builds <- bbs_pkgs |> - dplyr::filter(node == nde, stage == "checksrc") |> - dplyr::select(pkg, version, Deprecated, git_last_commit, result) |> - dplyr::rename(Package = pkg, Version = version, - `BBS Commit` = git_last_commit, Check = result) |> - dplyr::mutate(`BBS Commit` = substr(`BBS Commit`, 1, 7)) |> - dplyr::distinct() - -filtered_ru_builds <- ru_builds |> - dplyr::filter(job_os == os) |> - biocUniTools::filter_by_arch(os, arch = "x86_64") -vignettes_ru_builds <- ru_builds |> - dplyr::filter(job_type == "source") |> - dplyr::select(Package, Version, jobs_check, `_buildurl`) |> - dplyr::rename(`Vignettes Check` = jobs_check, - vignettes_buildurl = `_buildurl`) - -builds <- merge(bbs_builds, filtered_ru_builds, by = c("Package", "Version"), - all.x = TRUE) -builds <- merge(builds, vignettes_ru_builds, by = c("Package", "Version"), - all.x = TRUE) - -# Get _buildurl if not available -needs_url <- builds |> - dplyr::filter(!Deprecated & is.na(`_buildurl`)) |> - dplyr::distinct(Package, .keep_all = TRUE) - -needs_url$`_buildurl` <- purrr::map_chr(needs_url$Package, get_buildurl, bu$universe) - -builds <- builds |> - dplyr::rows_update(needs_url |> dplyr::select(Package, `_buildurl`), - by = "Package", - unmatched = "ignore") - -condensed_builds <- builds |> - dplyr::rename(`Job Check` = jobs_check, `Binary Check` = binaries_check, - `Build Url` = `_buildurl`, `RU Commit` = RemoteSha, - `Job Url` = job_url, `Binary Status` = binaries_status) |> - dplyr::mutate(`RU Commit` = substr(`RU Commit`, 1, 7)) |> - dplyr::mutate(`Commit Match` = `BBS Commit` == `RU Commit`, - Propagate = propagate(`Commit Match`, Deprecated, - `Job Check`, `Binary Check`, - `Binary Status`, `Vignettes Check`)) |> - dplyr::select(Package, Version, `BBS Commit`, Deprecated, `Commit Match`, - Check, `Job Check`, `Binary Check`, `Binary Status`, - `Job Url`, `Build Url`, `Vignettes Check`, vignettes_buildurl, - Propagate) - -condensed_builds_display <- condensed_builds |> - dplyr::mutate( - Check = ifelse(!is.na(Check), - paste0('', Check, ''), - as.character(Check)), - `Job Check` = ifelse(!is.na(`Job Url`) & !is.na(`Job Check`), - paste0('', `Job Check`, ''), - as.character(`Job Check`)), - `Binary Check` = ifelse(!is.na(`Build Url`) & !is.na(`Binary Check`), - paste0('', `Binary Check`, - ''), - as.character(`Binary Check`)), - `Vignettes Check` = ifelse(!is.na(vignettes_buildurl) & !is.na(`Vignettes Check`), - paste0('', - `Vignettes Check`, ''), - as.character(`Vignettes Check`)), - Propagate = ifelse(Propagate, - '✅', - '\U0001F6AB') - ) |> - dplyr::select(-`Job Url`, -`Build Url`, -vignettes_buildurl) - -reactable::reactable(condensed_builds_display, searchable = TRUE, #wrap = FALSE, - showPageSizeOptions = TRUE, compact = TRUE, - columns = list( - Check = reactable::colDef(html = TRUE), - `Job Check` = reactable::colDef(html = TRUE), - `Binary Check` = reactable::colDef(html = TRUE), - `Vignettes Check` = reactable::colDef(html = TRUE), - Propagate = reactable::colDef(html = TRUE) - - ), - defaultColDef = reactable::colDef( - minWidth = 150, - style = list(whiteSpace = "nowrap"), - headerStyle = list(whiteSpace = "nowrap") - ) -) -``` -::: - -```{r sessionInfo} -sessionInfo() -``` From 10bb5470113a444a3e117eea9af2ee545ef88e7e Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 10 Nov 2026 01:07:21 +0000 Subject: [PATCH 3/9] Link macOS arm64 binaries to the sonoma-arm64 folder Binaries without compiled code work on both Mac architectures and were linked to big-sur-x86_64 for arm64 too. For arm64, get_candidates() and propagation_checks() now use sonoma-arm64, where R Universe serves the same file. Also keep get_jobs() artifacts character when a universe has no source jobs. Co-Authored-By: Claude Opus 5.5 --- R/utils.R | 34 ++++++++++++++++++++++++++++---- tests/testthat/test-candidates.R | 22 +++++++++++++++++++++ 2 files changed, 52 insertions(+), 4 deletions(-) diff --git a/R/utils.R b/R/utils.R index 955acc4..9ac0f6f 100644 --- a/R/utils.R +++ b/R/utils.R @@ -430,7 +430,7 @@ get_jobs <- function(universe_df, r_version, universe) { job_type == "binary", job_r_xy == r_xy_ver(r_version)) |> dplyr::mutate( - artifact = ifelse( + artifact = as.character(ifelse( binaries_check %in% failed | jobs_check %in% failed | binaries_status == "failure", NA_character_, @@ -438,17 +438,17 @@ get_jobs <- function(universe_df, r_version, universe) { get_binary_os(binary_os), arch = get_arch(binaries_arch, job_arch), distro = binaries_distro), - uni_pkg_file(Package, binary_os, binaries_version)))) + uni_pkg_file(Package, binary_os, binaries_version))))) # Source packages are built by the source job source_rows <- universe_df |> dplyr::filter(job_type == "source") |> dplyr::mutate( - artifact = ifelse( + artifact = as.character(ifelse( jobs_check %in% failed | is.na(Version), NA_character_, in_repo(uni_repo_url(universe, r_version, type = "source"), - uni_pkg_file(Package, "source", Version)))) + uni_pkg_file(Package, "source", Version))))) other_rows <- universe_df |> dplyr::filter(!job_type %in% c("binary", "source")) |> @@ -683,6 +683,8 @@ propagation_checks <- function(bu, os = NA_character_, arch = NA_character_, dplyr::filter(job_type == "binary", job_os == os) if (!is.na(arch)) rows <- filter_by_arch(rows, os, arch) + if (os == "mac" && arch %in% "arm64") + rows$artifact <- .mac_arm64_artifact(rows) rules <- list() source_job <- match(key(rows), key(source_jobs)) job_urls <- list(source_ok = source_jobs$job_url[source_job], @@ -732,6 +734,30 @@ propagation_checks <- function(bu, os = NA_character_, arch = NA_character_, dplyr::arrange(Package) } +#' Link a Mac binary built for both architectures to the arm64 folder +#' +#' @description A binary without compiled code works on both Mac +#' architectures. `get_jobs()` links it to the x86_64 (big-sur) folder, and R +#' Universe serves the same file from the arm64 (sonoma) folder, which is +#' where arm64 binaries are expected. +#' +#' @param rows data.frame of mac binary jobs from `get_jobs()` +#' +#' @returns character artifacts +#' +#' @noRd +.mac_arm64_artifact <- function(rows) { + r_xy <- rows$binary_r_xy + x86 <- paste0("/bin/macosx/", get_subpath("mac", "x86_64", r_xy), "/") + arm <- paste0("/bin/macosx/", get_subpath("mac", "arm64", r_xy), "/") + universal <- is.na(rows$binaries_arch) & !is.na(rows$artifact) & + !is.na(r_xy) + out <- rows$artifact + out[universal] <- vapply(which(universal), function(i) + sub(x86[i], arm[i], out[i], fixed = TRUE), character(1)) + out +} + #' Commits BBS built, for every package type #' #' @param bu result of uni_for_bioc diff --git a/tests/testthat/test-candidates.R b/tests/testthat/test-candidates.R index 717cfec..08fa7b9 100644 --- a/tests/testthat/test-candidates.R +++ b/tests/testthat/test-candidates.R @@ -67,3 +67,25 @@ test_that("get_candidates source can require the BBS commit", { x <- get_candidates(list(), type = "source", commit = TRUE, jobs = jobs) expect_equal(nrow(x), 0) }) + +test_that("mac binaries for both architectures use each target's folder", { + mac <- data.frame( + Package = "m", Version = "1.0.0", RemoteSha = "mmmmmmm1", + job_type = "binary", job_os = "mac", job_arch = "arm64", + job_r_xy = "4.6", jobs_check = "OK", binary_os = "mac", + binary_r_xy = "4.6", binaries_version = "1.0.0", + binaries_arch = NA_character_, binaries_distro = NA_character_, + binaries_check = "OK", binaries_status = "success", job_id = 9L, + `_buildurl` = "https://github.com/r-universe/bioc/actions/runs/1", + `Config/Bioconductor/UnsupportedPlatforms` = NA_character_, + check.names = FALSE) + mac_jobs <- get_jobs(mac, "4.6.1", "bioc") + arm <- get_candidates(list(), "macosx", "arm64", vignettes = FALSE, + jobs = mac_jobs) + x86 <- get_candidates(list(), "macosx", "x86_64", vignettes = FALSE, + jobs = mac_jobs) + expect_equal(arm$artifact, paste0("https://bioc.r-universe.dev/bin/macosx/", + "sonoma-arm64/contrib/4.6/m_1.0.0.tgz")) + expect_equal(x86$artifact, paste0("https://bioc.r-universe.dev/bin/macosx/", + "big-sur-x86_64/contrib/4.6/m_1.0.0.tgz")) +}) From b05b7fa8a7d936faf2e0eeadab49c6d114a831c6 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 10 Nov 2026 03:15:28 +0000 Subject: [PATCH 4/9] Keep get_candidates() columns the same as before propagation_checks() also returns vignettes_check and linux_check, the results its rules use, and get_candidates() drops the rule columns, so its output has the same columns as before. Tests that read vignettes_check were otherwise passing without checking anything. Co-Authored-By: Claude Opus 5.5 --- R/utils.R | 20 ++++++++++++++------ man/propagation_checks.Rd | 9 ++++++--- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/R/utils.R b/R/utils.R index 9ac0f6f..5d6c636 100644 --- a/R/utils.R +++ b/R/utils.R @@ -612,7 +612,9 @@ get_candidates <- function(bu, os = NA_character_, arch = NA_character_, unsupported_platforms = unsupported_platforms, check = check, jobs = jobs) |> dplyr::filter(propagates) |> - dplyr::select(-propagates, -reason, -failure_url) + dplyr::select(-dplyr::any_of(c("propagates", "reason", "failure_url", + "source_ok", "check_ok", "platform_ok", + "commit_ok"))) } #' Check which R Universe packages can propagate to Bioconductor @@ -633,9 +635,12 @@ get_candidates <- function(bu, os = NA_character_, arch = NA_character_, #' BBS_commit, and Deprecated, used when `commit` is `TRUE`. If `NULL` #' (default), read with `bbs_status()` for every package type. #' -#' @returns data.frame of jobs with logical columns for each rule that applies, -#' `propagates`, `reason`, the rules a package fails, and `failure_url`, -#' the R Universe job of the first failing build or check +#' @returns data.frame of jobs with the results the rules use +#' (`vignettes_check`, the source build, for binaries; `linux_check` for +#' source packages; `BBS_commit` and `Deprecated` if `commit`), logical +#' columns for each rule that applies, `propagates`, `reason`, the rules a +#' package fails, and `failure_url`, the R Universe job of the first +#' failing build or check #' #' @examples #' \dontrun{ @@ -672,6 +677,7 @@ propagation_checks <- function(bu, os = NA_character_, arch = NA_character_, filter_by_arch("linux", "x86_64") |> one_per_package() linux_job <- match(key(rows), key(linux)) + rows$linux_check <- linux$jobs_check[linux_job] rules <- list( source_ok = rows$jobs_check %in% check & !is.na(rows$artifact), check_ok = linux$jobs_check[linux_job] %in% check) @@ -689,8 +695,10 @@ propagation_checks <- function(bu, os = NA_character_, arch = NA_character_, source_job <- match(key(rows), key(source_jobs)) job_urls <- list(source_ok = source_jobs$job_url[source_job], check_ok = rows$job_url) - if (vignettes) - rules$source_ok <- source_jobs$jobs_check[source_job] %in% check + if (vignettes) { + rows$vignettes_check <- source_jobs$jobs_check[source_job] + rules$source_ok <- rows$vignettes_check %in% check + } if (commit) rules$check_ok <- rows$binaries_check %in% check & rows$jobs_check %in% check & diff --git a/man/propagation_checks.Rd b/man/propagation_checks.Rd index 988cbf0..b09829f 100644 --- a/man/propagation_checks.Rd +++ b/man/propagation_checks.Rd @@ -43,9 +43,12 @@ BBS_commit, and Deprecated, used when \code{commit} is \code{TRUE}. If \code{NUL (default), read with \code{bbs_status()} for every package type.} } \value{ -data.frame of jobs with logical columns for each rule that applies, -\code{propagates}, \code{reason}, the rules a package fails, and \code{failure_url}, -the R Universe job of the first failing build or check +data.frame of jobs with the results the rules use +(\code{vignettes_check}, the source build, for binaries; \code{linux_check} for +source packages; \code{BBS_commit} and \code{Deprecated} if \code{commit}), logical +columns for each rule that applies, \code{propagates}, \code{reason}, the rules a +package fails, and \code{failure_url}, the R Universe job of the first +failing build or check } \description{ Applies the rules of \code{get_candidates()} to every package and reports which From be94b13cafc7e46e6d10ef43f380c33b1fb889fe Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 10 Nov 2026 03:31:43 +0000 Subject: [PATCH 5/9] Return only the passing jobs from get_candidates() Nothing but tests read vignettes_check or linux_check from get_candidates(), so it no longer returns them; propagation_checks() still does. The vignette tests check the source build through propagation_checks(), and a test for a column get_candidates() never had is removed. Co-Authored-By: Claude Opus 5.5 --- R/utils.R | 3 ++- tests/testthat/test-candidates.R | 10 ++++++++++ tests/testthat/test-utils.R | 23 +++++++++++++---------- 3 files changed, 25 insertions(+), 11 deletions(-) diff --git a/R/utils.R b/R/utils.R index 5d6c636..87f3709 100644 --- a/R/utils.R +++ b/R/utils.R @@ -614,7 +614,8 @@ get_candidates <- function(bu, os = NA_character_, arch = NA_character_, dplyr::filter(propagates) |> dplyr::select(-dplyr::any_of(c("propagates", "reason", "failure_url", "source_ok", "check_ok", "platform_ok", - "commit_ok"))) + "commit_ok", "vignettes_check", + "linux_check"))) } #' Check which R Universe packages can propagate to Bioconductor diff --git a/tests/testthat/test-candidates.R b/tests/testthat/test-candidates.R index 08fa7b9..6d92b3d 100644 --- a/tests/testthat/test-candidates.R +++ b/tests/testthat/test-candidates.R @@ -89,3 +89,13 @@ test_that("mac binaries for both architectures use each target's folder", { expect_equal(x86$artifact, paste0("https://bioc.r-universe.dev/bin/macosx/", "big-sur-x86_64/contrib/4.6/m_1.0.0.tgz")) }) + +test_that("get_candidates returns jobs without the rule results", { + x <- get_candidates(list(), type = "source", jobs = jobs) + rule_columns <- c("propagates", "reason", "failure_url", "source_ok", + "check_ok", "platform_ok", "commit_ok", + "vignettes_check", "linux_check") + expect_false(any(rule_columns %in% names(x))) + y <- propagation_checks(list(), type = "source", jobs = jobs) + expect_true(all(c("linux_check", "propagates", "reason") %in% names(y))) +}) diff --git a/tests/testthat/test-utils.R b/tests/testthat/test-utils.R index 678b32f..02de016 100644 --- a/tests/testthat/test-utils.R +++ b/tests/testthat/test-utils.R @@ -438,20 +438,23 @@ test_that("get_candidates vignette filter works", { # vignettes = TRUE (default) should filter on source check with_vignettes <- get_candidates(release, "windows", vignettes = TRUE) without_vignettes <- get_candidates(release, "windows", vignettes = FALSE) - + # vignette filter should only reduce or maintain row count expect_lte(nrow(with_vignettes), nrow(without_vignettes)) - - # all rows should have passing vignette check - expect_true(all(with_vignettes$vignettes_check %in% c("NOTE", "WARNING", "OK"))) + + # all candidates should have a passing source build + checks <- propagation_checks(release, "windows", vignettes = TRUE) + passed <- checks$vignettes_check[match(with_vignettes$Package, + checks$Package)] + expect_true(all(passed %in% c("NOTE", "WARNING", "OK"))) }) test_that("get_candidates vignette check respects check argument", { - ok_only <- get_candidates(release, "windows", vignettes = TRUE, check = "OK") - expect_true(all(ok_only$vignettes_check == "OK")) + ok_only <- get_candidates(release, "windows", vignettes = TRUE, + check = "OK") + checks <- propagation_checks(release, "windows", vignettes = TRUE, + check = "OK") + passed <- checks$vignettes_check[match(ok_only$Package, checks$Package)] + expect_true(all(passed == "OK")) }) -test_that("get_candidates without vignettes has no Vignettes Check column", { - without_vignettes <- get_candidates(release, "windows", vignettes = FALSE) - expect_false("Vignettes Check" %in% names(without_vignettes)) -}) From 86a07e513cc9c08be0a4a2cb6bb600e4d13da50f Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 10 Nov 2026 04:02:58 +0000 Subject: [PATCH 6/9] Add only propagates, reason, and failure_url in propagation_checks() The rule results, vignettes_check, linux_check, BBS_commit, and Deprecated were only read by tests, so propagation_checks() keeps them internal and get_candidates() returns only the jobs that pass. Reports still show deprecation and the BBS commit from bbs_status(). Tests now check candidates against R Universe's source build results and BBS commits. Co-Authored-By: Claude Opus 5.5 --- R/utils.R | 38 ++++++++++--------------------- man/propagation_checks.Rd | 18 ++++++--------- tests/testthat/test-candidates.R | 20 +++++++++------- tests/testthat/test-utils.R | 39 ++++++++++++++++++++------------ 4 files changed, 55 insertions(+), 60 deletions(-) diff --git a/R/utils.R b/R/utils.R index 87f3709..7e64dc3 100644 --- a/R/utils.R +++ b/R/utils.R @@ -612,10 +612,7 @@ get_candidates <- function(bu, os = NA_character_, arch = NA_character_, unsupported_platforms = unsupported_platforms, check = check, jobs = jobs) |> dplyr::filter(propagates) |> - dplyr::select(-dplyr::any_of(c("propagates", "reason", "failure_url", - "source_ok", "check_ok", "platform_ok", - "commit_ok", "vignettes_check", - "linux_check"))) + dplyr::select(-propagates, -reason, -failure_url) } #' Check which R Universe packages can propagate to Bioconductor @@ -623,23 +620,19 @@ get_candidates <- function(bu, os = NA_character_, arch = NA_character_, #' @description #' Applies the rules of `get_candidates()` to every package and reports which #' rules each one fails. For binaries, a package propagates when its binary -#' for `os` and `arch` passed `R CMD check` and built (`check_ok`), the source -#' build passed (`source_ok`, if `vignettes`), R Universe built the same -#' commit as BBS (`commit_ok`, if `commit`), and the platform is supported -#' (`platform_ok`, if `unsupported_platforms`). For source packages, the -#' source build passed (`source_ok`), `R CMD check` on Linux x86_64 passed -#' (`check_ok`), and, if `commit`, the commit is the same as BBS -#' (`commit_ok`). +#' for `os` and `arch` passed `R CMD check` and built (if `commit`), the +#' source build passed (if `vignettes`), R Universe built the same commit as +#' BBS (if `commit`), and the platform is supported (if +#' `unsupported_platforms`). For source packages, the source build passed, +#' `R CMD check` on Linux x86_64 passed, and, if `commit`, the commit is the +#' same as BBS. #' #' @inheritParams get_candidates #' @param bbs data.frame of BBS commits with columns Package, Version, #' BBS_commit, and Deprecated, used when `commit` is `TRUE`. If `NULL` #' (default), read with `bbs_status()` for every package type. #' -#' @returns data.frame of jobs with the results the rules use -#' (`vignettes_check`, the source build, for binaries; `linux_check` for -#' source packages; `BBS_commit` and `Deprecated` if `commit`), logical -#' columns for each rule that applies, `propagates`, `reason`, the rules a +#' @returns data.frame of jobs with `propagates`, `reason`, the rules a #' package fails, and `failure_url`, the R Universe job of the first #' failing build or check #' @@ -678,7 +671,6 @@ propagation_checks <- function(bu, os = NA_character_, arch = NA_character_, filter_by_arch("linux", "x86_64") |> one_per_package() linux_job <- match(key(rows), key(linux)) - rows$linux_check <- linux$jobs_check[linux_job] rules <- list( source_ok = rows$jobs_check %in% check & !is.na(rows$artifact), check_ok = linux$jobs_check[linux_job] %in% check) @@ -696,10 +688,8 @@ propagation_checks <- function(bu, os = NA_character_, arch = NA_character_, source_job <- match(key(rows), key(source_jobs)) job_urls <- list(source_ok = source_jobs$job_url[source_job], check_ok = rows$job_url) - if (vignettes) { - rows$vignettes_check <- source_jobs$jobs_check[source_job] - rules$source_ok <- rows$vignettes_check %in% check - } + if (vignettes) + rules$source_ok <- source_jobs$jobs_check[source_job] %in% check if (commit) rules$check_ok <- rows$binaries_check %in% check & rows$jobs_check %in% check & @@ -711,10 +701,8 @@ propagation_checks <- function(bu, os = NA_character_, arch = NA_character_, is_unsupported_platform) } if (commit) { - i <- match(key(rows), key(bbs)) - rows$BBS_commit <- bbs$BBS_commit[i] - rows$Deprecated <- bbs$Deprecated[i] - same <- substr(rows$RemoteSha, 1, 7) == rows$BBS_commit + bbs_commit <- bbs$BBS_commit[match(key(rows), key(bbs))] + same <- substr(rows$RemoteSha, 1, 7) == bbs_commit # Packages not in BBS have no commit to compare, so they fail rules$commit_ok <- !is.na(same) & same } @@ -724,8 +712,6 @@ propagation_checks <- function(bu, os = NA_character_, arch = NA_character_, "check failed", platform_ok = "unsupported platform", commit_ok = "commit differs from BBS") - for (rule in names(rules)) - rows[[rule]] <- rules[[rule]] passed <- if (length(rules)) Reduce(`&`, rules) else rep(TRUE, nrow(rows)) rows$propagates <- passed diff --git a/man/propagation_checks.Rd b/man/propagation_checks.Rd index b09829f..c7222fd 100644 --- a/man/propagation_checks.Rd +++ b/man/propagation_checks.Rd @@ -43,23 +43,19 @@ BBS_commit, and Deprecated, used when \code{commit} is \code{TRUE}. If \code{NUL (default), read with \code{bbs_status()} for every package type.} } \value{ -data.frame of jobs with the results the rules use -(\code{vignettes_check}, the source build, for binaries; \code{linux_check} for -source packages; \code{BBS_commit} and \code{Deprecated} if \code{commit}), logical -columns for each rule that applies, \code{propagates}, \code{reason}, the rules a +data.frame of jobs with \code{propagates}, \code{reason}, the rules a package fails, and \code{failure_url}, the R Universe job of the first failing build or check } \description{ Applies the rules of \code{get_candidates()} to every package and reports which rules each one fails. For binaries, a package propagates when its binary -for \code{os} and \code{arch} passed \verb{R CMD check} and built (\code{check_ok}), the source -build passed (\code{source_ok}, if \code{vignettes}), R Universe built the same -commit as BBS (\code{commit_ok}, if \code{commit}), and the platform is supported -(\code{platform_ok}, if \code{unsupported_platforms}). For source packages, the -source build passed (\code{source_ok}), \verb{R CMD check} on Linux x86_64 passed -(\code{check_ok}), and, if \code{commit}, the commit is the same as BBS -(\code{commit_ok}). +for \code{os} and \code{arch} passed \verb{R CMD check} and built (if \code{commit}), the +source build passed (if \code{vignettes}), R Universe built the same commit as +BBS (if \code{commit}), and the platform is supported (if +\code{unsupported_platforms}). For source packages, the source build passed, +\verb{R CMD check} on Linux x86_64 passed, and, if \code{commit}, the commit is the +same as BBS. } \examples{ \dontrun{ diff --git a/tests/testthat/test-candidates.R b/tests/testthat/test-candidates.R index 6d92b3d..392a709 100644 --- a/tests/testthat/test-candidates.R +++ b/tests/testthat/test-candidates.R @@ -90,12 +90,16 @@ test_that("mac binaries for both architectures use each target's folder", { "big-sur-x86_64/contrib/4.6/m_1.0.0.tgz")) }) -test_that("get_candidates returns jobs without the rule results", { - x <- get_candidates(list(), type = "source", jobs = jobs) - rule_columns <- c("propagates", "reason", "failure_url", "source_ok", - "check_ok", "platform_ok", "commit_ok", - "vignettes_check", "linux_check") - expect_false(any(rule_columns %in% names(x))) - y <- propagation_checks(list(), type = "source", jobs = jobs) - expect_true(all(c("linux_check", "propagates", "reason") %in% names(y))) +test_that("get_candidates returns jobs; propagation_checks adds 3 columns", { + bbs <- data.frame(Package = c("a", "b"), Version = c("1.0.0", "2.0.0"), + BBS_commit = c("aaaaaaa", "bbbbbbb"), Deprecated = FALSE) + for (type in c("source", "binary")) { + os <- if (type == "binary") "linux" else NA + x <- get_candidates(list(), os, type = type, jobs = jobs) + expect_true(all(names(x) %in% names(jobs))) + y <- propagation_checks(list(), os, type = type, commit = TRUE, + jobs = jobs, bbs = bbs) + expect_setequal(setdiff(names(y), names(jobs)), + c("propagates", "reason", "failure_url")) + } }) diff --git a/tests/testthat/test-utils.R b/tests/testthat/test-utils.R index 02de016..a7cd00c 100644 --- a/tests/testthat/test-utils.R +++ b/tests/testthat/test-utils.R @@ -253,10 +253,14 @@ test_that("get_candidates commit filter works", { # commit filter should only reduce or maintain row count expect_lte(nrow(with_commit), nrow(without_commit)) - # commit-filtered rows should all have matching commits - if (nrow(with_commit) > 0) - expect_true(all(with_commit$BBS_commit == - substr(with_commit$RemoteSha, 1, 7))) + # commit-filtered rows should all have the commit BBS built + if (nrow(with_commit) > 0) { + bbs <- biocUniTools:::.bbs_commits(devel) + bbs_commit <- bbs$BBS_commit[match( + paste(with_commit$Package, with_commit$Version), + paste(bbs$Package, bbs$Version))] + expect_true(all(bbs_commit == substr(with_commit$RemoteSha, 1, 7))) + } }) test_that("get_candidates for sonoma includes arm64 and universal binaries", { @@ -434,27 +438,32 @@ test_that("filter_by_arch passes all rows for windows", { expect_equal(nrow(result), 2) }) +# Source build results from R Universe, to check candidates against +release_jobs <- get_jobs(get_uni_df(get_raw_uni_df(release$universe)), + release$r_version, release$universe) +source_check <- function(candidates) { + source_jobs <- release_jobs[release_jobs$job_type == "source", ] + source_jobs$jobs_check[match(candidates$Package, source_jobs$Package)] +} + test_that("get_candidates vignette filter works", { # vignettes = TRUE (default) should filter on source check - with_vignettes <- get_candidates(release, "windows", vignettes = TRUE) - without_vignettes <- get_candidates(release, "windows", vignettes = FALSE) + with_vignettes <- get_candidates(release, "windows", vignettes = TRUE, + jobs = release_jobs) + without_vignettes <- get_candidates(release, "windows", vignettes = FALSE, + jobs = release_jobs) # vignette filter should only reduce or maintain row count expect_lte(nrow(with_vignettes), nrow(without_vignettes)) # all candidates should have a passing source build - checks <- propagation_checks(release, "windows", vignettes = TRUE) - passed <- checks$vignettes_check[match(with_vignettes$Package, - checks$Package)] - expect_true(all(passed %in% c("NOTE", "WARNING", "OK"))) + expect_true(all(source_check(with_vignettes) %in% + c("NOTE", "WARNING", "OK"))) }) test_that("get_candidates vignette check respects check argument", { ok_only <- get_candidates(release, "windows", vignettes = TRUE, - check = "OK") - checks <- propagation_checks(release, "windows", vignettes = TRUE, - check = "OK") - passed <- checks$vignettes_check[match(ok_only$Package, checks$Package)] - expect_true(all(passed == "OK")) + check = "OK", jobs = release_jobs) + expect_true(all(source_check(ok_only) == "OK")) }) From d291263cdd1351783f84ef873a1d0f7af348c580 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 10 Nov 2026 05:29:46 +0000 Subject: [PATCH 7/9] Draft: put all propagation criteria back in get_candidates() Rebuilt from the original get_candidates(), with each criterion as its own block controlled by an argument and source criteria inline. Results are the same as before. propagation_checks() is not yet removed. Co-Authored-By: Claude Opus 5.5 --- R/utils.R | 112 ++++++++++++++++++++++++++++----- man/get_candidates.Rd | 36 ++++++++--- tests/testthat/test-criteria.R | 97 ++++++++++++++++++++++++++++ 3 files changed, 220 insertions(+), 25 deletions(-) create mode 100644 tests/testthat/test-criteria.R diff --git a/R/utils.R b/R/utils.R index 7e64dc3..8e625ad 100644 --- a/R/utils.R +++ b/R/utils.R @@ -571,14 +571,24 @@ is_unsupported_platform <- function(unsupported_str, os, arch) { #' Get candidate packages in R Universe based on criteria #' -#' @description Filters binary or source packages that R Universe built by -#' check results and, optionally, the same commit in BBS. +#' @description +#' Finds the packages R Universe built that can propagate to Bioconductor. All +#' propagation criteria are in this function; each argument turns one on or +#' off. +#' +#' Binary candidates are binaries for `os` and `arch` that pass: +#' * `vignettes`: the source build (`R CMD build`, which builds vignettes) +#' passed +#' * `commit`: R Universe built the same commit as BBS, and the binary passed +#' `R CMD check` and built +#' * `unsupported_platforms`: the package does not declare the platform +#' unsupported #' -#' Binary candidates are binaries for `os` and `arch` whose check passed, -#' optionally with a passing source build (`vignettes`) and the same commit as -#' BBS (`commit`). Source candidates have a passing source build and a passing -#' Linux x86_64 `R CMD check`, optionally with the same commit as BBS -#' (`commit`). +#' Source candidates are source packages whose source build passed and whose +#' `R CMD check` on Linux x86_64 passed, and, with `commit`, R Universe built +#' the same commit as BBS. +#' +#' `check` is the results that pass. #' #' @param bu result of uni_for_bioc #' @param os character, for binaries @@ -591,8 +601,11 @@ is_unsupported_platform <- function(unsupported_str, os, arch) { #' platforms, for binaries #' @param check character results that pass #' @param jobs data.frame from `get_jobs()`. If `NULL` (default), downloaded. +#' @param bbs data.frame of BBS commits with columns Package, Version, and +#' BBS_commit, used when `commit` is `TRUE`. If `NULL` (default), read with +#' `bbs_status()`. #' -#' @returns data.frame of filtered candidate packages, with `artifact` the +#' @returns data.frame of the jobs that propagate, with `artifact` the #' download link #' #' @examples @@ -606,13 +619,82 @@ is_unsupported_platform <- function(unsupported_str, os, arch) { get_candidates <- function(bu, os = NA_character_, arch = NA_character_, type = c("binary", "source"), vignettes = TRUE, commit = FALSE, unsupported_platforms = TRUE, - check = c("NOTE", "WARNING", "OK"), jobs = NULL) { - propagation_checks(bu, os = os, arch = arch, type = type, - vignettes = vignettes, commit = commit, - unsupported_platforms = unsupported_platforms, - check = check, jobs = jobs) |> - dplyr::filter(propagates) |> - dplyr::select(-propagates, -reason, -failure_url) + check = c("NOTE", "WARNING", "OK"), jobs = NULL, + bbs = NULL) { + type <- match.arg(type) + if (is.null(jobs)) { + raw_universe_df <- get_raw_uni_df(bu$universe) + universe_df <- get_uni_df(raw_universe_df) + jobs <- get_jobs(universe_df, bu$r_version, bu$universe) + } + key <- function(df) paste(df$Package, df$Version) + + if (type == "source") { + candidates <- jobs |> + dplyr::filter(job_type == "source") + + # The source build passed, so there is a source package + candidates <- candidates |> + dplyr::filter(jobs_check %in% check, !is.na(artifact)) + + # R CMD check passed on Linux x86_64 + linux_jobs <- jobs |> + dplyr::filter(job_type == "binary", job_os == "linux") |> + filter_by_arch("linux", "x86_64") + linux_check <- linux_jobs$jobs_check[match(key(candidates), + key(linux_jobs))] + candidates <- candidates[linux_check %in% check, ] + } else { + os <- get_binary_os(os) + candidates <- jobs |> + dplyr::filter(job_type == "binary", job_os == os) + + if (!is.na(arch)) + candidates <- candidates |> + filter_by_arch(os, arch) + + # A Mac binary without compiled code works on both architectures; + # link arm64 to the arm64 folder, where R Universe also serves it + if (os == "mac" && arch %in% "arm64") + candidates$artifact <- .mac_arm64_artifact(candidates) + + if (vignettes) { + source_jobs <- jobs |> + dplyr::filter(job_type == "source") + vignettes_check <- source_jobs$jobs_check[match(key(candidates), + key(source_jobs))] + candidates <- candidates[vignettes_check %in% check, ] + } + } + + if (commit) { + if (is.null(bbs)) + bbs <- .bbs_commits(bu) + + if (type == "binary") + candidates <- candidates |> + dplyr::filter(binaries_check %in% check, + jobs_check %in% check, + binaries_status == "success") + + # R Universe built the same commit as BBS; packages not in BBS fail + bbs_commit <- bbs$BBS_commit[match(key(candidates), key(bbs))] + same_commit <- substr(candidates$RemoteSha, 1, 7) == bbs_commit + candidates <- candidates[same_commit %in% TRUE, ] + } + + if (unsupported_platforms && type == "binary") { + candidates <- candidates |> + dplyr::filter( + !purrr::pmap_lgl( + list(`Config/Bioconductor/UnsupportedPlatforms`, + job_os, + job_arch), + is_unsupported_platform)) + } + + candidates |> + dplyr::arrange(Package) } #' Check which R Universe packages can propagate to Bioconductor diff --git a/man/get_candidates.Rd b/man/get_candidates.Rd index 280bbd5..75c334e 100644 --- a/man/get_candidates.Rd +++ b/man/get_candidates.Rd @@ -13,7 +13,8 @@ get_candidates( commit = FALSE, unsupported_platforms = TRUE, check = c("NOTE", "WARNING", "OK"), - jobs = NULL + jobs = NULL, + bbs = NULL ) } \arguments{ @@ -36,20 +37,35 @@ platforms, for binaries} \item{check}{character results that pass} \item{jobs}{data.frame from \code{get_jobs()}. If \code{NULL} (default), downloaded.} + +\item{bbs}{data.frame of BBS commits with columns Package, Version, and +BBS_commit, used when \code{commit} is \code{TRUE}. If \code{NULL} (default), read with +\code{bbs_status()}.} } \value{ -data.frame of filtered candidate packages, with \code{artifact} the +data.frame of the jobs that propagate, with \code{artifact} the download link } \description{ -Filters binary or source packages that R Universe built by -check results and, optionally, the same commit in BBS. - -Binary candidates are binaries for \code{os} and \code{arch} whose check passed, -optionally with a passing source build (\code{vignettes}) and the same commit as -BBS (\code{commit}). Source candidates have a passing source build and a passing -Linux x86_64 \verb{R CMD check}, optionally with the same commit as BBS -(\code{commit}). +Finds the packages R Universe built that can propagate to Bioconductor. All +propagation criteria are in this function; each argument turns one on or +off. + +Binary candidates are binaries for \code{os} and \code{arch} that pass: +\itemize{ +\item \code{vignettes}: the source build (\verb{R CMD build}, which builds vignettes) +passed +\item \code{commit}: R Universe built the same commit as BBS, and the binary passed +\verb{R CMD check} and built +\item \code{unsupported_platforms}: the package does not declare the platform +unsupported +} + +Source candidates are source packages whose source build passed and whose +\verb{R CMD check} on Linux x86_64 passed, and, with \code{commit}, R Universe built +the same commit as BBS. + +\code{check} is the results that pass. } \examples{ \dontrun{ diff --git a/tests/testthat/test-criteria.R b/tests/testthat/test-criteria.R new file mode 100644 index 0000000..8abdf47 --- /dev/null +++ b/tests/testthat/test-criteria.R @@ -0,0 +1,97 @@ +# One package per criterion, each failing only that criterion, plus one +# that passes everything. Jobs are built from a small universe table. +make_jobs <- function() { + pkgs <- c("pass", "no_source", "no_check", "no_commit", "unsupported", + "note_only") + one <- function(pkg, source_check, win_check, unsupported) { + data.frame( + Package = pkg, Version = "1.0.0", + RemoteSha = paste0(substr(pkg, 1, 1), "aaaaaa1"), + job_type = c("source", "binary", "binary"), + job_os = c("linux", "linux", "win"), + job_arch = c(NA, "x86_64", "x86_64"), job_r_xy = "4.6", + jobs_check = c(source_check, "OK", win_check), + binary_os = c(NA, "linux", "win"), + binary_r_xy = c(NA, "4.6", "4.6"), + binaries_version = c(NA, "1.0.0", "1.0.0"), + binaries_arch = NA_character_, + binaries_distro = c(NA, "resolute", NA), + binaries_check = c(NA, "OK", win_check), + binaries_status = c(NA, "success", "success"), + job_id = seq_len(3) + 10 * match(pkg, pkgs), + `_buildurl` = "https://github.com/r-universe/bioc/actions/runs/1", + `Config/Bioconductor/UnsupportedPlatforms` = unsupported, + check.names = FALSE) + } + uni_df <- rbind(one("pass", "OK", "OK", NA), + one("no_source", "ERROR", "OK", NA), + one("no_check", "OK", "ERROR", NA), + one("no_commit", "OK", "OK", NA), + one("unsupported", "OK", "OK", "windows"), + one("note_only", "NOTE", "OK", NA)) + get_jobs(uni_df, "4.6.1", "bioc") +} +jobs <- make_jobs() +# BBS built the same commits, except for no_commit +bbs <- data.frame(Package = unique(jobs$Package), Version = "1.0.0") +bbs$BBS_commit <- paste0(substr(bbs$Package, 1, 1), "aaaaaa") +bbs$BBS_commit[bbs$Package == "no_commit"] <- "zzzzzzz" + +win <- function(...) + sort(get_candidates(list(), "windows", "x86_64", jobs = jobs, bbs = bbs, + ...)$Package) + +test_that("binary candidates pass every criterion that is on", { + expect_equal(win(commit = TRUE), c("note_only", "pass")) +}) + +test_that("vignettes turns only the source build criterion on and off", { + expect_false("no_source" %in% win(commit = TRUE)) + expect_true("no_source" %in% win(commit = TRUE, vignettes = FALSE)) +}) + +test_that("commit turns the commit criterion on and off", { + expect_false("no_commit" %in% win(commit = TRUE)) + expect_true("no_commit" %in% win(commit = FALSE)) +}) + +test_that("commit also turns the binary check criterion on and off", { + # The binary check is only applied with commit = TRUE, as before + expect_false("no_check" %in% win(commit = TRUE)) + expect_true("no_check" %in% win(commit = FALSE)) +}) + +test_that("unsupported_platforms turns only the platform criterion on and off", { + expect_false("unsupported" %in% win(commit = TRUE)) + expect_true("unsupported" %in% + win(commit = TRUE, unsupported_platforms = FALSE)) +}) + +test_that("check sets which results pass", { + expect_false("note_only" %in% win(commit = TRUE, check = "OK")) +}) + +src <- function(...) + sort(get_candidates(list(), type = "source", jobs = jobs, bbs = bbs, + ...)$Package) + +test_that("source candidates need the source build, linux check, and commit", { + # Every package passed its linux check; no_source failed its source build + expect_equal(src(commit = TRUE), + c("no_check", "note_only", "pass", "unsupported")) + expect_true("no_commit" %in% src(commit = FALSE)) + expect_false("no_source" %in% src(commit = FALSE)) + expect_false("note_only" %in% src(check = "OK")) +}) + +test_that("source candidates link to the source package", { + x <- get_candidates(list(), type = "source", jobs = jobs) + expect_equal(x$artifact[x$Package == "pass"], + "https://bioc.r-universe.dev/src/contrib/pass_1.0.0.tar.gz") +}) + +test_that("get_candidates returns only the jobs' columns", { + x <- get_candidates(list(), "windows", "x86_64", commit = TRUE, + jobs = jobs, bbs = bbs) + expect_true(all(names(x) %in% names(jobs))) +}) From da9d87e1a3f9e262c95ea02d01c8adf0634a45fc Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 10 Nov 2026 05:46:44 +0000 Subject: [PATCH 8/9] Remove propagation_checks(); propagation pages use get_candidates() get_candidates() is the only place with propagation criteria. A package propagates to a target if get_candidates() selects it; Yes links to the file and No to the target's R Universe job. source_jobs is created once. For macOS x86_64, Rbec now propagates as get_candidates() and harvest.R already had it; the page had used its failed arm64 job instead of its passing x86_64 binary. Co-Authored-By: Claude Opus 5.5 --- NAMESPACE | 1 - R/propagation.R | 70 +++++++++-------- R/utils.R | 121 +----------------------------- man/propagation_checks.Rd | 66 ---------------- man/propagation_status.Rd | 14 ++-- tests/testthat/test-candidates.R | 14 ---- tests/testthat/test-propagation.R | 21 ++---- 7 files changed, 53 insertions(+), 254 deletions(-) delete mode 100644 man/propagation_checks.Rd diff --git a/NAMESPACE b/NAMESPACE index c9675bb..baf343f 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -30,7 +30,6 @@ export(is_unsupported_platform) export(manifest_url) export(normalize_result) export(parse_uni_log) -export(propagation_checks) export(propagation_status) export(propagation_summary_table) export(propagation_table) diff --git a/R/propagation.R b/R/propagation.R index 0617438..3dc47e8 100644 --- a/R/propagation.R +++ b/R/propagation.R @@ -24,9 +24,9 @@ propagation_targets <- function() { #' Which Bioconductor packages propagate from R Universe #' #' @description -#' Applies `propagation_checks()` with the rules `harvest.R` uses, requiring -#' the same commit as BBS, for each of `targets` and every package of `types` -#' in the branch's manifest. +#' Uses `get_candidates()`, requiring the same commit as BBS, for each of +#' `targets` and every package of `types` in the branch's manifest. A package +#' propagates to a target if it is one of the target's candidates. #' #' @param branch character "devel" or "release" #' @param types character package types, from `bioc_pkg_types()$type` @@ -39,11 +39,9 @@ propagation_targets <- function() { #' @returns data.frame with one row per package and columns Package, type, #' Version, deprecated, uni_commit and bbs_commit (the commits R Universe #' and BBS built), and, for each target id, a logical column whether it -#' propagates, `_reason`, why not, `_artifact`, the file to -#' download if it propagates, and `_failure_url`, the R Universe job -#' that failed if it does not. A package with no job for a -#' target does not propagate, with reason "not built". Targets that do not -#' apply to a package's type are `NA`. +#' propagates, `_artifact`, the file to download if it does, and +#' `_job_url`, the target's R Universe job if it does not. Targets +#' that do not apply to a package's type are `NA`. #' #' @examples #' \dontrun{ @@ -70,24 +68,34 @@ propagation_status <- function(branch, types = bioc_pkg_types()$type, pkgs$bbs_commit <- bbs$BBS_commit[match(pkgs$Package, bbs$Package)] for (i in seq_len(nrow(targets))) { - checks <- propagation_checks(bu, os = targets$os[i], - arch = targets$arch[i], - type = targets$type[i], commit = TRUE, - jobs = jobs, bbs = bbs) - j <- match(pkgs$Package, checks$Package) id <- targets$id[i] + type <- targets$type[i] + os <- targets$os[i] + arch <- targets$arch[i] + candidates <- get_candidates(bu, os = os, arch = arch, type = type, + commit = TRUE, jobs = jobs, bbs = bbs) + + # The target's own job, linked when a package does not propagate + target_jobs <- if (type == "source") { + jobs[jobs$job_type == "source", ] + } else { + binary_os <- get_binary_os(os) + filter_by_arch(jobs[jobs$job_type == "binary" & + jobs$job_os == binary_os, ], + binary_os, arch) + } + applies <- pkgs$type %in% targets$types[[i]] - pkgs[[id]] <- ifelse(applies, !is.na(j) & checks$propagates[j] %in% TRUE, - NA) - pkgs[[paste0(id, "_reason")]] <- ifelse( - !applies, NA_character_, - ifelse(is.na(j), "not built", checks$reason[j])) - pkgs[[paste0(id, "_artifact")]] <- ifelse(pkgs[[id]] %in% TRUE, - checks$artifact[j], - NA_character_) - pkgs[[paste0(id, "_failure_url")]] <- ifelse(pkgs[[id]] %in% FALSE, - checks$failure_url[j], - NA_character_) + yes <- pkgs$Package %in% candidates$Package + pkgs[[id]] <- ifelse(applies, yes, NA) + pkgs[[paste0(id, "_artifact")]] <- ifelse( + applies & yes, + candidates$artifact[match(pkgs$Package, candidates$Package)], + NA_character_) + pkgs[[paste0(id, "_job_url")]] <- ifelse( + applies & !yes, + target_jobs$job_url[match(pkgs$Package, target_jobs$Package)], + NA_character_) } pkgs <- pkgs[order(pkgs$Package), , drop = FALSE] rownames(pkgs) <- NULL @@ -166,15 +174,12 @@ propagation_table <- function(status) { link <- function(url, html) ifelse(is.na(url), html, sprintf('%s', url, html)) - # "Yes" linked to the file, or "No" linked to the failing job, with the - # reasons - answer <- function(yes, reason, artifact, failure_url) + # "Yes" linked to the file, or "No" linked to the target's job + answer <- function(yes, artifact, job_url) dplyr::case_when( is.na(yes) ~ '—', yes ~ link(artifact, pill("success", "Yes")), - .default = paste0(link(failure_url, pill("danger", "No")), - '
', - htmltools::htmlEscape(reason), '')) + .default = link(job_url, pill("danger", "No"))) columns <- list( Package = reactable::colDef(name = "Package", html = TRUE, minWidth = 160), @@ -186,9 +191,8 @@ propagation_table <- function(status) { minWidth = 110)) for (i in seq_len(nrow(targets))) { id <- targets$id[i] - df[[id]] <- answer(status[[id]], status[[paste0(id, "_reason")]], - status[[paste0(id, "_artifact")]], - status[[paste0(id, "_failure_url")]]) + df[[id]] <- answer(status[[id]], status[[paste0(id, "_artifact")]], + status[[paste0(id, "_job_url")]]) columns[[id]] <- reactable::colDef(name = targets$label[i], html = TRUE, minWidth = 130) } diff --git a/R/utils.R b/R/utils.R index 8e625ad..cfb3caf 100644 --- a/R/utils.R +++ b/R/utils.R @@ -628,10 +628,11 @@ get_candidates <- function(bu, os = NA_character_, arch = NA_character_, jobs <- get_jobs(universe_df, bu$r_version, bu$universe) } key <- function(df) paste(df$Package, df$Version) + source_jobs <- jobs |> + dplyr::filter(job_type == "source") if (type == "source") { - candidates <- jobs |> - dplyr::filter(job_type == "source") + candidates <- source_jobs # The source build passed, so there is a source package candidates <- candidates |> @@ -659,8 +660,6 @@ get_candidates <- function(bu, os = NA_character_, arch = NA_character_, candidates$artifact <- .mac_arm64_artifact(candidates) if (vignettes) { - source_jobs <- jobs |> - dplyr::filter(job_type == "source") vignettes_check <- source_jobs$jobs_check[match(key(candidates), key(source_jobs))] candidates <- candidates[vignettes_check %in% check, ] @@ -697,120 +696,6 @@ get_candidates <- function(bu, os = NA_character_, arch = NA_character_, dplyr::arrange(Package) } -#' Check which R Universe packages can propagate to Bioconductor -#' -#' @description -#' Applies the rules of `get_candidates()` to every package and reports which -#' rules each one fails. For binaries, a package propagates when its binary -#' for `os` and `arch` passed `R CMD check` and built (if `commit`), the -#' source build passed (if `vignettes`), R Universe built the same commit as -#' BBS (if `commit`), and the platform is supported (if -#' `unsupported_platforms`). For source packages, the source build passed, -#' `R CMD check` on Linux x86_64 passed, and, if `commit`, the commit is the -#' same as BBS. -#' -#' @inheritParams get_candidates -#' @param bbs data.frame of BBS commits with columns Package, Version, -#' BBS_commit, and Deprecated, used when `commit` is `TRUE`. If `NULL` -#' (default), read with `bbs_status()` for every package type. -#' -#' @returns data.frame of jobs with `propagates`, `reason`, the rules a -#' package fails, and `failure_url`, the R Universe job of the first -#' failing build or check -#' -#' @examples -#' \dontrun{ -#' bu <- uni_for_bioc("devel") -#' propagation_checks(bu, type = "source", commit = TRUE) -#' } -#' -#' @export -propagation_checks <- function(bu, os = NA_character_, arch = NA_character_, - type = c("binary", "source"), vignettes = TRUE, - commit = FALSE, unsupported_platforms = TRUE, - check = c("NOTE", "WARNING", "OK"), jobs = NULL, - bbs = NULL) { - type <- match.arg(type) - if (is.null(jobs)) { - raw_universe_df <- get_raw_uni_df(bu$universe) - universe_df <- get_uni_df(raw_universe_df) - jobs <- get_jobs(universe_df, bu$r_version, bu$universe) - } - if (commit && is.null(bbs)) - bbs <- .bbs_commits(bu) - key <- function(df) paste(df$Package, df$Version) - one_per_package <- function(df) - dplyr::distinct(df, Package, Version, .keep_all = TRUE) - - source_jobs <- jobs |> - dplyr::filter(job_type == "source") |> - one_per_package() - - if (type == "source") { - rows <- source_jobs - linux <- jobs |> - dplyr::filter(job_type == "binary", job_os == "linux") |> - filter_by_arch("linux", "x86_64") |> - one_per_package() - linux_job <- match(key(rows), key(linux)) - rules <- list( - source_ok = rows$jobs_check %in% check & !is.na(rows$artifact), - check_ok = linux$jobs_check[linux_job] %in% check) - job_urls <- list(source_ok = rows$job_url, - check_ok = linux$job_url[linux_job]) - } else { - os <- get_binary_os(os) - rows <- jobs |> - dplyr::filter(job_type == "binary", job_os == os) - if (!is.na(arch)) - rows <- filter_by_arch(rows, os, arch) - if (os == "mac" && arch %in% "arm64") - rows$artifact <- .mac_arm64_artifact(rows) - rules <- list() - source_job <- match(key(rows), key(source_jobs)) - job_urls <- list(source_ok = source_jobs$job_url[source_job], - check_ok = rows$job_url) - if (vignettes) - rules$source_ok <- source_jobs$jobs_check[source_job] %in% check - if (commit) - rules$check_ok <- rows$binaries_check %in% check & - rows$jobs_check %in% check & - rows$binaries_status %in% "success" - if (unsupported_platforms) - rules$platform_ok <- !purrr::pmap_lgl( - list(rows$`Config/Bioconductor/UnsupportedPlatforms`, - rows$job_os, rows$job_arch), - is_unsupported_platform) - } - if (commit) { - bbs_commit <- bbs$BBS_commit[match(key(rows), key(bbs))] - same <- substr(rows$RemoteSha, 1, 7) == bbs_commit - # Packages not in BBS have no commit to compare, so they fail - rules$commit_ok <- !is.na(same) & same - } - - labels <- c(source_ok = "source build failed", - check_ok = if (type == "source") "linux check failed" else - "check failed", - platform_ok = "unsupported platform", - commit_ok = "commit differs from BBS") - passed <- if (length(rules)) Reduce(`&`, rules) else - rep(TRUE, nrow(rows)) - rows$propagates <- passed - rows$reason <- vapply(seq_len(nrow(rows)), function(i) { - failed <- names(rules)[!vapply(rules, `[`, logical(1), i)] - paste(labels[failed], collapse = "; ") - }, character(1)) - # Link to the first failing job; the commit and platform rules have none - rows$failure_url <- NA_character_ - for (rule in rev(intersect(c("source_ok", "check_ok"), names(rules)))) { - failed <- !rules[[rule]] - rows$failure_url[failed] <- job_urls[[rule]][failed] - } - rows |> - dplyr::arrange(Package) -} - #' Link a Mac binary built for both architectures to the arm64 folder #' #' @description A binary without compiled code works on both Mac diff --git a/man/propagation_checks.Rd b/man/propagation_checks.Rd deleted file mode 100644 index c7222fd..0000000 --- a/man/propagation_checks.Rd +++ /dev/null @@ -1,66 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/utils.R -\name{propagation_checks} -\alias{propagation_checks} -\title{Check which R Universe packages can propagate to Bioconductor} -\usage{ -propagation_checks( - bu, - os = NA_character_, - arch = NA_character_, - type = c("binary", "source"), - vignettes = TRUE, - commit = FALSE, - unsupported_platforms = TRUE, - check = c("NOTE", "WARNING", "OK"), - jobs = NULL, - bbs = NULL -) -} -\arguments{ -\item{bu}{result of uni_for_bioc} - -\item{os}{character, for binaries} - -\item{arch}{character (default NA_character_) x86_64 or arm64, for binaries} - -\item{type}{character "binary" (default) or "source"} - -\item{vignettes}{logical (default TRUE) Check status of source / vignettes, -for binaries} - -\item{commit}{logical (default FALSE) Check commit hash in RU and BBS?} - -\item{unsupported_platforms}{logical (default TRUE) Check for unsupported -platforms, for binaries} - -\item{check}{character results that pass} - -\item{jobs}{data.frame from \code{get_jobs()}. If \code{NULL} (default), downloaded.} - -\item{bbs}{data.frame of BBS commits with columns Package, Version, -BBS_commit, and Deprecated, used when \code{commit} is \code{TRUE}. If \code{NULL} -(default), read with \code{bbs_status()} for every package type.} -} -\value{ -data.frame of jobs with \code{propagates}, \code{reason}, the rules a -package fails, and \code{failure_url}, the R Universe job of the first -failing build or check -} -\description{ -Applies the rules of \code{get_candidates()} to every package and reports which -rules each one fails. For binaries, a package propagates when its binary -for \code{os} and \code{arch} passed \verb{R CMD check} and built (if \code{commit}), the -source build passed (if \code{vignettes}), R Universe built the same commit as -BBS (if \code{commit}), and the platform is supported (if -\code{unsupported_platforms}). For source packages, the source build passed, -\verb{R CMD check} on Linux x86_64 passed, and, if \code{commit}, the commit is the -same as BBS. -} -\examples{ -\dontrun{ -bu <- uni_for_bioc("devel") -propagation_checks(bu, type = "source", commit = TRUE) -} - -} diff --git a/man/propagation_status.Rd b/man/propagation_status.Rd index e5fd4a4..70d451f 100644 --- a/man/propagation_status.Rd +++ b/man/propagation_status.Rd @@ -29,16 +29,14 @@ BBS_commit, and Deprecated. If \code{NULL} (default), read with data.frame with one row per package and columns Package, type, Version, deprecated, uni_commit and bbs_commit (the commits R Universe and BBS built), and, for each target id, a logical column whether it -propagates, \verb{_reason}, why not, \verb{_artifact}, the file to -download if it propagates, and \verb{_failure_url}, the R Universe job -that failed if it does not. A package with no job for a -target does not propagate, with reason "not built". Targets that do not -apply to a package's type are \code{NA}. +propagates, \verb{_artifact}, the file to download if it does, and +\verb{_job_url}, the target's R Universe job if it does not. Targets +that do not apply to a package's type are \code{NA}. } \description{ -Applies \code{propagation_checks()} with the rules \code{harvest.R} uses, requiring -the same commit as BBS, for each of \code{targets} and every package of \code{types} -in the branch's manifest. +Uses \code{get_candidates()}, requiring the same commit as BBS, for each of +\code{targets} and every package of \code{types} in the branch's manifest. A package +propagates to a target if it is one of the target's candidates. } \examples{ \dontrun{ diff --git a/tests/testthat/test-candidates.R b/tests/testthat/test-candidates.R index 392a709..08fa7b9 100644 --- a/tests/testthat/test-candidates.R +++ b/tests/testthat/test-candidates.R @@ -89,17 +89,3 @@ test_that("mac binaries for both architectures use each target's folder", { expect_equal(x86$artifact, paste0("https://bioc.r-universe.dev/bin/macosx/", "big-sur-x86_64/contrib/4.6/m_1.0.0.tgz")) }) - -test_that("get_candidates returns jobs; propagation_checks adds 3 columns", { - bbs <- data.frame(Package = c("a", "b"), Version = c("1.0.0", "2.0.0"), - BBS_commit = c("aaaaaaa", "bbbbbbb"), Deprecated = FALSE) - for (type in c("source", "binary")) { - os <- if (type == "binary") "linux" else NA - x <- get_candidates(list(), os, type = type, jobs = jobs) - expect_true(all(names(x) %in% names(jobs))) - y <- propagation_checks(list(), os, type = type, commit = TRUE, - jobs = jobs, bbs = bbs) - expect_setequal(setdiff(names(y), names(jobs)), - c("propagates", "reason", "failure_url")) - } -}) diff --git a/tests/testthat/test-propagation.R b/tests/testthat/test-propagation.R index 85aab0f..52b57eb 100644 --- a/tests/testthat/test-propagation.R +++ b/tests/testthat/test-propagation.R @@ -25,20 +25,7 @@ bbs <- data.frame(Package = c("a", "b", "d"), BBS_commit = c("aaaaaaa", "bbbbbbb", "zzzzzzz"), Deprecated = c(FALSE, TRUE, FALSE)) -test_that("propagation_checks reports the rules a package fails", { - x <- propagation_checks(list(), type = "source", commit = TRUE, - jobs = jobs, bbs = bbs) - expect_equal(x$Package, c("a", "b", "d")) - expect_equal(x$propagates, c(TRUE, FALSE, FALSE)) - expect_equal(x$reason, c("", "linux check failed", - "linux check failed; commit differs from BBS")) - # "b" links to its failing linux job; "d" has no linux job - expect_equal(x$failure_url, - c(NA, "https://github.com/r-universe/bioc/actions/runs/1/job/5", - NA)) -}) - -test_that("propagation_status marks binaries of data packages not applicable", { +test_that("propagation_status marks what get_candidates() selects", { local_mocked_bindings( uni_for_bioc = function(branch) list(bioc_branch = branch), bioc_manifest = function(branch, type) @@ -47,14 +34,20 @@ test_that("propagation_status marks binaries of data packages not applicable", { st <- propagation_status("devel", types = c("software", "data-experiment"), jobs = jobs, bbs = bbs, targets = targets) expect_equal(st$Package, c("a", "b", "d")) + # "b" fails its linux check; "d" has a different commit than BBS expect_equal(st$source, c(TRUE, FALSE, FALSE)) + # binaries only apply to software expect_equal(st$windows_x86_64, c(TRUE, TRUE, NA)) expect_equal(st$deprecated, c(FALSE, TRUE, FALSE)) expect_equal(st$uni_commit, c("aaaaaaa", "bbbbbbb", "ddddddd")) expect_equal(st$bbs_commit, c("aaaaaaa", "bbbbbbb", "zzzzzzz")) + # "Yes" links to the file; "No" links to the target's own job expect_equal(st$source_artifact, c("https://bioc.r-universe.dev/src/contrib/a_1.0.0.tar.gz", NA, NA)) + expect_equal(st$source_job_url, + c(NA, "https://github.com/r-universe/bioc/actions/runs/1/job/4", + "https://github.com/r-universe/bioc/actions/runs/1/job/7")) skip_if_not_installed("reactable") expect_s3_class(propagation_table(st), "reactable") }) From d4ca32cd1cc6b210ac034b0b30baf49c5e13b414 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 10 Nov 2026 06:14:35 +0000 Subject: [PATCH 9/9] Require arch, check binaries without commit, and fix universal binaries get_candidates() stops if arch is missing for windows or macosx, and checks that a binary passed R CMD check and built whether or not commit is on, so commit = FALSE only drops the commit criterion. filter_by_arch() uses a universal binary for the other architecture only for packages without a job for that architecture; a compiled package's failed job is not a universal binary. With commit = TRUE the candidates are unchanged. Co-Authored-By: Claude Opus 5.5 --- R/utils.R | 48 ++++++++++++++++++++++---------- man/filter_by_arch.Rd | 10 +++++-- man/get_candidates.Rd | 9 +++--- tests/testthat/test-candidates.R | 5 ++-- tests/testthat/test-criteria.R | 12 ++++++-- tests/testthat/test-utils.R | 42 ++++++++++++++++++++++------ 6 files changed, 92 insertions(+), 34 deletions(-) diff --git a/R/utils.R b/R/utils.R index cfb3caf..af119f7 100644 --- a/R/utils.R +++ b/R/utils.R @@ -468,8 +468,14 @@ get_jobs <- function(universe_df, r_version, universe) { #' function filters specifically for data observed in R Universe, which could #' change in the future. #' -#' * mac universal binaries: job_arch == 'arm64' & !is.na(binaries_arch) -#' * linux/win default binaries: job_arch == 'x86_64' & !is.na(binaries_arch) +#' * mac universal binaries: job_arch == 'arm64' & is.na(binaries_arch), for +#' packages without a mac x86_64 job +#' * linux/win universal binaries: job_arch == 'x86_64' & is.na(binaries_arch), +#' for packages without an arm64 job +#' +#' Packages that need compilation have a job for each architecture, so a row +#' without binaries_arch from them is a job that built no binary, not a +#' universal binary, and is not used for the other architecture. #' #' @param df data.frame from get_jobs() #' @param os character @@ -491,9 +497,17 @@ filter_by_arch <- function(df, os, arch = "x86_64") { if (is.na(arch)) return(df) + # Packages that need compilation have a job for each architecture; only + # packages without one for `job_arch` can have a universal binary + has_job <- function(job_arch) + paste(df$Package, df$job_os) %in% + paste(df$Package, df$job_os)[df$job_arch %in% job_arch] + if (os == "mac" && arch == "x86_64") { dplyr::filter(df, - (binaries_arch == "x86_64") | (job_arch == "arm64" & is.na(binaries_arch))) + (binaries_arch == "x86_64") | + (job_arch == "arm64" & is.na(binaries_arch) & + !has_job("x86_64"))) } else if (os == "mac" && arch == "arm64") { dplyr::filter(df, (binaries_arch == "aarch64") | (job_arch == "arm64" & is.na(binaries_arch))) @@ -502,7 +516,9 @@ filter_by_arch <- function(df, os, arch = "x86_64") { (binaries_arch == "x86_64") | (job_arch == "x86_64" & is.na(binaries_arch))) } else if (os %in% c("linux", "win") && arch == "arm64") { dplyr::filter(df, - (binaries_arch == "aarch64") | (job_arch == "x86_64" & is.na(binaries_arch))) + (binaries_arch == "aarch64") | + (job_arch == "x86_64" & is.na(binaries_arch) & + !has_job("arm64"))) } else { df } @@ -576,11 +592,11 @@ is_unsupported_platform <- function(unsupported_str, os, arch) { #' propagation criteria are in this function; each argument turns one on or #' off. #' -#' Binary candidates are binaries for `os` and `arch` that pass: +#' Binary candidates are binaries for `os` and `arch` that passed +#' `R CMD check` and built, and pass: #' * `vignettes`: the source build (`R CMD build`, which builds vignettes) #' passed -#' * `commit`: R Universe built the same commit as BBS, and the binary passed -#' `R CMD check` and built +#' * `commit`: R Universe built the same commit as BBS #' * `unsupported_platforms`: the package does not declare the platform #' unsupported #' @@ -592,7 +608,8 @@ is_unsupported_platform <- function(unsupported_str, os, arch) { #' #' @param bu result of uni_for_bioc #' @param os character, for binaries -#' @param arch character (default NA_character_) x86_64 or arm64, for binaries +#' @param arch character x86_64 or arm64, required for windows and macosx +#' binaries #' @param type character "binary" (default) or "source" #' @param vignettes logical (default TRUE) Check status of source / vignettes, #' for binaries @@ -622,6 +639,9 @@ get_candidates <- function(bu, os = NA_character_, arch = NA_character_, check = c("NOTE", "WARNING", "OK"), jobs = NULL, bbs = NULL) { type <- match.arg(type) + if (type == "binary" && get_binary_os(os) %in% c("mac", "win") && + is.na(arch)) + stop("arch must be x86_64 or arm64 for ", os, " binaries") if (is.null(jobs)) { raw_universe_df <- get_raw_uni_df(bu$universe) universe_df <- get_uni_df(raw_universe_df) @@ -659,6 +679,12 @@ get_candidates <- function(bu, os = NA_character_, arch = NA_character_, if (os == "mac" && arch %in% "arm64") candidates$artifact <- .mac_arm64_artifact(candidates) + # The binary passed R CMD check and built + candidates <- candidates |> + dplyr::filter(binaries_check %in% check, + jobs_check %in% check, + binaries_status == "success") + if (vignettes) { vignettes_check <- source_jobs$jobs_check[match(key(candidates), key(source_jobs))] @@ -670,12 +696,6 @@ get_candidates <- function(bu, os = NA_character_, arch = NA_character_, if (is.null(bbs)) bbs <- .bbs_commits(bu) - if (type == "binary") - candidates <- candidates |> - dplyr::filter(binaries_check %in% check, - jobs_check %in% check, - binaries_status == "success") - # R Universe built the same commit as BBS; packages not in BBS fail bbs_commit <- bbs$BBS_commit[match(key(candidates), key(bbs))] same_commit <- substr(candidates$RemoteSha, 1, 7) == bbs_commit diff --git a/man/filter_by_arch.Rd b/man/filter_by_arch.Rd index 5013465..2a40ec7 100644 --- a/man/filter_by_arch.Rd +++ b/man/filter_by_arch.Rd @@ -21,9 +21,15 @@ Some builds can have arch-specific and universal binaries. This function filters specifically for data observed in R Universe, which could change in the future. \itemize{ -\item mac universal binaries: job_arch == 'arm64' & !is.na(binaries_arch) -\item linux/win default binaries: job_arch == 'x86_64' & !is.na(binaries_arch) +\item mac universal binaries: job_arch == 'arm64' & is.na(binaries_arch), for +packages without a mac x86_64 job +\item linux/win universal binaries: job_arch == 'x86_64' & is.na(binaries_arch), +for packages without an arm64 job } + +Packages that need compilation have a job for each architecture, so a row +without binaries_arch from them is a job that built no binary, not a +universal binary, and is not used for the other architecture. } \examples{ bu <- uni_for_bioc("devel") diff --git a/man/get_candidates.Rd b/man/get_candidates.Rd index 75c334e..477c659 100644 --- a/man/get_candidates.Rd +++ b/man/get_candidates.Rd @@ -22,7 +22,8 @@ get_candidates( \item{os}{character, for binaries} -\item{arch}{character (default NA_character_) x86_64 or arm64, for binaries} +\item{arch}{character x86_64 or arm64, required for windows and macosx +binaries} \item{type}{character "binary" (default) or "source"} @@ -51,12 +52,12 @@ Finds the packages R Universe built that can propagate to Bioconductor. All propagation criteria are in this function; each argument turns one on or off. -Binary candidates are binaries for \code{os} and \code{arch} that pass: +Binary candidates are binaries for \code{os} and \code{arch} that passed +\verb{R CMD check} and built, and pass: \itemize{ \item \code{vignettes}: the source build (\verb{R CMD build}, which builds vignettes) passed -\item \code{commit}: R Universe built the same commit as BBS, and the binary passed -\verb{R CMD check} and built +\item \code{commit}: R Universe built the same commit as BBS \item \code{unsupported_platforms}: the package does not declare the platform unsupported } diff --git a/tests/testthat/test-candidates.R b/tests/testthat/test-candidates.R index 08fa7b9..3ba74cf 100644 --- a/tests/testthat/test-candidates.R +++ b/tests/testthat/test-candidates.R @@ -48,9 +48,8 @@ test_that("get_candidates keeps only binaries for type binary", { x <- get_candidates(list(), "linux", "x86_64", vignettes = FALSE, jobs = jobs) expect_true(all(x$job_type == "binary")) - expect_equal(x$Package, c("a", "b")) - # "b" failed its check, so there is nothing to download - expect_true(is.na(x$artifact[x$Package == "b"])) + # "b" failed its linux check, so it is not a candidate + expect_equal(x$Package, "a") }) test_that("get_candidates source needs the source build and linux check", { diff --git a/tests/testthat/test-criteria.R b/tests/testthat/test-criteria.R index 8abdf47..2f7f8ce 100644 --- a/tests/testthat/test-criteria.R +++ b/tests/testthat/test-criteria.R @@ -55,10 +55,16 @@ test_that("commit turns the commit criterion on and off", { expect_true("no_commit" %in% win(commit = FALSE)) }) -test_that("commit also turns the binary check criterion on and off", { - # The binary check is only applied with commit = TRUE, as before +test_that("the binary check applies whether or not commit is on", { expect_false("no_check" %in% win(commit = TRUE)) - expect_true("no_check" %in% win(commit = FALSE)) + expect_false("no_check" %in% win(commit = FALSE)) +}) + +test_that("arch is required for windows and macosx binaries", { + expect_error(get_candidates(list(), "windows", jobs = jobs), + "arch must be x86_64 or arm64") + expect_error(get_candidates(list(), "macosx", jobs = jobs), + "arch must be x86_64 or arm64") }) test_that("unsupported_platforms turns only the platform criterion on and off", { diff --git a/tests/testthat/test-utils.R b/tests/testthat/test-utils.R index a7cd00c..5f90575 100644 --- a/tests/testthat/test-utils.R +++ b/tests/testthat/test-utils.R @@ -231,24 +231,24 @@ test_that("get_candidates gets R Universe data", { test_that("get_candidates filters on check status", { # without commit filter, check status is not filtered - candidates <- get_candidates(devel, "windows") + candidates <- get_candidates(devel, "windows", "x86_64") expect_gt(nrow(candidates), 0) # with commit filter, check status is filtered - candidates_commit <- get_candidates(devel, "windows", commit = TRUE) + candidates_commit <- get_candidates(devel, "windows", "x86_64", commit = TRUE) expect_true(all(candidates_commit$binaries_check %in% c("NOTE", "WARNING", "OK"))) expect_true(all(candidates_commit$jobs_check %in% c("NOTE", "WARNING", "OK"))) expect_true(all(candidates_commit$binaries_status == "success")) # restrict to OK only - ok_only <- get_candidates(devel, "windows", commit = TRUE, check = "OK") + ok_only <- get_candidates(devel, "windows", "x86_64", commit = TRUE, check = "OK") expect_true(all(ok_only$binaries_check == "OK")) expect_true(all(ok_only$jobs_check == "OK")) }) test_that("get_candidates commit filter works", { - without_commit <- get_candidates(devel, "windows", commit = FALSE) - with_commit <- get_candidates(devel, "windows", commit = TRUE) + without_commit <- get_candidates(devel, "windows", "x86_64", commit = FALSE) + with_commit <- get_candidates(devel, "windows", "x86_64", commit = TRUE) # commit filter should only reduce or maintain row count expect_lte(nrow(with_commit), nrow(without_commit)) @@ -448,9 +448,9 @@ source_check <- function(candidates) { test_that("get_candidates vignette filter works", { # vignettes = TRUE (default) should filter on source check - with_vignettes <- get_candidates(release, "windows", vignettes = TRUE, + with_vignettes <- get_candidates(release, "windows", "x86_64", vignettes = TRUE, jobs = release_jobs) - without_vignettes <- get_candidates(release, "windows", vignettes = FALSE, + without_vignettes <- get_candidates(release, "windows", "x86_64", vignettes = FALSE, jobs = release_jobs) # vignette filter should only reduce or maintain row count @@ -462,8 +462,34 @@ test_that("get_candidates vignette filter works", { }) test_that("get_candidates vignette check respects check argument", { - ok_only <- get_candidates(release, "windows", vignettes = TRUE, + ok_only <- get_candidates(release, "windows", "x86_64", vignettes = TRUE, check = "OK", jobs = release_jobs) expect_true(all(source_check(ok_only) == "OK")) }) + +test_that("filter_by_arch uses universal binaries only for packages without the other arch's job", { + # "pure" has one universal binary from its arm64 job; "compiled" has jobs + # for both architectures and its arm64 job built no binary + df <- data.frame( + Package = c("pure", "compiled", "compiled"), + job_os = "mac", + job_arch = c("arm64", "arm64", "x86_64"), + binaries_arch = c(NA, NA, "x86_64")) + result <- filter_by_arch(df, "mac", "x86_64") + expect_equal(result$Package, c("pure", "compiled")) + expect_equal(result$job_arch, c("arm64", "x86_64")) + # the compiled package's failed arm64 job is still its own arm64 row + expect_equal(nrow(filter_by_arch(df, "mac", "arm64")), 2) +}) + +test_that("filter_by_arch windows arm64 uses x86_64 universal binaries only for packages without an arm64 job", { + df <- data.frame( + Package = c("pure", "compiled", "compiled"), + job_os = "win", + job_arch = c("x86_64", "x86_64", "arm64"), + binaries_arch = c(NA, NA, "aarch64")) + result <- filter_by_arch(df, "win", "arm64") + expect_equal(result$Package, c("pure", "compiled")) + expect_equal(result$job_arch, c("x86_64", "arm64")) +})