Skip to content

assess_reverse_dependencies(): don't depend on devtools::revdep() - #401

Draft
aclark02-arcus wants to merge 3 commits into
pharmaR:masterfrom
aclark02-arcus:flexible-assess-reverse-dependencies
Draft

aclark02-arcus wants to merge 3 commits into
pharmaR:masterfrom
aclark02-arcus:flexible-assess-reverse-dependencies

Conversation

@aclark02-arcus

Copy link
Copy Markdown

Motivation

riskmetric::assess_reverse_dependencies.default() currently calls:

devtools::revdep(x$name, bioconductor = TRUE)

devtools::revdep in turn calls devtools:::bioc_packages() (source), which is memoised and unconditionally does:

views <- paste(BiocManager::repositories()[["BioCsoft"]], "VIEWS", sep = "/")
con   <- url(views)
read.dcf(con)

On an air-gapped host — e.g. an internal Posit Package Manager mirror that only serves <repo>/src/contrib/PACKAGES and does not expose a <repo>/VIEWS file at the mirror root — that read.dcf fails with:

cannot open the connection to '<mirror>/bioc-<snapshot>/latest/VIEWS'

assess_reverse_dependencies then wraps the error into a pkg_metric_error for every package in the run. Downstream tooling (riskreports) renders that as "unknown", so validation reports coming out of air-gapped environments silently drop reverse-dependency information for every package. This can be perceived as a bug because a fully-configured internal CRAN + BioC mirror is nonetheless unable to answer a question that only needs PACKAGES.

Change

Compute the reverse-dependency list from utils::available.packages() + tools::dependsOnPkgs() instead. This uses the caller's options("repos") (or an explicit repos = vector), which works cleanly against both public CRAN + Bioconductor and internal mirrors — no VIEWS file needed.

Three new arguments make the behaviour explicit and configurable:

  • repos — defaults to getOption("repos").
  • dependencies — defaults to c("Depends", "Imports", "LinkingTo", "Suggests") to match the previous devtools::revdep() behaviour.
  • available — an optional pre-computed available.packages() matrix, so callers that iterate over many packages in one session can avoid repeated network fetches.

Public-network callers get the same result as before (the default repos picks up whatever's already configured); air-gapped callers can point options(repos = ...) at their internal mirror once and expect the metric to populate correctly.

Compatibility

  • The generic assess_reverse_dependencies() and the return-type contract are unchanged — still a pkg_metric_reverse_dependencies object carrying a character vector of reverse-dependency package names.
  • devtools remains a dependency (still imported by pkg_ref_cache.r_cmd_check), so no DESCRIPTION change is needed.
  • importFrom(devtools, revdep) dropped from NAMESPACE — no longer referenced.
  • Roxygen chunk reworked so the new arguments show up in the man page (uses @rdname on the method).

Tests

Three new tests in tests/testthat/test_assess_reverse_dependencies.R cover:

  1. Default expansion. Given a fake available.packages() matrix with revdeps via Depends, Imports, and LinkingTo, the metric returns all three revdep package names.
  2. dependencies filter. Passing dependencies = "Depends" on a matrix whose revdeps live in Depends, Imports, and Suggests returns only the Depends revdep.
  3. Empty result path. No matches → the metric returns a zero-length character vector still classed pkg_metric_reverse_dependencies.

All three pass locally.

Notes

Opened as a draft for maintainer discussion. Two things I'd love feedback on:

  • The default for dependencies. c("Depends", "Imports", "LinkingTo", "Suggests") matches the old devtools::revdep() default. Reasonable to narrow to "strong" (drop Suggests) if there's a preference — that would change the numeric metric_score() for packages with many Suggests-only revdeps.
  • The available short-circuit is designed for the riskmetric::pkg_assess() bulk case. Happy to also wire it through all_assessments() / pkg_assess() so the available.packages() call is memoised across the whole run, if that's the preferred pattern here.

aclark02-arcus and others added 2 commits July 29, 2026 10:59
devtools::revdep(bioconductor = TRUE) — the previous
implementation of assess_reverse_dependencies.default() —
internally calls devtools:::bioc_packages(), which
unconditionally reads a VIEWS file from
BiocManager::repositories()[['BioCsoft']]. On air-gapped
mirrors (for example, an internal Posit Package Manager
snapshot that only serves <repo>/src/contrib/PACKAGES), that
read fails with:

  cannot open the connection to
  '<mirror>/bioc-<snapshot>/latest/VIEWS'

The failure was silently converting the reverse-dependencies
metric to a pkg_metric_error for every package in a run, which
downstream tooling (riskreports) rendered as 'unknown'.

Compute reverse dependencies from utils::available.packages() +
tools::dependsOnPkgs() instead. That uses whatever's on
options('repos'), which works cleanly against both public
CRAN/Bioconductor and internal mirrors — no VIEWS file needed.

Expose three new arguments to make the behaviour explicit and
configurable:

  * repos        — defaults to getOption('repos')
  * dependencies — defaults to c('Depends', 'Imports',
                                 'LinkingTo', 'Suggests') to
                   match the previous devtools::revdep()
                   behaviour
  * available    — optional pre-computed available.packages()
                   matrix for callers that want to avoid a
                   repeated network fetch across many packages

Ships with three tests exercising the default expansion, the
dependencies filter, and the empty-result path, plus a NEWS
entry under the development version.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR refactors assess_reverse_dependencies.default() to avoid relying on devtools::revdep(bioconductor = TRUE) (and its VIEWS fetch), computing reverse dependencies instead from repository PACKAGES metadata via utils::available.packages() + tools::dependsOnPkgs() to improve behavior on air-gapped/internal mirrors.

Changes:

  • Reimplemented assess_reverse_dependencies.default() to compute revdeps from an available-packages index, adding repos, dependencies, and available parameters.
  • Added testthat coverage for the new computation logic (using a supplied available matrix).
  • Updated NEWS.md and removed importFrom(devtools, revdep) from NAMESPACE.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

File Description
R/assess_reverse_dependencies.R Replaces devtools::revdep() with an available.packages()/dependsOnPkgs()-based implementation and documents new parameters.
tests/testthat/test_assess_reverse_dependencies.R Adds unit tests for the revdep computation (primarily via the available= short-circuit).
NEWS.md Documents the behavioral change and rationale (air-gapped mirror compatibility).
NAMESPACE Removes the unused devtools::revdep import.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +59 to +63
ap <- if (is.null(available)) {
utils::available.packages(repos = repos)
} else {
available
}
Comment on lines +1 to +28
test_that("assess_reverse_dependencies.default computes revdeps from available.packages()", {
# Build a fake CRAN-style available-packages matrix with three reverse
# dependents of the target package "target" (via Depends, Imports, and
# LinkingTo), plus one unrelated package.
fake_available <- matrix(
c(
"revA", "1.0", "target", "", "", "",
"revB", "2.0", "", "target", "", "",
"revC", "3.0", "", "", "target", "",
"unrelated","0.1", "utils", "", "", ""
),
ncol = 6,
byrow = TRUE,
dimnames = list(NULL, c("Package", "Version", "Depends",
"Imports", "LinkingTo", "Suggests"))
)
# Package-level Repository column is required by tools::dependsOnPkgs
fake_available <- cbind(
fake_available,
Repository = "https://example.test/src/contrib"
)

x <- list(name = "target")
res <- assess_reverse_dependencies.default(x, available = fake_available)

expect_s3_class(res, "pkg_metric_reverse_dependencies")
expect_setequal(as.character(res), c("revA", "revB", "revC"))
})
Comment thread NEWS.md Resolved
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants