Skip to content

subscript out of bounds when repos include an R-universe repository #106

Description

@ddsjoberg

Summary

When repos passed to plan_rev_dep_checks() / checker$new() includes an
R-universe repository, the check run fails during package source resolution with:

Error in db[package, ] : subscript out of bounds
Calls: <Anonymous> ... check_path -> check_path.pkg_origin_repo -> get_package_source

The root cause is that strip_src_contrib() compares a package's Repository
field to contrib.url(repo) with ==, but R-universe reports Repository as a
full, per-package tarball URL rather than a plain src/contrib path. The
equality check never matches, strip_src_contrib() returns character(0), and
get_package_source() then indexes an empty available.packages() database.

Tested with checked 0.5.4.

Reproducible example

repos <- c(
  "https://ddsjoberg.r-universe.dev",  # any R-universe repo
  "https://cloud.r-project.org"
)

ap <- available.packages(repos = repos)

# R-universe reports the Repository field as a full tarball URL, not a
# contrib.url:
ap["gtsummary", "Repository"]
#> "https://ddsjoberg.r-universe.dev/src/contrib/gtsummary_2.5.1.9015.tar.gz?sha256=962c...&file="

contrib.url("https://ddsjoberg.r-universe.dev")
#> "https://ddsjoberg.r-universe.dev/src/contrib"

# checked:::strip_src_contrib() matches Repository against contrib.url() with `==`:
checked:::strip_src_contrib(ap["gtsummary", "Repository"], repos = repos)
#> character(0)      # <- no match, returns empty

Because the stripped repo is character(0), pkg_origin_repo() stores an empty
repos, and at check time get_package_source() does:

db <- available_packages(repos = character(0))  # does not contain the package
pkg <- db[package, ]                             # subscript out of bounds

In a full run this surfaces as (checking cards' reverse dependencies against
the ddsjoberg R-universe):

checked::check_rev_deps(
  path = ".",
  repos = c("https://ddsjoberg.r-universe.dev", "https://cloud.r-project.org")
)
#> Error in db[package, ] : subscript out of bounds

Root cause

For a CRAN-style repo, Repository is the contrib path, so
contrib.url(repo) == Repository holds:

Repository:      https://cloud.r-project.org/src/contrib
contrib.url():   https://cloud.r-project.org/src/contrib   # equal

For R-universe, Repository is the concrete tarball URL for that package/version:

Repository:      https://<user>.r-universe.dev/src/contrib/<pkg>_<ver>.tar.gz?sha256=...&file=
contrib.url():   https://<user>.r-universe.dev/src/contrib  # not equal

strip_src_contrib() (R/utils-pkg-source.R) therefore returns nothing:

strip_src_contrib <- function(x, repos) {
  match <- vlapply(repos, function(r) {
    utils::contrib.url(r) == x        # exact-equality fails for R-universe
  })
  repos[match]
}

which propagates through pkg_origin_repo() (R/pkg_origin.R) into
get_package_source() (R/utils-pkg-source.R):

get_package_source <- function(package, repos, db = NULL, destdir = NULL) {
  if (is.null(db)) {
    db <- available_packages(repos = repos)  # repos == character(0)
  }
  pkg <- db[package, ]                        # subscript out of bounds
  ...
}

Suggested fix

Match by prefix rather than equality, so a per-package tarball URL still maps
back to its originating repo:

strip_src_contrib <- function(x, repos) {
  match <- vlapply(repos, function(r) {
    startsWith(x, utils::contrib.url(r, type = "source"))
  })
  repos[match]
}

It may also be worth having get_package_source() build the archive URL
directly from the Repository field when it is already a full tarball URL
(as R-universe provides), or resolve the source via utils::download.packages(),
which handles R-universe Repository URLs natively.

Workaround

For anyone hitting this before a fix lands, the two internals can be patched in
place at the top of a check script:

loadNamespace("checked")

assignInNamespace("strip_src_contrib", function(x, repos) {
  repos[vapply(repos, function(r) startsWith(x, utils::contrib.url(r, type = "source")), logical(1))]
}, ns = "checked")

assignInNamespace("get_package_source", function(package, repos, db = NULL, destdir = NULL) {
  if (is.null(db)) db <- utils::available.packages(repos = repos)
  if (is.null(destdir)) {
    pkg <- db[package, ]
    return(sprintf("%s/%s_%s.tar.gz", pkg["Repository"], pkg["Package"], pkg["Version"]))
  }
  res <- utils::download.packages(package, destdir = destdir, available = db, repos = repos, type = "source")
  unname(res[1L, 2L])
}, ns = "checked")

Session info

  • checked 0.5.4
  • Reproducible with any R-universe repo in repos (e.g. https://<user>.r-universe.dev)

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions