Skip to content

Commit b2529b9

Browse files
assess_reverse_dependencies(): don't depend on devtools::revdep()
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.
1 parent fd07e38 commit b2529b9

4 files changed

Lines changed: 163 additions & 7 deletions

File tree

NAMESPACE

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,6 @@ importFrom(covr,tally_coverage)
132132
importFrom(cranlogs,cran_downloads)
133133
importFrom(curl,nslookup)
134134
importFrom(devtools,check)
135-
importFrom(devtools,revdep)
136135
importFrom(httr,GET)
137136
importFrom(httr,content)
138137
importFrom(memoise,memoise)

NEWS.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,29 @@
11
# riskmetric (development version)
22

3+
- `assess_reverse_dependencies.default()` no longer calls
4+
`devtools::revdep(bioconductor = TRUE)` internally. `devtools`'s
5+
reverse-dependency helper unconditionally reads a `VIEWS` file
6+
from `BiocManager::repositories()[["BioCsoft"]]`, which fails in
7+
air-gapped environments — for example, an internal Posit Package
8+
Manager mirror that serves `<repo>/src/contrib/PACKAGES` but no
9+
`<repo>/VIEWS`. That failure was silently converting the
10+
reverse-dependencies metric to a `pkg_metric_error` for every
11+
package in the run, and downstream tooling (`riskreports`) then
12+
rendered it as `"unknown"`.
13+
- The default method now computes the reverse-dependency list
14+
from `utils::available.packages()` + `tools::dependsOnPkgs()`.
15+
It accepts three new arguments to make the behaviour explicit
16+
and configurable: `repos` (defaults to `getOption("repos")`),
17+
`dependencies` (defaults to
18+
`c("Depends", "Imports", "LinkingTo", "Suggests")` to match the
19+
previous `devtools::revdep()` behaviour), and `available` (an
20+
optional pre-computed `available.packages()` matrix to avoid a
21+
repeated network fetch across many packages in the same
22+
session). Public-network callers get the same result as before;
23+
air-gapped callers can point `options(repos = ...)` at their
24+
internal mirror once and expect the metric to populate
25+
correctly.
26+
327
# riskmetric 0.2.6
428

529
- Update to address new failing tests responding to `devtools` v2.4.7 changes.

R/assess_reverse_dependencies.R

Lines changed: 58 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,74 @@
11
#' Generate list of Reverse Dependencies for a package
22
#'
33
#' @details The more packages that depend on a package the more chance
4-
#' for errors/bugs to be found
4+
#' for errors/bugs to be found.
5+
#'
6+
#' The default method computes the reverse-dependency list from an
7+
#' available-packages index (typically the CRAN and/or Bioconductor
8+
#' \code{PACKAGES} files reachable via \code{getOption("repos")}). This
9+
#' avoids the historical dependency on \code{devtools::revdep(bioconductor = TRUE)},
10+
#' which unconditionally reads a \code{VIEWS} file from
11+
#' \code{BiocManager::repositories()[["BioCsoft"]]}. That read fails in
12+
#' air-gapped environments (for example, an internal Posit Package
13+
#' Manager mirror that serves \code{<repo>/src/contrib/PACKAGES} but no
14+
#' \code{<repo>/VIEWS}), which caused the reverse-dependencies metric to
15+
#' silently degrade to a \code{pkg_metric_error} for every package in
16+
#' the run.
17+
#'
18+
#' Callers on the public network get the same result as before — the
19+
#' default \code{repos} value picks up whatever CRAN / Bioconductor
20+
#' mirrors are already configured — while callers on internal mirrors
21+
#' can point \code{repos} at those mirrors (or set
22+
#' \code{options(repos = ...)} once at the top of the session) and
23+
#' expect the metric to populate correctly.
524
#'
625
#' @eval roxygen_assess_family(
726
#' "reverse_dependencies",
827
#' "A character vector of reverse dependencies")
928
#'
29+
#' @param repos Character vector of repository URLs to consult when
30+
#' building the available-packages index. Defaults to
31+
#' \code{getOption("repos")}. Ignored when \code{available} is
32+
#' supplied.
33+
#' @param dependencies Character vector of dependency types to consider
34+
#' when computing reverse dependencies. Defaults to
35+
#' \code{c("Depends", "Imports", "LinkingTo", "Suggests")} to match
36+
#' the previous \code{devtools::revdep()} behaviour.
37+
#' @param available Optional pre-computed matrix returned by
38+
#' \code{\link[utils]{available.packages}}. Supplying it avoids a
39+
#' repeated network fetch when the metric is computed for many
40+
#' packages in the same session.
41+
#'
1042
#' @export
11-
assess_reverse_dependencies <- function(x, ...){
43+
assess_reverse_dependencies <- function(x, ...) {
1244
UseMethod("assess_reverse_dependencies")
1345
}
1446

15-
#' @importFrom devtools revdep
47+
#' @rdname assess_reverse_dependencies
1648
#' @export
17-
assess_reverse_dependencies.default <- function(x, ...){
18-
pkg_metric_eval(class = "pkg_metric_reverse_dependencies",
19-
devtools::revdep(x$name, bioconductor = TRUE)
49+
assess_reverse_dependencies.default <- function(
50+
x,
51+
...,
52+
repos = getOption("repos"),
53+
dependencies = c("Depends", "Imports", "LinkingTo", "Suggests"),
54+
available = NULL
55+
) {
56+
pkg_metric_eval(
57+
class = "pkg_metric_reverse_dependencies",
58+
{
59+
ap <- if (is.null(available)) {
60+
utils::available.packages(repos = repos)
61+
} else {
62+
available
63+
}
64+
revs <- tools::dependsOnPkgs(
65+
x$name,
66+
dependencies = dependencies,
67+
recursive = FALSE,
68+
installed = ap
69+
)
70+
sort(unique(as.character(revs)))
71+
}
2072
)
2173
}
2274

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
test_that("assess_reverse_dependencies.default computes revdeps from available.packages()", {
2+
# Build a fake CRAN-style available-packages matrix with three reverse
3+
# dependents of the target package "target" (via Depends, Imports, and
4+
# LinkingTo), plus one unrelated package.
5+
fake_available <- matrix(
6+
c(
7+
"revA", "1.0", "target", "", "", "",
8+
"revB", "2.0", "", "target", "", "",
9+
"revC", "3.0", "", "", "target", "",
10+
"unrelated","0.1", "utils", "", "", ""
11+
),
12+
ncol = 6,
13+
byrow = TRUE,
14+
dimnames = list(NULL, c("Package", "Version", "Depends",
15+
"Imports", "LinkingTo", "Suggests"))
16+
)
17+
# Package-level Repository column is required by tools::dependsOnPkgs
18+
fake_available <- cbind(
19+
fake_available,
20+
Repository = "https://example.test/src/contrib"
21+
)
22+
23+
x <- list(name = "target")
24+
res <- assess_reverse_dependencies.default(x, available = fake_available)
25+
26+
expect_s3_class(res, "pkg_metric_reverse_dependencies")
27+
expect_setequal(as.character(res), c("revA", "revB", "revC"))
28+
})
29+
30+
test_that("assess_reverse_dependencies.default respects the `dependencies` filter", {
31+
fake_available <- matrix(
32+
c(
33+
"revA", "1.0", "target", "", "", "",
34+
"revB", "2.0", "", "target", "", "",
35+
"revC", "3.0", "", "", "", "target"
36+
),
37+
ncol = 6,
38+
byrow = TRUE,
39+
dimnames = list(NULL, c("Package", "Version", "Depends",
40+
"Imports", "LinkingTo", "Suggests"))
41+
)
42+
fake_available <- cbind(
43+
fake_available,
44+
Repository = "https://example.test/src/contrib"
45+
)
46+
x <- list(name = "target")
47+
48+
res_all <- assess_reverse_dependencies.default(
49+
x,
50+
available = fake_available,
51+
dependencies = c("Depends", "Imports", "LinkingTo", "Suggests")
52+
)
53+
expect_setequal(as.character(res_all), c("revA", "revB", "revC"))
54+
55+
res_depends_only <- assess_reverse_dependencies.default(
56+
x,
57+
available = fake_available,
58+
dependencies = "Depends"
59+
)
60+
expect_setequal(as.character(res_depends_only), "revA")
61+
})
62+
63+
test_that("assess_reverse_dependencies.default returns an empty result cleanly", {
64+
fake_available <- matrix(
65+
c("unrelated", "0.1", "utils", "", "", ""),
66+
ncol = 6,
67+
byrow = TRUE,
68+
dimnames = list(NULL, c("Package", "Version", "Depends",
69+
"Imports", "LinkingTo", "Suggests"))
70+
)
71+
fake_available <- cbind(
72+
fake_available,
73+
Repository = "https://example.test/src/contrib"
74+
)
75+
76+
x <- list(name = "target")
77+
res <- assess_reverse_dependencies.default(x, available = fake_available)
78+
79+
expect_s3_class(res, "pkg_metric_reverse_dependencies")
80+
expect_length(as.character(res), 0L)
81+
})

0 commit comments

Comments
 (0)