1
2#' Run an `R CMD check` process in the background
3#'
4#' rcmdcheck_process is an R6 class, that extends the
5#' [callr::rcmd_process] class (which in turn extends [processx::process].
6#'
7#' @section Usage:
8#' ```
9#' cp <- rcmdcheck_process$new(path = ".", args = character(),
10#'          build_args = character(), check_dir = NULL,
11#'          libpath = .libPaths(), repos = getOption("repos"))
12#'
13#' cp$parse_results()
14#' ```
15#'
16#' Other methods are inherited from [callr::rcmd_process] and
17#' [processx::process].
18#'
19#' Note that you calling the `get_output_connection` and
20#' `get_error_connection` method on this is not a good idea, because
21#' then the stdout and/or stderr of the process will not be collected
22#' for `parse_results()`.
23#'
24#' You can still use the `read_output_lines()` and `read_error_lines()`
25#' methods to read the standard output and error, `parse_results()` is
26#' not affected by that.
27#'
28#' @section Arguments:
29#' * `cp`: A new rcmdcheck_process object.
30#' * `path`: Path to a package tree or a package archive file. This is the
31#'   package to check.
32#' * `args`: Command line arguments to `R CMD check`.
33#' * `build_args`: Command line arguments to `R CMD build`.
34#' * `check_dir`: Directory for the results.
35#' * `libpath`: The library path to set for the check.
36#' * `repos`: The `repos` option to set for the check.
37#'   This is needed for cyclic dependency checks if you use the
38#'   `--as-cran` argument. The default uses the current value.
39#'
40#' @section Details:
41#' Most methods are inherited from [callr::rcmd_process] and
42#' [processx::process].
43#'
44#' `cp$parse_results()` parses the results, and returns an S3 object with
45#' fields `errors`, `warnnigs` and `notes`, just like [rcmdcheck()]. It
46#' is an error to call it before the process has finished. Use the
47#' `wait()` method to wait for the check to finish, or the `is_alive()`
48#' method to check if it is still running.
49#'
50#' @importFrom R6 R6Class
51#' @name rcmdcheck_process
52NULL
53
54#' @export
55
56rcmdcheck_process <- R6Class(
57  "rcmdcheck_process",
58  inherit = callr::rcmd_process,
59
60  public = list(
61
62    initialize = function(path = ".", args = character(),
63      build_args = character(), check_dir = NULL, libpath = .libPaths(),
64      repos = getOption("repos"))
65      rcc_init(self, private, super, path, args, build_args, check_dir,
66               libpath, repos),
67
68    parse_results = function()
69      rcc_parse_results(self, private),
70
71    read_output_lines = function(...) {
72      l <- super$read_output_lines(...)
73      private$cstdout <- c(private$cstdout, paste0(l, "\n"))
74      l
75    },
76
77    read_error_lines = function(...) {
78      l <- super$read_error_lines(...)
79      private$cstderr <- c(private$cstderr, paste0(l, "\n"))
80      l
81    },
82
83    read_output = function(...) {
84      l <- super$read_output(...)
85      private$cstdout <- c(private$cstdout, l)
86      l
87    },
88
89    read_error = function(...) {
90      l <- super$read_error(...)
91      private$cstderr <- c(private$cstderr, l)
92      l
93    },
94
95    kill = function(...) {
96      private$killed <- TRUE
97      super$kill(...)
98    }
99
100  ),
101  private = list(
102    path  = NULL,
103    check_dir = NULL,
104    targz = NULL,
105    description = NULL,
106    cstdout = character(),
107    cstderr = character(),
108    killed = FALSE,
109    session_output = NULL,
110    tempfiles = character()
111  )
112)
113
114#' @importFrom callr rcmd_process rcmd_process_options
115#' @importFrom desc desc
116
117rcc_init <- function(self, private, super, path, args, build_args,
118                     check_dir, libpath, repos) {
119
120  if (file.info(path)$isdir) {
121    path <- find_package_root_file(path = path)
122  } else {
123    path <- normalizePath(path)
124  }
125
126  if (is.null(check_dir)) {
127    check_dir <- tempfile()
128    cleanup <- TRUE
129  } else {
130    cleanup <- FALSE
131  }
132
133  targz <- build_package(path, check_dir, build_args = build_args,
134                         libpath = libpath, quiet = TRUE)
135
136  private$description <- desc(path)
137  private$path <- path
138  private$check_dir <- check_dir
139  private$targz <- targz
140
141  private$session_output <- tempfile()
142  profile <- make_fake_profile(session_output = private$session_output)
143  private$tempfiles  <- c(private$session_output, profile)
144
145  options <- rcmd_process_options(
146    cmd = "check",
147    cmdargs = c(basename(targz), args),
148    libpath = libpath,
149    repos = repos,
150    user_profile = TRUE
151  )
152
153  with_envvar(
154    c(R_PROFILE_USER = profile,
155      R_LIBS_USER = paste(libpath, collapse = .Platform$path.sep)),
156    with_dir(
157      dirname(targz),
158      super$initialize(options)
159    )
160  )
161
162  invisible(self)
163}
164
165rcc_parse_results <- function(self, private) {
166  if (self$is_alive()) stop("Process still alive")
167
168  ## Make sure all output is read out
169  self$read_output_lines()
170  self$read_error_lines()
171
172  on.exit(unlink(private$tempfiles, recursive = TRUE), add = TRUE)
173
174  new_rcmdcheck(
175    stdout =       paste(win2unix(private$cstdout), collapse = ""),
176    stderr =       paste(win2unix(private$cstderr), collapse = ""),
177    description =  private$description,
178    status =       self$get_exit_status(),
179    duration =     duration(self$get_start_time()),
180    timeout =      private$killed,
181    session_info = private$session_output
182  )
183}
184