From d0cd59fe83415fe9a479336277f573a92e9d7145 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 18 Oct 2026 12:50:06 +0000 Subject: [PATCH 1/2] ci: Check Bioconductor reverse dependencies in revdep4 Pin every cynkra/cynkratemplate action in revdep4 to e45daec and apply .github/revdepx/bioconductor.patch to its revdepx scripts in the plan, test and collect jobs. The patch is read from the workflow's own commit, so a `ref` input that lacks it still gets it. The patch adds REVDEPX_BIOC (none, direct or all): plan.R enumerates reverse dependencies over CRAN and Bioconductor metadata and accepts Bioconductor names in `packages:`, shard.R downloads those sources from Bioconductor, and collect.R links them to bioconductor.org. The new `bioconductor` input defaults to all. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015o4YxHfzPsnTZ9fUBQABDV --- .github/revdepx/bioconductor.patch | 167 +++++++++++++++++++++++++++++ .github/workflows/revdep4.yaml | 70 +++++++++--- 2 files changed, 221 insertions(+), 16 deletions(-) create mode 100644 .github/revdepx/bioconductor.patch diff --git a/.github/revdepx/bioconductor.patch b/.github/revdepx/bioconductor.patch new file mode 100644 index 00000000000..6276beb3f83 --- /dev/null +++ b/.github/revdepx/bioconductor.patch @@ -0,0 +1,167 @@ +rigraph's patch to the revdepx scripts of cynkra/cynkratemplate at e45daecc565a10e0624769f868a680e147d2920c, +applied by .github/workflows/revdep4.yaml: checks Bioconductor reverse dependencies (REVDEPX_BIOC). +See handbook/testing/revdep/README.md. + +diff --git a/.github/actions/revdepx/collect.R b/.github/actions/revdepx/collect.R +index 0792c2a..c1e4e6f 100644 +--- a/.github/actions/revdepx/collect.R ++++ b/.github/actions/revdepx/collect.R +@@ -42,6 +42,7 @@ source(file.path( + results_dir <- env_chr("RESULTS_DIR") + stopifnot(nzchar(results_dir)) + plan <- read_json(env_chr("PLAN", "plan.json")) ++options(revdepx.bioc_packages = unlist(plan$bioc_packages)) + retry_dir <- env_chr("RETRY_DIR") + out_dir <- env_chr("OUT_DIR", "revdep") + baseline_out <- env_chr("BASELINE_OUT", "baseline") +@@ -828,6 +829,18 @@ readme <- gsub( + "[\\1](https://cran.r-project.org/package=\\1)", + readme + ) ++for (bioc_package in getOption("revdepx.bioc_packages")) { ++ readme <- gsub( ++ sprintf( ++ "[%s](https://cran.r-project.org/package=%s)", ++ bioc_package, ++ bioc_package ++ ), ++ bioc_link(bioc_package), ++ readme, ++ fixed = TRUE ++ ) ++} + readme <- gsub("^(#+)(\\s)", "##\\1\\2", readme) + + run_id <- env_chr("GITHUB_RUN_ID") +diff --git a/.github/actions/revdepx/plan.R b/.github/actions/revdepx/plan.R +index bb519d8..705f53b 100644 +--- a/.github/actions/revdepx/plan.R ++++ b/.github/actions/revdepx/plan.R +@@ -46,6 +46,9 @@ + # `broken` to take them from the committed report + # REVDEPX_WHICH - "strong" (default) or "most" (adds Suggests/ + # Enhances dependents) ++# REVDEPX_BIOC - which Bioconductor reverse dependencies to check ++# too: "none" (default), "direct" (level 1 only) ++# or "all" (every level walks CRAN and Bioconductor) + # REVDEPX_WORKERS - packages checked concurrently per shard + # (default: 4) + # REVDEPX_R_VERSION - the full R version the *containers* check under, +@@ -113,6 +116,10 @@ which_input <- match.arg( + env_chr("REVDEPX_WHICH", "strong"), + c("strong", "most") + ) ++bioc_input <- match.arg( ++ tolower(env_chr("REVDEPX_BIOC", "none")), ++ c("none", "direct", "all") ++) + depth_raw <- tolower(env_chr("REVDEPX_DEPTH", "1")) + depth <- if (depth_raw %in% c("all", "max", "inf", "infinity")) { + Inf +@@ -347,6 +354,10 @@ if (!package %in% rownames(db)) { + cran_version <- unname(db[package, "Version"]) + inform("CRAN version: ", cran_version) + ++# What may be checked: CRAN alone, or CRAN and Bioconductor. dep_db() keeps ++# CRAN's row for a name both carry, so a CRAN package stays a CRAN check. ++check_db <- if (bioc_input == "none") db else dep_db() ++ + # ------------------------------------------------------------- enumeration --- + + # Breadth-first over reverse dependencies: level 1 depends on the package +@@ -357,9 +368,12 @@ level_of <- integer() + frontier <- package + level <- 0L + while (level < depth && length(frontier) > 0) { ++ # "direct" adds Bioconductor at level 1 only; deeper levels find CRAN ++ # dependents alone, as without Bioconductor. ++ bioc_level <- bioc_input == "all" || (bioc_input == "direct" && level == 0) + found <- tools::package_dependencies( + frontier, +- db = db, ++ db = if (bioc_level) check_db else db, + which = if (which_input == "most") "most" else "strong", + reverse = TRUE + ) +@@ -466,15 +480,23 @@ if (length(packages_input) > 0) { + candidates <- revdeps + } + +-dropped <- setdiff(candidates, rownames(db)) ++dropped <- setdiff(candidates, rownames(check_db)) + if (length(dropped) > 0) { +- inform("Not on CRAN, dropped: ", paste(dropped, collapse = ", ")) ++ inform( ++ if (bioc_input == "none") "Not on CRAN" else "Not on CRAN or Bioconductor", ++ ", dropped: ", ++ paste(dropped, collapse = ", ") ++ ) + } +-packages <- intersect(candidates, rownames(db)) ++packages <- intersect(candidates, rownames(check_db)) + if (length(packages) == 0) { + plan_nothing("no packages left to check") + } +-their_version <- setNames(unname(db[packages, "Version"]), packages) ++their_version <- setNames(unname(check_db[packages, "Version"]), packages) ++bioc_packages <- sort(setdiff(packages, rownames(db))) ++if (length(bioc_packages) > 0) { ++ inform(length(bioc_packages), " of them from Bioconductor") ++} + + # ------------------------------------------------------------------ weights -- + +@@ -1192,6 +1214,8 @@ plan <- list( + ref = env_chr("GITHUB_REF_NAME"), + which = which_input, + depth = depth_raw, ++ bioc = bioc_input, ++ bioc_packages = as.list(bioc_packages), + levels = as.list(level_counts), + selection = selection, + part = part, +diff --git a/.github/actions/revdepx/shard.R b/.github/actions/revdepx/shard.R +index 1460e84..6ac1c95 100644 +--- a/.github/actions/revdepx/shard.R ++++ b/.github/actions/revdepx/shard.R +@@ -606,12 +606,14 @@ src_dir <- file.path(work, "src") + dir.create(src_dir, showWarnings = FALSE) + sources <- list() + for (name in runnable) { ++ # The plan names the Bioconductor revdeps it admitted under REVDEPX_BIOC. ++ repos <- if (name %in% unlist(plan$bioc_packages)) bioc_repos() else cran_repo() + tarball <- tryCatch( + { + hit <- utils::download.packages( + name, + destdir = src_dir, +- repos = cran_repo(), ++ repos = repos, + type = "source", + quiet = TRUE + ) +diff --git a/.github/actions/revdepx/util.R b/.github/actions/revdepx/util.R +index 69d954f..9379269 100644 +--- a/.github/actions/revdepx/util.R ++++ b/.github/actions/revdepx/util.R +@@ -104,11 +104,19 @@ this_run_link <- function(text = env_chr("GITHUB_RUN_ID", "local")) { + } + + # A revdep, linked to its CRAN page -- the one page about it that is reachable +-# from a job summary, and the one that names its maintainer. ++# from a job summary, and the one that names its maintainer -- or to its ++# Bioconductor page for the names in option `revdepx.bioc_packages`. + cran_link <- function(package) { ++ if (package %in% getOption("revdepx.bioc_packages")) { ++ return(bioc_link(package)) ++ } + sprintf("[%s](https://cran.r-project.org/package=%s)", package, package) + } + ++bioc_link <- function(package) { ++ sprintf("[%s](https://bioconductor.org/packages/%s)", package, package) ++} ++ + # ------------------------------------------------------------------- JSON ---- + + write_json <- function(x, path) { diff --git a/.github/workflows/revdep4.yaml b/.github/workflows/revdep4.yaml index aef198f6b59..05d02c9e324 100644 --- a/.github/workflows/revdep4.yaml +++ b/.github/workflows/revdep4.yaml @@ -59,6 +59,14 @@ # so history from the sibling's live runs still serves as baselines and # timings. # +# Every kit action is pinned to one kit commit, and the jobs that run +# plan.R, shard.R or collect.R apply .github/revdepx/bioconductor.patch to +# the scripts first: it checks Bioconductor reverse dependencies too +# (REVDEPX_BIOC, the `bioconductor` input). The patch is read from the +# workflow's own commit, so a `ref` that lacks it still gets it. Moving the +# pin means re-cutting the patch; handbook/testing/revdep/ says what a run +# covers. +# # Results land in artifacts: # revdepx-report the merged report -- fetch with # `gh run download --name revdepx-report` @@ -100,6 +108,14 @@ on: - strong - most default: strong + bioconductor: + description: "Bioconductor reverse dependencies to check beside CRAN's: all (at every level, like CRAN's), direct (level 1 only) or none" + type: choice + options: + - all + - direct + - none + default: all depth: description: "Levels of reverse dependencies: 1 = direct, 2 = also revdeps of revdeps, ..., 'all' = the full transitive closure" type: string @@ -176,6 +192,7 @@ env: REVDEPX_PACKAGES: ${{ inputs.packages || '' }} REVDEPX_WHICH: ${{ inputs.which || 'strong' }} REVDEPX_DEPTH: ${{ inputs.depth || '1' }} + REVDEPX_BIOC: ${{ inputs.bioconductor || 'all' }} REVDEPX_RETRY_RUN: ${{ inputs.retry-run || '' }} REVDEPX_PART: ${{ inputs.part || '' }} REVDEPX_R_VERSION_INPUT: ${{ inputs.r-version || '4.6.1' }} @@ -254,20 +271,27 @@ jobs: steps: # The scripts are served with the kit's actions, not copied into the repository. - name: Locate the revdep scripts - uses: cynkra/cynkratemplate/.github/actions/revdep-scripts@main + uses: cynkra/cynkratemplate/.github/actions/revdep-scripts@e45daecc565a10e0624769f868a680e147d2920c - name: Check out the ref under test uses: actions/checkout@v6 with: ref: ${{ inputs.ref || github.ref }} + - name: Patch the revdep scripts for Bioconductor + run: | + git fetch --quiet --depth 1 origin "${GITHUB_SHA}" + git show FETCH_HEAD:.github/revdepx/bioconductor.patch | + patch --batch --forward -p4 -d "${REVDEPX_DIR}" + shell: bash + # The same resolution runs in the base and build jobs: the three run in # parallel and each needs the answer before any of them could publish # it. It is one cached HTTPS call; determinism within a run is what # matters, and the alias moves at most once every few months. - name: Resolve the R version id: resolve - uses: cynkra/cynkratemplate/.github/actions/revdepx-resolve-r-version@main + uses: cynkra/cynkratemplate/.github/actions/revdepx-resolve-r-version@e45daecc565a10e0624769f868a680e147d2920c with: input: ${{ env.REVDEPX_R_VERSION_INPUT }} @@ -322,7 +346,7 @@ jobs: steps: # The scripts are served with the kit's actions, not copied into the repository. - name: Locate the revdep scripts - uses: cynkra/cynkratemplate/.github/actions/revdep-scripts@main + uses: cynkra/cynkratemplate/.github/actions/revdep-scripts@e45daecc565a10e0624769f868a680e147d2920c - name: Check out the ref under test uses: actions/checkout@v6 @@ -331,7 +355,7 @@ jobs: # Same resolution as in the plan job; see the comment there. - name: Resolve the R version - uses: cynkra/cynkratemplate/.github/actions/revdepx-resolve-r-version@main + uses: cynkra/cynkratemplate/.github/actions/revdepx-resolve-r-version@e45daecc565a10e0624769f868a680e147d2920c with: input: ${{ env.REVDEPX_R_VERSION_INPUT }} @@ -400,7 +424,7 @@ jobs: # container's R (the pinned oldrel), and a binary built by the runner's # newer R would not. - name: Build the binary in the base container - uses: cynkra/cynkratemplate/.github/actions/revdepx-build-binary@main + uses: cynkra/cynkratemplate/.github/actions/revdepx-build-binary@e45daecc565a10e0624769f868a680e147d2920c with: base-image: ${{ needs.base.outputs.image }} - name: Upload the package binary @@ -445,7 +469,7 @@ jobs: steps: # The scripts are served with the kit's actions, not copied into the repository. - name: Locate the revdep scripts - uses: cynkra/cynkratemplate/.github/actions/revdep-scripts@main + uses: cynkra/cynkratemplate/.github/actions/revdep-scripts@e45daecc565a10e0624769f868a680e147d2920c - name: Check out the ref under test uses: actions/checkout@v6 @@ -474,7 +498,7 @@ jobs: # this job lands in one accounted place. The commit step below # measures against that one disk before copying anything large. - name: Make room for docker on the one disk - uses: cynkra/cynkratemplate/.github/actions/revdepx-docker-room@main + uses: cynkra/cynkratemplate/.github/actions/revdepx-docker-room@e45daecc565a10e0624769f868a680e147d2920c # The universe build downloads every dependency once; saving the pak # cache under the plan's hash hands the next build a warm start even # when the image itself cannot be reused. @@ -499,7 +523,7 @@ jobs: - name: Build the universe image id: build-universe - uses: cynkra/cynkratemplate/.github/actions/revdepx-build-universe@main + uses: cynkra/cynkratemplate/.github/actions/revdepx-build-universe@e45daecc565a10e0624769f868a680e147d2920c with: base-image: ${{ needs.base.outputs.image }} # Everything here is written and UPLOADED before the commit below @@ -510,7 +534,7 @@ jobs: # starts, or they do not exist. - name: Record the state before the commit id: precommit - uses: cynkra/cynkratemplate/.github/actions/revdepx-record-precommit@main + uses: cynkra/cynkratemplate/.github/actions/revdepx-record-precommit@e45daecc565a10e0624769f868a680e147d2920c - name: Publish the pre-commit state (survives a dying runner) uses: actions/upload-artifact@v6 with: @@ -533,7 +557,7 @@ jobs: # already takes. - name: Commit and push the universe image id: publish - uses: cynkra/cynkratemplate/.github/actions/revdepx-publish-universe@main + uses: cynkra/cynkratemplate/.github/actions/revdepx-publish-universe@e45daecc565a10e0624769f868a680e147d2920c - name: Upload the universe image artifact (registry fallback only) uses: actions/upload-artifact@v6 if: steps.publish.outputs.saved == 'true' @@ -641,13 +665,20 @@ jobs: steps: # The scripts are served with the kit's actions, not copied into the repository. - name: Locate the revdep scripts - uses: cynkra/cynkratemplate/.github/actions/revdep-scripts@main + uses: cynkra/cynkratemplate/.github/actions/revdep-scripts@e45daecc565a10e0624769f868a680e147d2920c - name: Check out the ref under test uses: actions/checkout@v6 with: ref: ${{ inputs.ref || github.ref }} + - name: Patch the revdep scripts for Bioconductor + run: | + git fetch --quiet --depth 1 origin "${GITHUB_SHA}" + git show FETCH_HEAD:.github/revdepx/bioconductor.patch | + patch --batch --forward -p4 -d "${REVDEPX_DIR}" + shell: bash + # The host runs only the driver: R with jsonlite and rcmdcheck to # parse, compare and account. Everything a check needs -- TeX, pandoc, # qpdf, the dependency library -- lives in the image. This is the whole @@ -709,7 +740,7 @@ jobs: # and put that data-root under RUNNER_TEMP beside the checks' work # directories, so everything heavy is in one accounted place. - name: Make room for docker on the one disk - uses: cynkra/cynkratemplate/.github/actions/revdepx-docker-room@main + uses: cynkra/cynkratemplate/.github/actions/revdepx-docker-room@e45daecc565a10e0624769f868a680e147d2920c # Both driver phases need these and neither can name them itself: the # `runner` context does not reach the job's `env`, so they are set once @@ -719,7 +750,7 @@ jobs: # `NOT_CRAN=true` into `$GITHUB_ENV`, and a later write is what # reliably overrides an earlier one. - name: Point the driver at this runner's directories - uses: cynkra/cynkratemplate/.github/actions/revdepx-driver-env@main + uses: cynkra/cynkratemplate/.github/actions/revdepx-driver-env@e45daecc565a10e0624769f868a680e147d2920c with: not-cran-input: ${{ env.REVDEPX_NOT_CRAN }} # Emit machine state while the work runs: a reclaimed runner never @@ -739,7 +770,7 @@ jobs: # universe job died -- build a shard-local image from the base), and # extract its library index for the driver's dependency screen. - name: Prepare the check image - uses: cynkra/cynkratemplate/.github/actions/revdepx-check-image@main + uses: cynkra/cynkratemplate/.github/actions/revdepx-check-image@e45daecc565a10e0624769f868a680e147d2920c with: universe-image: ${{ needs.universe.outputs.image }} revdepx-base-image: ${{ needs.base.outputs.image }} @@ -875,13 +906,20 @@ jobs: steps: # The scripts are served with the kit's actions, not copied into the repository. - name: Locate the revdep scripts - uses: cynkra/cynkratemplate/.github/actions/revdep-scripts@main + uses: cynkra/cynkratemplate/.github/actions/revdep-scripts@e45daecc565a10e0624769f868a680e147d2920c - name: Check out the ref under test uses: actions/checkout@v6 with: ref: ${{ inputs.ref || github.ref }} + - name: Patch the revdep scripts for Bioconductor + run: | + git fetch --quiet --depth 1 origin "${GITHUB_SHA}" + git show FETCH_HEAD:.github/revdepx/bioconductor.patch | + patch --batch --forward -p4 -d "${REVDEPX_DIR}" + shell: bash + - name: Set up R uses: r-lib/actions/setup-r@v2 with: @@ -969,7 +1007,7 @@ jobs: && vars.REVDEPX_COMMIT_REPORT != 'false' && steps.collect.outputs.compared != '0' continue-on-error: true - uses: cynkra/cynkratemplate/.github/actions/revdep-commit-report@main + uses: cynkra/cynkratemplate/.github/actions/revdep-commit-report@e45daecc565a10e0624769f868a680e147d2920c with: ref: ${{ inputs.ref }} workflow: revdep4 From 65912b467b0438c1a99253991e641358968d7a17 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 18 Oct 2026 12:50:06 +0000 Subject: [PATCH 2/2] docs: Say what a reverse-dependency run covers Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015o4YxHfzPsnTZ9fUBQABDV --- handbook/testing/revdep/README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/handbook/testing/revdep/README.md b/handbook/testing/revdep/README.md index ace01a72e22..44dd1edee92 100644 --- a/handbook/testing/revdep/README.md +++ b/handbook/testing/revdep/README.md @@ -2,4 +2,17 @@ Checking the packages that depend on igraph against a change, before a release. +## What a run covers + +* CRAN and Bioconductor packages alike, at every level up to `depth`. + The `bioconductor` input of `revdep4` narrows that to `direct` (Bioconductor at level 1 only) or `none` (CRAN only), + and `packages:` accepts a Bioconductor name too. +* Bioconductor packages come from the release that R's version maps to, as do the Bioconductor dependencies of every package checked. + Bioconductor devel is not checked. +* Report links for a Bioconductor package point at its bioconductor.org page. +* The planner and shard scripts come from [cynkra/cynkratemplate](https://github.com/cynkra/cynkratemplate)'s `.github/actions/revdepx/`, + pinned to one commit in `.github/workflows/revdep4.yaml`, + and `.github/revdepx/bioconductor.patch` adds Bioconductor to them, so moving the pin means re-cutting the patch. + `revdep` and `revdep2` check CRAN packages only. + *To deepen: absorb `revdep/README.md`, and say what the `revdep`, `revdep2` and `revdep4` workflows each run.*