Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/R-CMD-check.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
# Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help
on:
push:
branches: [main, master]
branches: [main, master, dev]
pull_request:
branches: [main, master]
branches: [main, master, dev]

name: R-CMD-check

Expand Down
2 changes: 1 addition & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Package: checked
Title: Systematically Run R CMD Checks
Version: 0.5.1.9000
Version: 0.5.4
Authors@R:
c(
person(
Expand Down
1 change: 0 additions & 1 deletion NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,6 @@ export(new_rev_dep_checker)
export(pkg_origin)
export(pkg_origin_archive)
export(pkg_origin_base)
export(pkg_origin_is_base)
export(pkg_origin_local)
export(pkg_origin_remote)
export(pkg_origin_repo)
Expand Down
40 changes: 39 additions & 1 deletion NEWS.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,45 @@
# checked (development)
# checked 0.5.4

* Improve error messaging when using basic_tty

* Various improvements to reporters and minor display tweaks

# checked 0.5.3

* Make is possible to construct reporter environments with additional values
to better control their behavior.

* Add `checks_only` parameter to `reporter_basic_tty` which if set to TRUE
will make the reporter broadcast only check tasks instead of both
install and check.

* Address changes related to default parameters changes in `callr`
and resulting NULL comparison in `checked` (@gaborcsardi, #93)

* Multiple API changes facilitating additional customization for tasks.

* Ensure packages destined into isolated libraries or those coming from
non-standard sources 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.

* Remove `pkg_origin_is_base()` helper function and use memoised `base_pkgs()`.

* Update `RE_CHECK` to capture even more edge cases while parsing R CMD check
output.

* Finish check process even if checks seem incomplete but 3 or mire minutes have
passed since the process ended to avoid infinite loops.

* Further improvements to the check process finisher.

* Make `graph_dedup_attrs` rebuild the graph from scratch with deduplicated
attributes rather than manipulating the exiting graph. I significantly speeds
up the function.

# checked 0.5.1

Expand Down
33 changes: 20 additions & 13 deletions R/check_process.R
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
# Regular Expression for Parsing R CMD check checks
# nolint start, styler: off
RE_CHECK <- paste0(
"(?<=^|\\n)", # must start at beginning of string OR right after a newline
"\\* checking ", # literal "* checking "
"(?<check>.*?)", # capture check name/content (non-greedy) as "check"
" \\.\\.\\.", # followed by literal " ..."
Expand All @@ -11,23 +10,27 @@ RE_CHECK <- paste0(
"\\n", # or: a normal detail line starting on the next line
"(?:[ \\t]{2,}", # either indented (2+ spaces/tabs)
"|\\*(?! (?:DONE|checking )))", # or a '*' line that is NOT "* DONE" and NOT "* checking ..."
"[^\\n]*(?:\\n|$)", # consume the rest of that detail line (to newline/end)
"[^\\n]*?", # consume content...
"(?:\\n|$|(?=\\* (?:DONE|checking )))", # ...until newline/end OR before next "* DONE"/"* checking"
")*",
"[ \\t]*", # allow extra spaces/tabs after "..." on the SAME line
"(?:", # position the engine right before a status token if one exists
# Case 1: status token is on the current line (possibly preceded by comment text)
"(?:[^\\n]*[ \\t]+)?(?=(?:[A-Z]{2}[A-Z0-9_-]*)\\s*(?:\\n|$))",
"(?:(?:(?!\\* (?:DONE|checking ))[^\\n])*[ \\t]+)?",
"(?=(?:[A-Z]{2}[A-Z0-9_-]*)\\s*(?:\\n|$|\\* (?:DONE|checking )))",
"|",
# Case 2: status token is on the next line:
# consume remainder of current line + newline + optional indent,
# but only if the next thing is a status token at end-of-line
"[^\\n]*\\n[ \\t]*(?=(?:[A-Z]{2}[A-Z0-9_-]*)\\s*(?:\\n|$))",
# but only if the next thing is a status token at end-of-line (or right before next marker)
"(?:(?!\\* (?:DONE|checking ))[^\\n])*\\n[ \\t]*",
"(?=(?:[A-Z]{2}[A-Z0-9_-]*)\\s*(?:\\n|$|\\* (?:DONE|checking )))",
"|",
# Case 3: no status token (eat remainder/comment and stop here)
"[^\\n]*",
# Case 3: no status token (eat remainder/comment and stop here),
# but do NOT swallow the next "* checking/* DONE" if it's glued to this line
"[^\\n]*?(?=\\n|$|\\* (?:DONE|checking ))",
")",
"(?<status>(?:[A-Z]{2}[A-Z0-9_-]*)|)", # capture status token, or capture empty string if absent
"(?=\\s*(?:\\n|$))" # must end at newline/end (allow trailing whitespace)
"(?<status>(?:[A-Z]{2}[A-Z0-9_-]*)|)", # status token OR empty string
"(?=\\s*(?:\\n|$|\\* (?:DONE|checking )))" # terminated by newline/end OR next marker
)

# nolint end, styler: on
Expand Down Expand Up @@ -68,14 +71,18 @@ check_process <- R6::R6Class(
if (!self$is_alive()) callback(self)
},
finish = function() {
# self$checks active binding calls poll_output so there is not need
# to call it explicitly
# Make sure results are polled
self$poll_output()
checks <- self$checks
# In some cases, check subprocess might suffer from a race condition, when
# process itself finished, but the final results of the last subcheck
# are not yet available to parse. Therefore we allow the process to
# finalize only if the last subcheck has reported status.
if (checks[length(checks)] != "") {
# finalize only if the last subcheck has reported status. However
# if we stuck in this state for longer than 3 minutes we should
# try to finish anyway, to prevent possible infinite loops.
time_finished <- self$get_time_finish() %||% Sys.time()
if (checks[length(checks)] != "" ||
((Sys.time() - time_finished) >= as.difftime(3, units = "mins"))) {
self$save_results()
private$cache_parsed_results()
private$free_file_descriptors()
Expand Down
24 changes: 22 additions & 2 deletions R/checker.R
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,11 @@ 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 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].
Expand All @@ -95,6 +100,8 @@ checker <- R6::R6Class(
lib.loc = .libPaths(),
repos = getOption("repos"),
restore = options::opt("restore"),
dependencies = TRUE,
upgrade = FALSE,
...
) {
check_past_output(output, restore, ask = interactive())
Expand All @@ -110,8 +117,9 @@ checker <- R6::R6Class(
lib.loc
)
private$repos <- repos
private$upgrade <- upgrade

self$graph <- task_graph(self$plan, repos)
self$graph <- task_graph(self$plan, repos, dependencies = dependencies)
private$restore_complete_checks()
},

Expand Down Expand Up @@ -183,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)) {
Expand Down Expand Up @@ -219,6 +228,14 @@ checker <- R6::R6Class(
all(V(self$graph)$status == STATUS$done)
}

},
#' @description
#' Tasks
#'
#' Returns what type of tasks the checker consists of and returns a unique
#' vector of primary classes
tasks = function() {
unique(vcapply(V(self$graph)$task, function(x) class(x)[[1]]))
}
),
private = list(
Expand All @@ -240,6 +257,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`
private$start_node_meta_parents(node)
Expand Down
53 changes: 36 additions & 17 deletions R/install.R
Original file line number Diff line number Diff line change
Expand Up @@ -12,38 +12,38 @@ 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
self$log <- log
private$callr_r_bg(
function(..., escalate_warning, available_packages_filters) {
options(available_packages_filters = available_packages_filters)
withCallingHandlers(
utils::install.packages(...),
function(..., available_packages_filters) {
options(
timeout = 600,
available_packages_filters = available_packages_filters
)
invisible(capture.output(withCallingHandlers(
utils::install.packages(..., quiet = FALSE, verbose = TRUE),
warning = function(w) {
if (escalate_warning(w)) {
print(w$message)
stop(w$message)
} else {
print(w$message)
warning(w)
}
print(w$message)
}
)
), split = TRUE))
},
args = list(
private$package,
...,
lib = lib,
escalate_warning = is_install_failure_warning,
available_packages_filters = available_packages_filters
),
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"),
cmdargs = c("--slave", "--no-save", "--no-restore", "--vanilla"),
env = env
)
},
get_duration = function() {
Expand All @@ -62,7 +62,26 @@ install_process <- R6::R6Class(
if (is.function(f <- private$finish_callback)) f(self)
},
get_r_exit_status = function() {
as.integer(inherits(try(self$get_result(), silent = TRUE), "try-error"))
res <- self$get_results_safe()
if (inherits(self$get_results_safe(), "callr_error")) {
1L
} else if (any(is_install_failure_warning(res))) {
1L
} else {
0L
}

},
get_results_safe = function() {
tryCatch(
gsub("\\n", "\n", self$get_result(), fixed = TRUE),
error = function(e) {
e
},
warning = function(w) {
w
}
)
}
),
private = list(
Expand Down Expand Up @@ -109,5 +128,5 @@ is_install_failure_warning <- function(w) {
)

re <- paste0("(", paste0(patterns, collapse = "|"), ")")
grepl(re, w$message)
grepl(re, w)
}
4 changes: 4 additions & 0 deletions R/lib.R
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
26 changes: 17 additions & 9 deletions R/next_task.R
Original file line number Diff line number Diff line change
Expand Up @@ -62,36 +62,44 @@ start_task.install_task <- function(
g,
output,
lib.loc,
upgrade,
...
) {
task <- node$task[[1]]

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)) {
is_installed <- is_package_installed(
install_parameters$package,
libpaths,
upgrade %nif% task$origin$version
)

if (is_installed && is_lib_path_default(task) && inherits(task$origin, "pkg_origin_repo")) # nolint
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
)
}

Expand Down
14 changes: 13 additions & 1 deletion R/options.R
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
Loading
Loading