From 2ecc9f75cb1f85ddf93281fe057445215c67fbb0 Mon Sep 17 00:00:00 2001 From: maksymis <32574056+maksymiuks@users.noreply.github.com> Date: Mon, 27 Apr 2026 21:23:04 +0200 Subject: [PATCH 1/6] Update pacage installation process --- R/checker.R | 5 +++- R/pkg_origin.R | 2 +- R/plan.R | 28 ++++++++++++++------ R/remotes.R | 26 +++++++++++------- R/task_graph.R | 54 ++++++++++++++++++++++++++------------ R/utils-deps.R | 15 +++++------ man/checker.Rd | 4 +++ man/plan_local_checks.Rd | 10 ++++++- man/plan_local_install.Rd | 10 ++++++- man/plan_rev_dep_checks.Rd | 10 ++++++- man/task_graph.Rd | 5 +++- 11 files changed, 121 insertions(+), 48 deletions(-) diff --git a/R/checker.R b/R/checker.R index 16adfb5..2dcac34 100644 --- a/R/checker.R +++ b/R/checker.R @@ -82,6 +82,8 @@ checker <- R6::R6Class( #' @param restore `logical` value, whether output directory should be #' unlinked before running checks. If `FALSE`, an attempt will me made to #' restore previous progress from the same `output`. + #' @param dependencies A vector of length one or a named list. + #' Compatible with [`as_pkg_dependencies`]. #' @param ... Additional arguments unused #' #' @return [checker]. @@ -95,6 +97,7 @@ checker <- R6::R6Class( lib.loc = .libPaths(), repos = getOption("repos"), restore = options::opt("restore"), + dependencies = TRUE, ... ) { check_past_output(output, restore, ask = interactive()) @@ -111,7 +114,7 @@ checker <- R6::R6Class( ) private$repos <- repos - self$graph <- task_graph(self$plan, repos) + self$graph <- task_graph(self$plan, repos, dependencies = dependencies) private$restore_complete_checks() }, diff --git a/R/pkg_origin.R b/R/pkg_origin.R index 40ebedd..8110742 100644 --- a/R/pkg_origin.R +++ b/R/pkg_origin.R @@ -214,7 +214,7 @@ pkg_deps.pkg_origin_local <- function( indirect_deps <- pkg_dependencies( packages = direct_deps$name, - dependencies = dependencies, + dependencies = "hard", db = db ) indirect_deps$depth <- rep.int("indirect", NROW(indirect_deps)) diff --git a/R/plan.R b/R/plan.R index eda9c32..44839e4 100644 --- a/R/plan.R +++ b/R/plan.R @@ -7,12 +7,16 @@ #' #' @param path path to the package source. #' @param repos repository used to identify reverse dependencies. +#' @param remotes_dependencies A vector of length one or a named list. +#' Compatible with [`as_pkg_dependencies`]. Used to filter out remotes +#' dependencies. #' #' @family plan #' @export plan_rev_dep_checks <- function( path, - repos = getOption("repos") + repos = getOption("repos"), + remotes_dependencies = TRUE ) { path <- check_path_is_pkg_source(path) ap <- available_packages(repos = repos) @@ -75,7 +79,7 @@ plan_rev_dep_checks <- function( g <- task_graph_class(g) if (remotes_permitted()) { - remotes_graph(g) + remotes_graph(g, dependencies = remotes_dependencies) } else { g } @@ -131,6 +135,9 @@ plan_rev_dep_release_check <- function(origin, revdep, repos) { #' @param package A path to either package, directory with packages or name #' of the package (details) #' @param repos repository used to identify packages when name is provided. +#' @param remotes_dependencies A vector of length one or a named list. +#' Compatible with [`as_pkg_dependencies`]. Used to filter out remotes +#' dependencies. #' #' @details #' `package` parameter has two different allowed values: @@ -145,7 +152,8 @@ plan_rev_dep_release_check <- function(origin, revdep, repos) { #' @export plan_local_checks <- function( package, - repos = getOption("repos") + repos = getOption("repos"), + remotes_dependencies = TRUE ) { task <- meta_task( @@ -179,7 +187,7 @@ plan_local_checks <- function( star_plan_template(c( list(task), local_checks_tasks - )) + ), remotes_dependencies) } @@ -189,11 +197,15 @@ plan_local_checks <- function( #' #' @param package A path to package source. #' @param repos repository used to identify packages when name is provided. +#' @param remotes_dependencies A vector of length one or a named list. +#' Compatible with [`as_pkg_dependencies`]. Used to filter out remotes +#' dependencies. #' #' @family plan plan_local_install <- function( package, - repos = getOption("repos") + repos = getOption("repos"), + remotes_dependencies = TRUE ) { m_task <- meta_task( @@ -208,10 +220,10 @@ plan_local_install <- function( star_plan_template(list( m_task, i_task - )) + ), remotes_dependencies) } -star_plan_template <- function(tasks) { +star_plan_template <- function(tasks, remotes_dependencies) { g <- star_graph( task = tasks ) @@ -221,7 +233,7 @@ star_plan_template <- function(tasks) { g <- task_graph_class(g) if (remotes_permitted()) { - remotes_graph(g) + remotes_graph(g, dependencies = remotes_dependencies) } else { g } diff --git a/R/remotes.R b/R/remotes.R index 198dafc..c9a4257 100644 --- a/R/remotes.R +++ b/R/remotes.R @@ -3,9 +3,14 @@ remotes_graph <- function(x, ...) { } #' @export -remotes_graph.task_graph <- function(x, ...) { +remotes_graph.task_graph <- function(x, ..., dependencies = TRUE) { vs <- V(x) - remotes_subgraphs <- lapply(vs, remotes_graph, vs = vs) + remotes_subgraphs <- lapply( + vs, + remotes_graph, + vs = vs, + dependencies = dependencies + ) task_graph_class( suppressWarningsRegex( @@ -23,13 +28,13 @@ remotes_graph.task_graph <- function(x, ...) { #' @export remotes_graph.integer <- function(x, ..., vs) { - remotes_graph(vs[[x]]) + remotes_graph(vs[[x]], ...) } #' @export #' @method remotes_graph igraph.vs remotes_graph.igraph.vs <- function(x, ...) { - remotes_graph(x$task) + remotes_graph(x$task, ...) } #' @export @@ -40,16 +45,19 @@ remotes_graph.task <- function(x, ...) { } #' @export -remotes_graph.install_task <- function(x, ...) { +remotes_graph.install_task <- function(x, ..., dependencies = TRUE) { remote_tasks <- get_remote_tasks(x) if (length(remote_tasks) == 0) return(igraph::make_empty_graph()) remote_tasks_names <- vcapply(remote_tasks, package) + dependencies <- as_pkg_dependencies(dependencies)$direct x_deps <- pkg_deps(x$origin) - x_remote_deps <- x_deps[ - x_deps$package == package(x) & x_deps$name %in% remote_tasks_names, - ] - + x_remote_deps <- x_deps[x_deps$package == package(x) & + x_deps$name %in% remote_tasks_names & + x_deps$type %in% dependencies, ] + + if (NROW(x_remote_deps) == 0) return(igraph::make_empty_graph()) + # Sort tasks according to same key remote_tasks <- remote_tasks[order(remote_tasks_names)] remote_tasks_types <- x_remote_deps[order(x_remote_deps$name), ]$type diff --git a/R/task_graph.R b/R/task_graph.R index 8f79499..e5d691d 100644 --- a/R/task_graph.R +++ b/R/task_graph.R @@ -14,6 +14,8 @@ #' @param x a `plan` object, containing a list of related steps. #' @param repos `repos`, as expected by [`tools::package_dependencies()`] to #' determine package relationships. +#' @param dependencies A vector of length one or a named list. Compatible with +#' [`as_pkg_dependencies`]. #' @param ... params passed to helper methods. #' @return A `data.frame` that can be used to build #' [`igraph::make_graph`] edges. @@ -26,13 +28,20 @@ #' @keywords internal #' #' @importFrom igraph V E -task_graph <- function(x, repos = getOption("repos"), ...) { +task_graph <- function( + x, repos = getOption("repos"), dependencies = TRUE, ... +) { UseMethod("task_graph") } #' @export -task_graph.task <- function(x, repos = getOption("repos"), ...) { - df <- pkg_deps(x$origin, repos = repos, dependencies = TRUE) +task_graph.task <- function( + x, + repos = getOption("repos"), + dependencies = TRUE, + ... +) { + df <- pkg_deps(x$origin, repos = repos, dependencies = dependencies) # Distinguish direct dependencies of the package form possible indirect # in the same data.frame which could come from suggested loops. This ensures # there is a separate node for the root task. @@ -47,17 +56,14 @@ task_graph.task <- function(x, repos = getOption("repos"), ...) { E(g_dep)$relation <- RELATION$dep E(g_dep)$type <- DEP[E(g_dep)$type] - V(g_dep)$task <- lapply( - V(g_dep)$name, - function(p) { - if (endsWith(p, "-root")) { - x - } else { - origin <- try_pkg_origin_repo(package = p, repos = repos) - install_task(origin = origin) - } + V(g_dep)$task <- lapply(V(g_dep)$name, function(p) { + if (endsWith(p, "-root")) { + x + } else { + origin <- try_pkg_origin_repo(package = p, repos = repos) + install_task(origin = origin) } - ) + }) V(g_dep)$name <- vcapply(V(g_dep)$task, as_vertex_name) @@ -65,7 +71,9 @@ task_graph.task <- function(x, repos = getOption("repos"), ...) { } #' @export -task_graph.task_graph <- function(x, repos = getOption("repos"), ...) { +task_graph.task_graph <- function( + x, repos = getOption("repos"), dependencies = TRUE, ... +) { # only use dependency edges when populating graph nodes <- V(x)[is_actionable_task(V(x)$task)] @@ -82,7 +90,7 @@ task_graph.task_graph <- function(x, repos = getOption("repos"), ...) { subtree <- igraph::induced_subgraph(x, nh) # build dependency graph, with fallback installation task - deps <- task_graph(nh[[1]]$task, repos = repos) + deps <- task_graph(nh[[1]]$task, repos = repos, dependencies = dependencies) # merge trees on package names # NOTE: attributes (tasks) are preserved in the order they appear @@ -94,8 +102,12 @@ task_graph.task_graph <- function(x, repos = getOption("repos"), ...) { deduplicate_task_graph(subtree) }) + # then merge all the full check task task trees into a single graph g <- graph_dedup_attrs(igraph::union(x, check_task_neighborhoods)) + # Make sure orphaned packages, so those that do not lead to any meta tasks, + # are skipped + g <- task_graph_removed_orphaned(g) E(g)$type <- DEP[E(g)$type] V(g)$status <- STATUS$pending @@ -138,8 +150,16 @@ deduplicate_task_graph <- function(g) { ) } } - isolated <- which(igraph::degree(g) == 0) - igraph::delete_vertices(g, isolated) + g +} + +task_graph_removed_orphaned <- function(g) { + # The only package without "in" vertex should be meta tasks + vs <- igraph::V(g) + + isolated <- igraph::degree(g, mode = "in") == 0 & !is_meta(vs$task) + + igraph::delete_vertices(g, vs[isolated]) } dep_edges <- function(edges, dependencies = TRUE) { diff --git a/R/utils-deps.R b/R/utils-deps.R index 3122dcd..cfc4b40 100644 --- a/R/utils-deps.R +++ b/R/utils-deps.R @@ -23,18 +23,17 @@ as_pkg_dependencies <- function(x) { x <- character(0L) } - deptypes <- c("Depends", "Imports", "LinkingTo", "Suggests", "Enhances") - if (is.character(x)) { + if (is.character(x) || is.factor(x)) { x <- switch(x, - "all" = deptypes, - "most" = c("Depends", "Imports", "LinkingTo", "Suggests"), - "hard" = c("Depends", "Imports", "LinkingTo"), - "soft" = c("Suggests", "Enhances"), - x + "all" = DEP, + "most" = DEP[c("Depends", "Imports", "LinkingTo", "Suggests")], + "hard" = DEP[c("Depends", "Imports", "LinkingTo")], + "soft" = DEP[c("Suggests", "Enhances")], + DEP[x] ) } - stopifnot(all(x %in% deptypes)) + stopifnot(all(x %in% DEP)) x } diff --git a/man/checker.Rd b/man/checker.Rd index f5cbff3..6270218 100644 --- a/man/checker.Rd +++ b/man/checker.Rd @@ -81,6 +81,7 @@ and installation order are embedded. lib.loc = .libPaths(), repos = getOption("repos"), restore = options::opt("restore"), + dependencies = TRUE, ... )}\if{html}{\out{}} } @@ -106,6 +107,9 @@ generating task graph and later pulling dependencies.} unlinked before running checks. If \code{FALSE}, an attempt will me made to restore previous progress from the same \code{output}.} +\item{\code{dependencies}}{A vector of length one or a named list. +Compatible with \code{\link{as_pkg_dependencies}}.} + \item{\code{...}}{Additional arguments unused} } \if{html}{\out{}} diff --git a/man/plan_local_checks.Rd b/man/plan_local_checks.Rd index 34f3e14..6916c83 100644 --- a/man/plan_local_checks.Rd +++ b/man/plan_local_checks.Rd @@ -4,13 +4,21 @@ \alias{plan_local_checks} \title{Plan R CMD Checks} \usage{ -plan_local_checks(package, repos = getOption("repos")) +plan_local_checks( + package, + repos = getOption("repos"), + remotes_dependencies = TRUE +) } \arguments{ \item{package}{A path to either package, directory with packages or name of the package (details)} \item{repos}{repository used to identify packages when name is provided.} + +\item{remotes_dependencies}{A vector of length one or a named list. +Compatible with \code{\link{as_pkg_dependencies}}. Used to filter out remotes +dependencies.} } \description{ Generates a plan for running R CMD check for a specified set of packages. diff --git a/man/plan_local_install.Rd b/man/plan_local_install.Rd index 8346cb5..fa95455 100644 --- a/man/plan_local_install.Rd +++ b/man/plan_local_install.Rd @@ -4,12 +4,20 @@ \alias{plan_local_install} \title{Plan source package installation} \usage{ -plan_local_install(package, repos = getOption("repos")) +plan_local_install( + package, + repos = getOption("repos"), + remotes_dependencies = TRUE +) } \arguments{ \item{package}{A path to package source.} \item{repos}{repository used to identify packages when name is provided.} + +\item{remotes_dependencies}{A vector of length one or a named list. +Compatible with \code{\link{as_pkg_dependencies}}. Used to filter out remotes +dependencies.} } \description{ Generates a plan for running installing a package from source. diff --git a/man/plan_rev_dep_checks.Rd b/man/plan_rev_dep_checks.Rd index a61a5a6..9708ecb 100644 --- a/man/plan_rev_dep_checks.Rd +++ b/man/plan_rev_dep_checks.Rd @@ -4,12 +4,20 @@ \alias{plan_rev_dep_checks} \title{Plan Reverse Dependency Checks} \usage{ -plan_rev_dep_checks(path, repos = getOption("repos")) +plan_rev_dep_checks( + path, + repos = getOption("repos"), + remotes_dependencies = TRUE +) } \arguments{ \item{path}{path to the package source.} \item{repos}{repository used to identify reverse dependencies.} + +\item{remotes_dependencies}{A vector of length one or a named list. +Compatible with \code{\link{as_pkg_dependencies}}. Used to filter out remotes +dependencies.} } \description{ Generates a plan for running reverse dependency check for certain diff --git a/man/task_graph.Rd b/man/task_graph.Rd index e01a62f..6a2b2df 100644 --- a/man/task_graph.Rd +++ b/man/task_graph.Rd @@ -4,7 +4,7 @@ \alias{task_graph} \title{Build task graph edges} \usage{ -task_graph(x, repos = getOption("repos"), ...) +task_graph(x, repos = getOption("repos"), dependencies = TRUE, ...) } \arguments{ \item{x}{a \code{plan} object, containing a list of related steps.} @@ -12,6 +12,9 @@ task_graph(x, repos = getOption("repos"), ...) \item{repos}{\code{repos}, as expected by \code{\link[tools:package_dependencies]{tools::package_dependencies()}} to determine package relationships.} +\item{dependencies}{A vector of length one or a named list. Compatible with +\code{\link{as_pkg_dependencies}}.} + \item{...}{params passed to helper methods.} } \value{ From 2e3c8b0e077422598ea2ff78932fb478d101f17d Mon Sep 17 00:00:00 2001 From: maksymis <32574056+maksymiuks@users.noreply.github.com> Date: Wed, 3 Jun 2026 18:51:47 +0200 Subject: [PATCH 2/6] Initial changes --- R/checker.R | 11 ++++++++++- R/install.R | 9 ++++++--- R/next_task.R | 28 ++++++++++++++++++---------- R/options.R | 14 +++++++++++++- R/pkg_origin.R | 9 +++------ R/plan.R | 10 +++++++--- R/remotes.R | 5 +++++ R/task.R | 3 +++ R/task_graph.R | 8 +++++--- R/utils-deps.R | 2 +- R/utils-paths.R | 6 +++--- R/utils-pkg-source.R | 7 +++++-- R/utils.R | 21 ++++++++++++++++++--- man/checker.Rd | 5 +++++ man/install_task.Rd | 3 +++ man/options.Rd | 21 +++++++++++++++++++++ man/options_params.Rd | 9 +++++++++ man/plan_local_install.Rd | 6 +++++- 18 files changed, 140 insertions(+), 37 deletions(-) diff --git a/R/checker.R b/R/checker.R index 2dcac34..c29a489 100644 --- a/R/checker.R +++ b/R/checker.R @@ -84,6 +84,9 @@ checker <- R6::R6Class( #' restore previous progress from the same `output`. #' @param dependencies A vector of length one or a named list. #' Compatible with [`as_pkg_dependencies`]. + #' @param upgrade `logical` value, whether packages should be upgraded + #' if more recent version is discovered in available sources. Remotes + #' packages, if allowed to be used, are always installed and prioritized. #' @param ... Additional arguments unused #' #' @return [checker]. @@ -98,6 +101,7 @@ checker <- R6::R6Class( repos = getOption("repos"), restore = options::opt("restore"), dependencies = TRUE, + upgrade = FALSE, ... ) { check_past_output(output, restore, ask = interactive()) @@ -113,6 +117,7 @@ checker <- R6::R6Class( lib.loc ) private$repos <- repos + private$upgrade <- upgrade self$graph <- task_graph(self$plan, repos, dependencies = dependencies) private$restore_complete_checks() @@ -186,7 +191,8 @@ checker <- R6::R6Class( node = next_node, g = self$graph, output = self$output, - lib.loc = private$lib.loc + lib.loc = private$lib.loc, + upgrade = private$upgrade ) if (is.null(process)) { @@ -242,6 +248,9 @@ checker <- R6::R6Class( # task loop counter gc_needed = FALSE, + + # upgrade flag + upgrade = FALSE, start_node = function(node) { task_graph_package_status(self$graph, node) <- STATUS$`in progress` diff --git a/R/install.R b/R/install.R index e971b00..f78c10f 100644 --- a/R/install.R +++ b/R/install.R @@ -12,7 +12,8 @@ install_process <- R6::R6Class( lib = .libPaths()[[1]], libpaths = .libPaths(), available_packages_filters = getOption("available_packages_filters"), - log = NULL + log = NULL, + env = callr::rcmd_safe_env() ) { if (!dir.exists(lib)) dir.create(lib, recursive = TRUE) private$package <- pkgs @@ -21,7 +22,7 @@ install_process <- R6::R6Class( function(..., escalate_warning, available_packages_filters) { options(available_packages_filters = available_packages_filters) withCallingHandlers( - utils::install.packages(...), + utils::install.packages(..., quiet = FALSE, verbose = TRUE), warning = function(w) { if (escalate_warning(w)) { print(w$message) @@ -43,7 +44,9 @@ install_process <- R6::R6Class( libpath = libpaths, stdout = self$log, stderr = "2>&1", - system_profile = TRUE + system_profile = options::opt("install_system_profile"), + user_profile = options::opt("install_user_profile"), + env = env ) }, get_duration = function() { diff --git a/R/next_task.R b/R/next_task.R index b832ba1..5d1fa6e 100644 --- a/R/next_task.R +++ b/R/next_task.R @@ -62,36 +62,44 @@ start_task.install_task <- function( g, output, lib.loc, + upgrade, ... ) { task <- node$task[[1]] + + if (package(task) == "DALEX") browser() + libpaths <- unique(c( task_graph_libpaths(g, node, lib.loc = lib.loc, output = output), lib.loc )) install_parameters <- install_params(task$origin) - if (any(inherits(task$origin, c("pkg_origin_base", "pkg_origin_unknown")))) { - return(NULL) - } + is_base <- any( + inherits(task$origin, c("pkg_origin_base", "pkg_origin_unknown")) + ) + if (is_base) return(NULL) # install_parameters$package is a valid package name only for # pkg_origin_repo. Otherwise it's a path to the source package in which case # is_package_installed returns FALSE (as it should) - if (is_package_installed(install_parameters$package, libpaths)) { - return(NULL) - } + is_installed <- is_package_installed( + install_parameters$package, + libpaths, + upgrade %nif% task$origin$version + ) + if (is_installed) return(NULL) install_process$new( install_parameters$package, lib = lib(task, lib.loc = lib.loc, lib.root = path_libs(output)), libpaths = libpaths, - repos = task$origin$repos, + repos = install_parameters$repos, dependencies = FALSE, type = task$type, - INSTALL_opts = c(), # TODO - log = path_install_log(output, node$name[[1]]), - env = c() # TODO + INSTALL_opts = task$INSTALL_opts, + log = path_install_log(output, package(task), node$name[[1]]), + env = task$env ) } diff --git a/R/options.R b/R/options.R index e5be46d..400aa9d 100644 --- a/R/options.R +++ b/R/options.R @@ -67,7 +67,19 @@ options::define_options( envvar_fn = structure( function(raw, ...) trimws(strsplit(raw, " ")[[1]]), desc = "space-separated R CMD check flags" - ) + ), + + "named `character` vector of environment variables to use during + the package installation.", + install_envvars = callr::rcmd_safe_env(), + + "`logical` used as `sytem_profile` parameter passed to the `callr::r_bg()` + function used to install packages", + install_system_profile = FALSE, + + "value used as `user_profile` parameter passed to the `callr::r_bg()` + function used to install packages", + install_user_profile = "project" ) #' @eval options::as_roxygen_docs() diff --git a/R/pkg_origin.R b/R/pkg_origin.R index 8110742..1310f73 100644 --- a/R/pkg_origin.R +++ b/R/pkg_origin.R @@ -57,16 +57,13 @@ pkg_origin_repo <- function(package, repos, ...) { ap_pkg <- available_packages(repos = repos)[package, ] version <- package_version(ap_pkg["Version"]) - source <- strip_src_contrib(ap_pkg["Repository"]) - if (any(which <- startsWith(repos, source))) { - source <- repos[which][1] - } + repo <- strip_src_contrib(ap_pkg["Repository"], repos = repos) pkg_origin( package = package, version = version, - source = source, - repos = repos, + source = repo, + repos = repo, ..., .class = "pkg_origin_repo" ) diff --git a/R/plan.R b/R/plan.R index 44839e4..25fbeba 100644 --- a/R/plan.R +++ b/R/plan.R @@ -200,12 +200,15 @@ plan_local_checks <- function( #' @param remotes_dependencies A vector of length one or a named list. #' Compatible with [`as_pkg_dependencies`]. Used to filter out remotes #' dependencies. +#' @param INSTALL_opts Options to set while the root package is being installed. +#' Check [`utils::install.packages`] for details. #' #' @family plan plan_local_install <- function( package, repos = getOption("repos"), - remotes_dependencies = TRUE + remotes_dependencies = TRUE, + INSTALL_opts = c() ) { m_task <- meta_task( @@ -214,7 +217,8 @@ plan_local_install <- function( ) i_task <- install_task( - origin = pkg_origin_local(package) + origin = pkg_origin_local(package), + INSTALL_opts = INSTALL_opts ) star_plan_template(list( @@ -232,7 +236,7 @@ star_plan_template <- function(tasks, remotes_dependencies) { g <- task_graph_class(g) - if (remotes_permitted()) { + if (remotes_permitted() && !isFALSE(remotes_dependencies)) { remotes_graph(g, dependencies = remotes_dependencies) } else { g diff --git a/R/remotes.R b/R/remotes.R index c9a4257..5bcf56f 100644 --- a/R/remotes.R +++ b/R/remotes.R @@ -58,6 +58,11 @@ remotes_graph.install_task <- function(x, ..., dependencies = TRUE) { if (NROW(x_remote_deps) == 0) return(igraph::make_empty_graph()) + # Filter out deps out of scopr + remote_tasks <- remote_tasks[remote_tasks_names %in% x_remote_deps$name] + remote_tasks_names <- + remote_tasks_names[remote_tasks_names %in% x_remote_deps$name] + # Sort tasks according to same key remote_tasks <- remote_tasks[order(remote_tasks_names)] remote_tasks_types <- x_remote_deps[order(x_remote_deps$name), ]$type diff --git a/R/task.R b/R/task.R index 940b480..5186b2a 100644 --- a/R/task.R +++ b/R/task.R @@ -53,6 +53,7 @@ print.task <- function(x, ...) { #' @param lib Any object that can be passed to [lib()] to generate a library #' path. #' @inheritParams utils::install.packages +#' @inheritParams callr::r_bg #' #' @family tasks #' @export @@ -61,6 +62,7 @@ install_task <- function( type = package_install_type(origin), INSTALL_opts = NULL, lib = lib_path(origin), + env = options::opt("install_envvars"), ... ) { task( @@ -68,6 +70,7 @@ install_task <- function( type = type, INSTALL_opts = INSTALL_opts, lib = lib, + env = env, ..., .subclass = "install" ) diff --git a/R/task_graph.R b/R/task_graph.R index e5d691d..bbdcae2 100644 --- a/R/task_graph.R +++ b/R/task_graph.R @@ -45,8 +45,10 @@ task_graph.task <- function( # Distinguish direct dependencies of the package form possible indirect # in the same data.frame which could come from suggested loops. This ensures # there is a separate node for the root task. - df$package[df$depth == "direct"] <- - paste(df$package[df$depth == "direct"], "root", sep = "-") + if (inherits(x, "check_task")) { + df$package[df$depth == "direct"] <- + paste(df$package[df$depth == "direct"], "root", sep = "-") + } colmap <- c("package" = "from", "name" = "to") rename <- match(names(df), names(colmap)) to_rename <- !is.na(rename) @@ -57,7 +59,7 @@ task_graph.task <- function( E(g_dep)$relation <- RELATION$dep E(g_dep)$type <- DEP[E(g_dep)$type] V(g_dep)$task <- lapply(V(g_dep)$name, function(p) { - if (endsWith(p, "-root")) { + if (endsWith(p, "-root") || (inherits(x, "install_task") && p == package(x))) { # nolint x } else { origin <- try_pkg_origin_repo(package = p, repos = repos) diff --git a/R/utils-deps.R b/R/utils-deps.R index cfc4b40..255710d 100644 --- a/R/utils-deps.R +++ b/R/utils-deps.R @@ -23,7 +23,7 @@ as_pkg_dependencies <- function(x) { x <- character(0L) } - if (is.character(x) || is.factor(x)) { + if ((is.character(x) || is.factor(x)) & length(x) > 0) { x <- switch(x, "all" = DEP, "most" = DEP[c("Depends", "Imports", "LinkingTo", "Suggests")], diff --git a/R/utils-paths.R b/R/utils-paths.R index 1ff40a1..ba14d53 100644 --- a/R/utils-paths.R +++ b/R/utils-paths.R @@ -67,9 +67,9 @@ path_checker_lib <- function(path) { normalizePath(p) } -path_install_log <- function(path, package, name = "lib") { - dir_create(p <- file.path(path_logs(path), name)) - normalizePath(file.path(p, sprintf("%s.log", package)), mustWork = FALSE) +path_install_log <- function(path, package, task) { + dir_create(p <- file.path(path_logs(path), package)) + normalizePath(file.path(p, sprintf("%s.log", task)), mustWork = FALSE) } path_logs <- function(path) { diff --git a/R/utils-pkg-source.R b/R/utils-pkg-source.R index 48719d5..67f8e80 100644 --- a/R/utils-pkg-source.R +++ b/R/utils-pkg-source.R @@ -1,5 +1,8 @@ -strip_src_contrib <- function(x) { - sub("/src/contrib$", "", x) +strip_src_contrib <- function(x, repos) { + match <- vlapply(repos, function(r) { + utils::contrib.url(r) == x + }) + repos[match] } check_dependencies <- function(dependencies) { diff --git a/R/utils.R b/R/utils.R index 9f45281..bb991b9 100644 --- a/R/utils.R +++ b/R/utils.R @@ -38,9 +38,24 @@ base_pkgs <- function() { c("R", utils::installed.packages(priority = "base")[, "Package"]) } -is_package_installed <- function(pkg, lib.loc = .libPaths()) { - path <- find.package(pkg, lib.loc = lib.loc, quiet = TRUE) - length(path) > 0 +is_package_installed <- function(pkg, lib.loc = .libPaths(), version = NULL) { + if (is.null(version)) { + path <- find.package(pkg, lib.loc = lib.loc, quiet = TRUE) + length(path) > 0 + } else { + tryCatch( + utils::packageVersion(pkg, lib.loc) >= version, + error = function(e) FALSE + ) + } +} + +`%nif%` <- function(lhs, rhs) { + if (isTRUE(lhs)) { + rhs + } else { + NULL + } } .callr <- as.list(getNamespace("callr"), all.names = TRUE)[c( diff --git a/man/checker.Rd b/man/checker.Rd index 6270218..f16eb06 100644 --- a/man/checker.Rd +++ b/man/checker.Rd @@ -82,6 +82,7 @@ and installation order are embedded. repos = getOption("repos"), restore = options::opt("restore"), dependencies = TRUE, + upgrade = FALSE, ... )}\if{html}{\out{}} } @@ -110,6 +111,10 @@ restore previous progress from the same \code{output}.} \item{\code{dependencies}}{A vector of length one or a named list. Compatible with \code{\link{as_pkg_dependencies}}.} +\item{\code{upgrade}}{\code{logical} value, whether packages should be upgraded +if more recent version is discovered in available sources. Remotes +packages, if allowed to be used, are always installed and prioritized.} + \item{\code{...}}{Additional arguments unused} } \if{html}{\out{}} diff --git a/man/install_task.Rd b/man/install_task.Rd index 0663971..385aa3e 100644 --- a/man/install_task.Rd +++ b/man/install_task.Rd @@ -9,6 +9,7 @@ install_task( type = package_install_type(origin), INSTALL_opts = NULL, lib = lib_path(origin), + env = options::opt("install_envvars"), ... ) } @@ -33,6 +34,8 @@ install_task( \item{lib}{Any object that can be passed to \code{\link[=lib]{lib()}} to generate a library path.} +\item{env}{Environment variables to set for the child process.} + \item{...}{ further arguments to be passed to \code{\link[utils]{download.file}}, \code{\link[utils]{available.packages}}, or to the functions for binary diff --git a/man/options.Rd b/man/options.Rd index c00ce40..71c9de2 100644 --- a/man/options.Rd +++ b/man/options.Rd @@ -99,6 +99,27 @@ the R CMD check.\item{default: }{\preformatted{c(`_R_CHECK_FORCE_SUGGESTS_` = "f \item{envvar: }{R_CHECKED_CHECK_ARGS (space-separated R CMD check flags)} }} +\item{install_envvars}{\describe{ +named \code{character} vector of environment variables to use during +the package installation.\item{default: }{\preformatted{callr::rcmd_safe_env()}} +\item{option: }{checked.install_envvars} +\item{envvar: }{R_CHECKED_INSTALL_ENVVARS (evaluated if possible, raw string otherwise)} +}} + +\item{install_system_profile}{\describe{ +\code{logical} used as \code{sytem_profile} parameter passed to the \code{callr::r_bg()} +function used to install packages\item{default: }{\preformatted{FALSE}} +\item{option: }{checked.install_system_profile} +\item{envvar: }{R_CHECKED_INSTALL_SYSTEM_PROFILE (evaluated if possible, raw string otherwise)} +}} + +\item{install_user_profile}{\describe{ +value used as \code{user_profile} parameter passed to the \code{callr::r_bg()} +function used to install packages\item{default: }{\preformatted{"project"}} +\item{option: }{checked.install_user_profile} +\item{envvar: }{R_CHECKED_INSTALL_USER_PROFILE (evaluated if possible, raw string otherwise)} +}} + } } diff --git a/man/options_params.Rd b/man/options_params.Rd index 4e68647..4402e65 100644 --- a/man/options_params.Rd +++ b/man/options_params.Rd @@ -16,8 +16,14 @@ are discovered when generating results. "never" means that no errors are thrown. If "issues" then errors are emitted only on issues, whereas "potential issues" stands for error on both issues and potential issues. (Defaults to \code{"never"}, overwritable using option 'checked.results_error_on' or environment variable 'R_CHECKED_RESULTS_ERROR_ON')} +\item{install_envvars}{named \code{character} vector of environment variables to use during +the package installation. (Defaults to \code{callr::rcmd_safe_env()}, overwritable using option 'checked.install_envvars' or environment variable 'R_CHECKED_INSTALL_ENVVARS')} + \item{check_args}{\code{character} vector of args passed to the R CMD check. (Defaults to \code{c("--timings", "--ignore-vignettes", "--no-manual", "--as-cran")}, overwritable using option 'checked.check_args' or environment variable 'R_CHECKED_CHECK_ARGS')} +\item{install_system_profile}{\code{logical} used as \code{sytem_profile} parameter passed to the \code{callr::r_bg()} +function used to install packages (Defaults to \code{FALSE}, overwritable using option 'checked.install_system_profile' or environment variable 'R_CHECKED_INSTALL_SYSTEM_PROFILE')} + \item{results_keep}{character vector indicating which packages should be included in the results. "all" means that all packages are kept. If "issues" then only packages with issues identified, whereas "potential_issues" stands for keeping @@ -38,6 +44,9 @@ the R CMD check. (Defaults to \verb{c(}\emph{R_CHECK_FORCE_SUGGESTS}\verb{= "fal running checks. If \code{FALSE}, an attempt will me made to restore previous progress from the same \code{output} (Defaults to \code{NA}, overwritable using option 'checked.restore' or environment variable 'R_CHECKED_RESTORE')} +\item{install_user_profile}{value used as \code{user_profile} parameter passed to the \code{callr::r_bg()} +function used to install packages (Defaults to \code{"project"}, overwritable using option 'checked.install_user_profile' or environment variable 'R_CHECKED_INSTALL_USER_PROFILE')} + \item{tty_default_height}{deafult tty height used for the ANSI reporter. Used only if correct values could not be acquired with system('tput lines') (Defaults to \code{50}, overwritable using option 'checked.tty_default_height' or environment variable 'R_CHECKED_TTY_DEFAULT_HEIGHT')} } diff --git a/man/plan_local_install.Rd b/man/plan_local_install.Rd index fa95455..aed52d5 100644 --- a/man/plan_local_install.Rd +++ b/man/plan_local_install.Rd @@ -7,7 +7,8 @@ plan_local_install( package, repos = getOption("repos"), - remotes_dependencies = TRUE + remotes_dependencies = TRUE, + INSTALL_opts = c() ) } \arguments{ @@ -18,6 +19,9 @@ plan_local_install( \item{remotes_dependencies}{A vector of length one or a named list. Compatible with \code{\link{as_pkg_dependencies}}. Used to filter out remotes dependencies.} + +\item{INSTALL_opts}{Options to set while the root package is being installed. +Check \code{\link[utils:install.packages]{utils::install.packages}} for details.} } \description{ Generates a plan for running installing a package from source. From 5fc87765da69c1361977a41f9654ff616a862e07 Mon Sep 17 00:00:00 2001 From: maksymis <32574056+maksymiuks@users.noreply.github.com> Date: Mon, 8 Jun 2026 14:19:06 +0200 Subject: [PATCH 3/6] Customization API changes --- DESCRIPTION | 2 +- NEWS.md | 6 +++++- R/checker.R | 2 +- R/lib.R | 4 ++++ R/next_task.R | 6 ++---- R/options.R | 6 +++--- R/remotes.R | 6 +++--- R/task_graph.R | 8 ++++---- R/utils-deps.R | 2 +- 9 files changed, 24 insertions(+), 18 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 67f2ef0..01a5994 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: checked Title: Systematically Run R CMD Checks -Version: 0.5.2.9000 +Version: 0.5.3 Authors@R: c( person( diff --git a/NEWS.md b/NEWS.md index 017cff1..fad867b 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,4 +1,4 @@ -# checked (development) +# checked 0.5.3 * Make is possible to construct reporter environments with additional values to better control their behavior. @@ -7,6 +7,10 @@ will make the reporter broadcast only check tasks instead of both install and check. +* Multiple API changes facilitating additional customization for tasks. + +* Ensure packages destined into isolated libraries are always installed. + # checked 0.5.2 * Add timers striping to `strip_details_from_issue()` to avoid false-positives. diff --git a/R/checker.R b/R/checker.R index c29a489..d631a3d 100644 --- a/R/checker.R +++ b/R/checker.R @@ -248,7 +248,7 @@ checker <- R6::R6Class( # task loop counter gc_needed = FALSE, - + # upgrade flag upgrade = FALSE, diff --git a/R/lib.R b/R/lib.R index 5cdb860..15eabbf 100644 --- a/R/lib.R +++ b/R/lib.R @@ -50,6 +50,10 @@ format.lib_path <- function(x, ...) { "library" } +is_lib_path_default <- function(task) { + inherits(task$lib, "lib_path_default") +} + #' Get Library Location #' #' @param x An object describing a library location diff --git a/R/next_task.R b/R/next_task.R index 5d1fa6e..c2691f6 100644 --- a/R/next_task.R +++ b/R/next_task.R @@ -66,9 +66,7 @@ start_task.install_task <- function( ... ) { task <- node$task[[1]] - - if (package(task) == "DALEX") browser() - + libpaths <- unique(c( task_graph_libpaths(g, node, lib.loc = lib.loc, output = output), lib.loc @@ -88,7 +86,7 @@ start_task.install_task <- function( libpaths, upgrade %nif% task$origin$version ) - if (is_installed) return(NULL) + if (is_installed && is_lib_path_default(task)) return(NULL) install_process$new( install_parameters$package, diff --git a/R/options.R b/R/options.R index 400aa9d..a81f764 100644 --- a/R/options.R +++ b/R/options.R @@ -68,15 +68,15 @@ options::define_options( function(raw, ...) trimws(strsplit(raw, " ")[[1]]), desc = "space-separated R CMD check flags" ), - + "named `character` vector of environment variables to use during the package installation.", install_envvars = callr::rcmd_safe_env(), - + "`logical` used as `sytem_profile` parameter passed to the `callr::r_bg()` function used to install packages", install_system_profile = FALSE, - + "value used as `user_profile` parameter passed to the `callr::r_bg()` function used to install packages", install_user_profile = "project" diff --git a/R/remotes.R b/R/remotes.R index 5bcf56f..23e5c6f 100644 --- a/R/remotes.R +++ b/R/remotes.R @@ -55,14 +55,14 @@ remotes_graph.install_task <- function(x, ..., dependencies = TRUE) { x_remote_deps <- x_deps[x_deps$package == package(x) & x_deps$name %in% remote_tasks_names & x_deps$type %in% dependencies, ] - + if (NROW(x_remote_deps) == 0) return(igraph::make_empty_graph()) - + # Filter out deps out of scopr remote_tasks <- remote_tasks[remote_tasks_names %in% x_remote_deps$name] remote_tasks_names <- remote_tasks_names[remote_tasks_names %in% x_remote_deps$name] - + # Sort tasks according to same key remote_tasks <- remote_tasks[order(remote_tasks_names)] remote_tasks_types <- x_remote_deps[order(x_remote_deps$name), ]$type diff --git a/R/task_graph.R b/R/task_graph.R index bbdcae2..6861cf9 100644 --- a/R/task_graph.R +++ b/R/task_graph.R @@ -104,11 +104,11 @@ task_graph.task_graph <- function( deduplicate_task_graph(subtree) }) - + # then merge all the full check task task trees into a single graph g <- graph_dedup_attrs(igraph::union(x, check_task_neighborhoods)) # Make sure orphaned packages, so those that do not lead to any meta tasks, - # are skipped + # are skipped g <- task_graph_removed_orphaned(g) E(g)$type <- DEP[E(g)$type] @@ -158,9 +158,9 @@ deduplicate_task_graph <- function(g) { task_graph_removed_orphaned <- function(g) { # The only package without "in" vertex should be meta tasks vs <- igraph::V(g) - + isolated <- igraph::degree(g, mode = "in") == 0 & !is_meta(vs$task) - + igraph::delete_vertices(g, vs[isolated]) } diff --git a/R/utils-deps.R b/R/utils-deps.R index 255710d..b4bcf77 100644 --- a/R/utils-deps.R +++ b/R/utils-deps.R @@ -23,7 +23,7 @@ as_pkg_dependencies <- function(x) { x <- character(0L) } - if ((is.character(x) || is.factor(x)) & length(x) > 0) { + if ((is.character(x) || is.factor(x)) && length(x) > 0) { x <- switch(x, "all" = DEP, "most" = DEP[c("Depends", "Imports", "LinkingTo", "Suggests")], From a14234d6a5243a4236a69afcca04ac8d1f5a8811 Mon Sep 17 00:00:00 2001 From: maksymis <32574056+maksymiuks@users.noreply.github.com> Date: Mon, 8 Jun 2026 14:21:41 +0200 Subject: [PATCH 4/6] Update news --- NEWS.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/NEWS.md b/NEWS.md index fad867b..4b141e6 100644 --- a/NEWS.md +++ b/NEWS.md @@ -11,6 +11,8 @@ * Ensure packages destined into isolated libraries are always installed. +* Redesign logs by grouping them into package specific directories. + # checked 0.5.2 * Add timers striping to `strip_details_from_issue()` to avoid false-positives. From 08362cdf6d3644285b996eca02a497bc712b2bd8 Mon Sep 17 00:00:00 2001 From: maksymis <32574056+maksymiuks@users.noreply.github.com> Date: Wed, 10 Jun 2026 16:41:27 +0200 Subject: [PATCH 5/6] Increase timeout for install --- R/install.R | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/R/install.R b/R/install.R index 7048cd3..0ab1056 100644 --- a/R/install.R +++ b/R/install.R @@ -20,7 +20,10 @@ install_process <- R6::R6Class( self$log <- log private$callr_r_bg( function(..., escalate_warning, available_packages_filters) { - options(available_packages_filters = available_packages_filters) + options( + timeout = 600, + available_packages_filters = available_packages_filters + ) withCallingHandlers( utils::install.packages(..., quiet = FALSE, verbose = TRUE), warning = function(w) { From 6d04ffe421a3367defb3d15a8dd58d08cf7649d9 Mon Sep 17 00:00:00 2001 From: maksymis <32574056+maksymiuks@users.noreply.github.com> Date: Thu, 11 Jun 2026 14:27:36 +0200 Subject: [PATCH 6/6] Add self keyword --- R/utils-deps.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/utils-deps.R b/R/utils-deps.R index b4bcf77..8f5399c 100644 --- a/R/utils-deps.R +++ b/R/utils-deps.R @@ -19,7 +19,7 @@ as_pkg_dependencies <- function(x) { x <- "hard" } else if (identical(x, TRUE)) { x <- "most" - } else if (identical(x, FALSE)) { + } else if (identical(x, FALSE) || identical(x, "self")) { x <- character(0L) }