From 6d202368b8d66068975332cebc068e5c5c36aca0 Mon Sep 17 00:00:00 2001 From: maksymis <32574056+maksymiuks@users.noreply.github.com> Date: Fri, 10 Jul 2026 15:58:08 +0200 Subject: [PATCH 1/2] Add option to specify options inherited by subprocesses --- R/install.R | 14 ++++++-------- R/options.R | 11 ++++++++++- cran-comments.md | 5 +++-- man/options.Rd | 8 ++++++++ man/options_params.Rd | 3 +++ 5 files changed, 30 insertions(+), 11 deletions(-) diff --git a/R/install.R b/R/install.R index 93adbae..2f697af 100644 --- a/R/install.R +++ b/R/install.R @@ -11,19 +11,15 @@ install_process <- R6::R6Class( ..., lib = .libPaths()[[1]], libpaths = .libPaths(), - available_packages_filters = getOption("available_packages_filters"), log = NULL, - env = callr::rcmd_safe_env() + env = options::opt("install_envvars") ) { if (!dir.exists(lib)) dir.create(lib, recursive = TRUE) private$package <- pkgs self$log <- log private$callr_r_bg( - function(..., available_packages_filters) { - options( - timeout = 600, - available_packages_filters = available_packages_filters - ) + function(..., opts_to_inherit) { + do.call(options, opts_to_inherit) invisible(capture.output(withCallingHandlers( utils::install.packages(..., quiet = FALSE, verbose = TRUE), warning = function(w) { @@ -35,7 +31,9 @@ install_process <- R6::R6Class( private$package, ..., lib = lib, - available_packages_filters = available_packages_filters + opts_to_inherit = do.call( + options, options::opt("install_opts_to_inherit") + ) ), libpath = libpaths, stdout = self$log, diff --git a/R/options.R b/R/options.R index a81f764..4ab78fd 100644 --- a/R/options.R +++ b/R/options.R @@ -79,7 +79,16 @@ options::define_options( "value used as `user_profile` parameter passed to the `callr::r_bg()` function used to install packages", - install_user_profile = "project" + install_user_profile = "project", + + "`list` of characters which specifies names of the R session options that + should be inherited by the install subprocesses, from the main process ", + install_opts_to_inherit = list( + "timeout", + "available_packages_filters", + "HTTPUserAgent", + "pkgType" + ) ) #' @eval options::as_roxygen_docs() diff --git a/cran-comments.md b/cran-comments.md index 8056835..a9397a7 100644 --- a/cran-comments.md +++ b/cran-comments.md @@ -1,2 +1,3 @@ -Few minor updates to to exported functions and CRAN results failure address. -Reduced the runtime of tests on CRAN \ No newline at end of file +Updates to the logic +Mitigate the risk of an infinite loop +Address callr changes \ No newline at end of file diff --git a/man/options.Rd b/man/options.Rd index 71c9de2..34780cc 100644 --- a/man/options.Rd +++ b/man/options.Rd @@ -120,6 +120,14 @@ function used to install packages\item{default: }{\preformatted{"project"}} \item{envvar: }{R_CHECKED_INSTALL_USER_PROFILE (evaluated if possible, raw string otherwise)} }} +\item{install_opts_to_inherit}{\describe{ +\code{list} of characters which specifies names of the R session options that +should be inherited by the install subprocesses, from the main process\item{default: }{\preformatted{list("timeout", "available_packages_filters", "HTTPUserAgent", + "pkgType")}} +\item{option: }{checked.install_opts_to_inherit} +\item{envvar: }{R_CHECKED_INSTALL_OPTS_TO_INHERIT (evaluated if possible, raw string otherwise)} +}} + } } diff --git a/man/options_params.Rd b/man/options_params.Rd index 4402e65..963eb30 100644 --- a/man/options_params.Rd +++ b/man/options_params.Rd @@ -36,6 +36,9 @@ constrocuting a plan \code{task_grap} (Defaults to \code{TRUE}, overwritable usi \item{check_envvars}{named \code{character} vector of environment variables to use during the R CMD check. (Defaults to \verb{c(}\emph{R_CHECK_FORCE_SUGGESTS}\verb{= "false",}\emph{R_CHECK_RD_XREFS}\verb{= "false", ; }\emph{R_CHECK_SYSTEM_CLOCK}\verb{= "false",}\emph{R_CHECK_SUGGESTS_ONLY}\verb{= "true", ; }\emph{R_CHECK_CRAN_INCOMING}\verb{ = "false")}, overwritable using option 'checked.check_envvars' or environment variable 'R_CHECKED_CHECK_ENVVARS')} +\item{install_opts_to_inherit}{\code{list} of characters which specifies names of the R session options that +should be inherited by the install subprocesses, from the main process (Defaults to \verb{list("timeout", "available_packages_filters", "HTTPUserAgent", ; "pkgType")}, overwritable using option 'checked.install_opts_to_inherit' or environment variable 'R_CHECKED_INSTALL_OPTS_TO_INHERIT')} + \item{tty_tick_interval}{tty refresh interval when reporting results in milliseconds (Defaults to \code{0.1}, overwritable using option 'checked.tty_tick_interval' or environment variable 'R_CHECKED_TTY_TICK_INTERVAL')} \item{check_build_args}{\code{character} vector of args passed to the R CMD build. (Defaults to \code{c("--no-build-vignettes", "--no-manual")}, overwritable using option 'checked.check_build_args' or environment variable 'R_CHECKED_CHECK_BUILD_ARGS')} From bade6eee25983ea6ce839a5d80f4e5be40eaa02d Mon Sep 17 00:00:00 2001 From: Daniel Sjoberg Date: Tue, 1 Sep 2026 11:16:41 -0700 Subject: [PATCH 2/2] Match repositories by prefix in strip_src_contrib() R-universe repositories report the `Repository` field of `available.packages()` as a full, per-package tarball url (`.../src/contrib/_.tar.gz?sha256=...&file=`) rather than the plain contrib url that CRAN-like repositories report. Comparing that field to `contrib.url(repo)` with `==` therefore never matched, so `strip_src_contrib()` returned `character(0)`, `pkg_origin_repo()` stored an empty `repos`, and `get_package_source()` failed at check time with `subscript out of bounds`. Match by prefix instead, so a per-package tarball url still resolves back to its originating repository. `contrib.url()` is left at its default `type`, matching the type `available_packages()` uses to produce the `Repository` field, so binary repositories keep working. No change is needed in `get_package_source()`: R-universe crafts the `Repository` field so that appending `/_.tar.gz` (what `utils::download.packages()` does, and what `get_package_source()` already did) yields a valid download url. Fixes #106 Co-Authored-By: Claude Opus 5 --- NEWS.md | 6 ++ R/utils-pkg-source.R | 7 ++- tests/testthat/test-pkg-source.R | 96 ++++++++++++++++++++++++++++++++ 3 files changed, 108 insertions(+), 1 deletion(-) create mode 100644 tests/testthat/test-pkg-source.R diff --git a/NEWS.md b/NEWS.md index dfe7132..7776d19 100644 --- a/NEWS.md +++ b/NEWS.md @@ -3,6 +3,12 @@ * Add 'install_opts_to_inherit' option which can be used to specify parameters that install subprocess should inherit from the main process +* Fix `subscript out of bounds` error when `repos` includes an R-universe + repository. Such repositories report the `Repository` field as a full + per-package tarball url rather than a plain `src/contrib` path, which + prevented packages from being traced back to their originating repository. + (@ddsjoberg, #106) + # checked 0.5.4 * Improve error messaging when using basic_tty diff --git a/R/utils-pkg-source.R b/R/utils-pkg-source.R index 67f8e80..8fa6536 100644 --- a/R/utils-pkg-source.R +++ b/R/utils-pkg-source.R @@ -1,6 +1,11 @@ strip_src_contrib <- function(x, repos) { + # Repositories are matched by prefix rather than equality. For CRAN-like + # repositories the `Repository` field is exactly the contrib url, but some + # repositories (notably R-universe) report it as the full, per-package + # tarball url, e.g. + # `https://.r-universe.dev/src/contrib/_.tar.gz?sha256=...&file=` match <- vlapply(repos, function(r) { - utils::contrib.url(r) == x + startsWith(x, utils::contrib.url(r)) }) repos[match] } diff --git a/tests/testthat/test-pkg-source.R b/tests/testthat/test-pkg-source.R new file mode 100644 index 0000000..456da0f --- /dev/null +++ b/tests/testthat/test-pkg-source.R @@ -0,0 +1,96 @@ +test_that("strip_src_contrib identifies the originating repository", { + repos <- c( + "https://ddsjoberg.r-universe.dev", + "https://cloud.r-project.org" + ) + + # CRAN-like repositories report `Repository` as the contrib url itself + expect_identical( + strip_src_contrib( + utils::contrib.url("https://cloud.r-project.org"), + repos = repos + ), + "https://cloud.r-project.org" + ) + + # R-universe reports `Repository` as the full, per-package tarball url + runiverse_repository <- paste0( + utils::contrib.url("https://ddsjoberg.r-universe.dev"), + "/gtsummary_2.5.1.9015.tar.gz?sha256=962c&file=" + ) + + expect_identical( + strip_src_contrib(runiverse_repository, repos = repos), + "https://ddsjoberg.r-universe.dev" + ) +}) + +test_that("strip_src_contrib returns nothing for unknown repositories", { + expect_identical( + strip_src_contrib( + utils::contrib.url("https://example.com/other"), + repos = "https://cloud.r-project.org" + ), + character(0L) + ) +}) + +test_that("get_package_source builds source archive urls", { + db <- matrix( + c("gtsummary", "2.5.1.9015", NA_character_), + nrow = 1L, + dimnames = list( + "gtsummary", + c("Package", "Version", "Repository") + ) + ) + + db[, "Repository"] <- utils::contrib.url("https://cloud.r-project.org") + expect_identical( + get_package_source("gtsummary", repos = NULL, db = db), + paste0( + utils::contrib.url("https://cloud.r-project.org"), + "/gtsummary_2.5.1.9015.tar.gz" + ) + ) + + # R-universe `Repository` fields are crafted so that appending the package + # file name (as `utils::download.packages()` does) yields a valid url + db[, "Repository"] <- paste0( + utils::contrib.url("https://ddsjoberg.r-universe.dev"), + "/gtsummary_2.5.1.9015.tar.gz?sha256=962c&file=" + ) + expect_identical( + get_package_source("gtsummary", repos = NULL, db = db), + paste0( + utils::contrib.url("https://ddsjoberg.r-universe.dev"), + "/gtsummary_2.5.1.9015.tar.gz?sha256=962c", + "&file=/gtsummary_2.5.1.9015.tar.gz" + ) + ) +}) + +test_that("pkg_origin_repo resolves packages from an R-universe repository", { + skip_on_cran() + + repos <- c( + "https://ddsjoberg.r-universe.dev", + "https://cloud.r-project.org" + ) + + db <- tryCatch( + available_packages(repos = repos), + warning = function(w) skip("repositories are not reachable") + ) + skip_if_not("gtsummary" %in% rownames(db)) + + origin <- pkg_origin_repo("gtsummary", repos = repos) + expect_identical( + unname(origin$repos), + "https://ddsjoberg.r-universe.dev" + ) + expect_match( + check_path(origin, output = NULL), + "^https://ddsjoberg\\.r-universe\\.dev/.+\\.tar\\.gz" + ) +})