callr/0000755000176200001440000000000014037561432011354 5ustar liggesuserscallr/NAMESPACE0000644000176200001440000000114714037526556012606 0ustar liggesusers# Generated by roxygen2: do not edit by hand S3method(print,callr_error) export(default_repos) export(poll) export(process) export(r) export(r_bg) export(r_copycat) export(r_process) export(r_process_options) export(r_safe) export(r_session) export(r_session_options) export(r_vanilla) export(rcmd) export(rcmd_bg) export(rcmd_copycat) export(rcmd_process) export(rcmd_process_options) export(rcmd_safe) export(rcmd_safe_env) export(rscript) export(rscript_process) export(rscript_process_options) export(run) export(supported_archs) importFrom(processx,poll) importFrom(processx,process) importFrom(processx,run) callr/LICENSE0000644000176200001440000000006613612550263012361 0ustar liggesusersYEAR: 2016 COPYRIGHT HOLDER: Mango Solutions, RStudio callr/README.md0000644000176200001440000003027514037526303012640 0ustar liggesusers # callr > Call R from R [![lifecycle](https://lifecycle.r-lib.org/articles/figures/lifecycle-stable.svg)](https://lifecycle.r-lib.org/articles/stages.html) [![R build status](https://github.com/r-lib/callr/workflows/R-CMD-check/badge.svg)](https://github.com/r-lib/callr/actions) [![](https://www.r-pkg.org/badges/version/callr)](https://www.r-pkg.org/pkg/callr) [![CRAN RStudio mirror downloads](https://cranlogs.r-pkg.org/badges/callr)](https://www.r-pkg.org/pkg/callr) [![Coverage Status](https://img.shields.io/codecov/c/github/r-lib/callr/master.svg)](https://codecov.io/github/r-lib/callr?branch=master) It is sometimes useful to perform a computation in a separate R process, without affecting the current R process at all. This packages does exactly that. --- - [Features](#features) - [Installation](#installation) - [Synchronous, one-off R processes](#synchronous-one-off-r-processes) - [Passing arguments](#passing-arguments) - [Using packages](#using-packages) - [Error handling](#error-handling) - [Standard output and error](#standard-output-and-error) - [Background R processes](#background-r-processes) - [Multiple background R processes and `poll()`](#multiple-background-r-processes-and-poll) - [Persistent R sessions](#persistent-r-sessions) - [Running `R CMD` commands](#running-r-cmd-commands) - [License](#license) Features -------- - Calls an R function, with arguments, in a subprocess. - Copies function arguments to the subprocess and copies the return value of the function back, seamlessly. - Copies error objects back from the subprocess, including a stack trace. - Shows and/or collects the standard output and standard error of the subprocess. - Supports both one-off and persistent R subprocesses. - Calls the function synchronously or asynchronously (in the background). - Can call `R CMD` commands, synchronously or asynchronously. - Can call R scripts, synchronously or asynchronously. - Provides extensible `r_process`, `rcmd_process` and `rscript_process` R6 classes, based on `processx::process`. Installation ------------ Install the stable version from CRAN: ``` r install.packages("callr") ``` Synchronous, one-off R processes -------------------------------- Use `r()` to run an R function in a new R process. The results are passed back seamlessly: ``` r library(callr) r(function() var(iris[, 1:4])) ``` #> Sepal.Length Sepal.Width Petal.Length Petal.Width #> Sepal.Length 0.6856935 -0.0424340 1.2743154 0.5162707 #> Sepal.Width -0.0424340 0.1899794 -0.3296564 -0.1216394 #> Petal.Length 1.2743154 -0.3296564 3.1162779 1.2956094 #> Petal.Width 0.5162707 -0.1216394 1.2956094 0.5810063 ### Passing arguments You can pass arguments to the function by setting `args` to the list of arguments. This is often necessary as these arguments are explicitly copied to the child process, whereas the evaluated function cannot refer to variables in the parent. For example, the following does not work: ``` r mycars <- cars r(function() summary(mycars)) ``` #> Error: callr subprocess failed: object 'mycars' not found But this does: ``` r r(function(x) summary(x), args = list(mycars)) ``` #> speed dist #> Min. : 4.0 Min. : 2.00 #> 1st Qu.:12.0 1st Qu.: 26.00 #> Median :15.0 Median : 36.00 #> Mean :15.4 Mean : 42.98 #> 3rd Qu.:19.0 3rd Qu.: 56.00 #> Max. :25.0 Max. :120.00 Note that the arguments will be serialized and saved to a file, so if they are large R objects, it might take a long time for the child process to start up. ### Using packages You can use any R package in the child process, just make sure to refer to it explicitly with the `::` operator. For example, the following code creates an [igraph](https://github.com/igraph/rigraph) graph in the child, and calculates some metrics of it. ``` r r(function() { g <- igraph::sample_gnp(1000, 4/1000); igraph::diameter(g) }) ``` #> [1] 14 ### Error handling callr copies errors from the child process back to the main R session: ``` r r(function() 1 + "A") ``` #> Error: callr subprocess failed: non-numeric argument to binary operator callr sets the `.Last.error` variable, and after an error you can inspect this for more details about the error, including stack traces both from the main R process and the subprocess. ``` r .Last.error ``` #> #> --> #> non-numeric argument to binary operator> #> in process 32341 The error objects has two parts. The first belongs to the main process, and the second belongs to the subprocess. `.Last.error` also includes a stack trace, that includes both the main R process and the subprocess: ``` r .Last.error.trace ``` #> #> Stack trace: #> #> Process 32229: #> 36. callr:::r(function() 1 + "A") #> 37. callr:::get_result(output = out, options) #> 38. throw(newerr, parent = remerr[[2]]) #> #> x callr subprocess failed: non-numeric argument to binary operator #> #> Process 32341: #> 50. (function () ... #> 51. base:::.handleSimpleError(function (e) ... #> 52. h(simpleError(msg, call)) #> #> x non-numeric argument to binary operator The top part of the trace contains the frames in the main process, and the bottom part contains the frames in the subprocess, starting with the anonymous function. ### Standard output and error By default, the standard output and error of the child is lost, but you can request callr to redirect them to files, and then inspect the files in the parent: ``` r x <- r(function() { print("hello world!"); message("hello again!") }, stdout = "/tmp/out", stderr = "/tmp/err" ) readLines("/tmp/out") ``` #> [1] "[1] \"hello world!\"" ``` r readLines("/tmp/err") ``` #> [1] "hello again!" With the `stdout` option, the standard output is collected and can be examined once the child process finished. The `show = TRUE` options will also show the output of the child, as it is printed, on the console of the parent. Background R processes ---------------------- `r_bg()` is similar to `r()` but it starts the R process in the background. It returns an `r_process` R6 object, that provides a rich API: ``` r rp <- r_bg(function() Sys.sleep(.2)) rp ``` #> PROCESS 'R', running, pid 32379. This is a list of all `r_process` methods: ``` r ls(rp) ``` #> [1] "as_ps_handle" "clone" "finalize" #> [4] "format" "get_cmdline" "get_cpu_times" #> [7] "get_error_connection" "get_error_file" "get_exe" #> [10] "get_exit_status" "get_input_connection" "get_input_file" #> [13] "get_memory_info" "get_name" "get_output_connection" #> [16] "get_output_file" "get_pid" "get_poll_connection" #> [19] "get_result" "get_start_time" "get_status" #> [22] "get_username" "get_wd" "has_error_connection" #> [25] "has_input_connection" "has_output_connection" "has_poll_connection" #> [28] "initialize" "interrupt" "is_alive" #> [31] "is_incomplete_error" "is_incomplete_output" "is_supervised" #> [34] "kill" "kill_tree" "poll_io" #> [37] "print" "read_all_error" "read_all_error_lines" #> [40] "read_all_output" "read_all_output_lines" "read_error" #> [43] "read_error_lines" "read_output" "read_output_lines" #> [46] "resume" "signal" "supervise" #> [49] "suspend" "wait" "write_input" These include all methods of the `processx::process` superclass and the new `get_result()` method, to retrieve the R object returned by the function call. Some of the handiest methods are: - `get_exit_status()` to query the exit status of a finished process. - `get_result()` to collect the return value of the R function call. - `interrupt()` to send an interrupt to the process. This is equivalent to a `CTRL+C` key press, and the R process might ignore it. - `is_alive()` to check if the process is alive. - `kill()` to terminate the process. - `poll_io()` to wait for any standard output, standard error, or the completion of the process, with a timeout. - `read_*()` to read the standard output or error. - `suspend()` and `resume()` to stop and continue a process. - `wait()` to wait for the completion of the process, with a timeout. Multiple background R processes and `poll()` -------------------------------------------- Multiple background R processes are best managed with the `processx::poll()` function that waits for events (standard output/error or termination) from multiple processes. It returns as soon as one process has generated an event, or if its timeout has expired. The timeout is in milliseconds. ``` r rp1 <- r_bg(function() { Sys.sleep(1/2); "1 done" }) rp2 <- r_bg(function() { Sys.sleep(1/1000); "2 done" }) processx::poll(list(rp1, rp2), 1000) ``` #> [[1]] #> output error process #> "silent" "silent" "silent" #> #> [[2]] #> output error process #> "silent" "silent" "ready" ``` r rp2$get_result() ``` #> [1] "2 done" ``` r processx::poll(list(rp1), 1000) ``` #> [[1]] #> output error process #> "silent" "silent" "ready" ``` r rp1$get_result() ``` #> [1] "1 done" Persistent R sessions --------------------- `r_session` is another `processx::process` subclass that represents a persistent background R session: ``` r rs <- r_session$new() rs ``` #> R SESSION, alive, idle, pid 32412. `r_session$run()` is a synchronous call, that works similarly to `r()`, but uses the persistent session. `r_session$call()` starts the function call and returns immediately. The `r_session$poll_process()` method or `processx::poll()` can then be used to wait for the completion or other events from one or more R sessions, R processes or other `processx::process` objects. Once an R session is done with an asynchronous computation, its `poll_process()` method returns `"ready"` and the `r_session$read()` method can read out the result. ``` r rs$run(function() runif(10)) ``` #> [1] 0.75342837 0.12946532 0.98800304 0.09682751 0.23944882 0.99726443 #> [7] 0.91098802 0.61136112 0.51781725 0.53566166 ``` r rs$call(function() rnorm(10)) rs ``` #> R SESSION, alive, busy, pid 32412. ``` r rs$poll_process(2000) ``` #> [1] "ready" ``` r rs$read() ``` #> $code #> [1] 200 #> #> $message #> [1] "done callr-rs-result-7de57e80bd54" #> #> $result #> [1] 0.73848421 -0.07600563 -1.18598532 0.10692265 -0.11717386 -0.24769265 #> [7] -0.13800969 -0.97854700 -0.30949881 -1.57689514 #> #> $stdout #> [1] "" #> #> $stderr #> [1] "" #> #> $error #> NULL #> #> attr(,"class") #> [1] "callr_session_result" Running `R CMD` commands ------------------------ The `rcmd()` function calls an `R CMD` command. For example, you can call `R CMD INSTALL`, `R CMD check` or `R CMD config` this way: ``` r rcmd("config", "CC") ``` #> $status #> [1] 0 #> #> $stdout #> [1] "clang -mmacosx-version-min=10.13\n" #> #> $stderr #> [1] "" #> #> $timeout #> [1] FALSE #> #> $command #> [1] "/Library/Frameworks/R.framework/Versions/4.0/Resources/bin/R" #> [2] "CMD" #> [3] "config" #> [4] "CC" ``` r #>$stdout #>[1] "clang\n" #> #>$stderr #>[1] "" #> #>$status #>[1] 0 ``` This returns a list with three components: the standard output, the standard error, and the exit (status) code of the `R CMD` command. License ------- MIT © Mango Solutions, RStudio callr/man/0000755000176200001440000000000014037526517012134 5ustar liggesuserscallr/man/r_vanilla.Rd0000644000176200001440000000631214037526556014377 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/presets.R \name{r_vanilla} \alias{r_vanilla} \title{Run an R child process, with no configuration} \usage{ r_vanilla( func, args = list(), libpath = character(), repos = c(CRAN = "@CRAN@"), cmdargs = "--slave", system_profile = FALSE, user_profile = FALSE, env = character(), ... ) } \arguments{ \item{func}{Function object to call in the new R process. The function should be self-contained and only refer to other functions and use variables explicitly from other packages using the \code{::} notation. By default the environment of the function is set to \code{.GlobalEnv} before passing it to the child process. (See the \code{package} option if you want to keep the environment.) Because of this, it is good practice to create an anonymous function and pass that to \code{callr}, instead of passing a function object from a (base or other) package. In particular\preformatted{r(.libPaths) } does not work, because \code{.libPaths} is defined in a special environment, but\preformatted{r(function() .libPaths()) } works just fine.} \item{args}{Arguments to pass to the function. Must be a list.} \item{libpath}{The library path.} \item{repos}{The \code{repos} option. If \code{NULL}, then no \code{repos} option is set. This options is only used if \code{user_profile} or \code{system_profile} is set \code{FALSE}, as it is set using the system or the user profile.} \item{cmdargs}{Command line arguments to pass to the R process. Note that \code{c("-f", rscript)} is appended to this, \code{rscript} is the name of the script file to run. This contains a call to the supplied function and some error handling code.} \item{system_profile}{Whether to use the system profile file.} \item{user_profile}{Whether to use the user's profile file. If this is \code{"project"}, then only the profile from the working directory is used, but the \code{R_PROFILE_USER} environment variable and the user level profile are not. See also "Security considerations" below.} \item{env}{Environment variables to set for the child process.} \item{...}{Additional arguments are passed to \code{\link[=r]{r()}}.} } \description{ It tries to mimic a fresh R installation. In particular: \itemize{ \item No library path setting. \item No CRAN(-like) repository is set. \item The system and user profiles are not run. } } \section{Security considerations}{ \code{callr} makes a copy of the user's \code{.Renviron} file and potentially of the local or user \code{.Rprofile}, in the session temporary directory. Avoid storing sensitive information such as passwords, in your environment file or your profile, otherwise this information will get scattered in various files, at least temporarily, until the subprocess finishes. You can use the keyring package to avoid passwords in plain files. } \examples{ \dontshow{if (FALSE) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} # Compare to r() r(function() .libPaths()) r_vanilla(function() .libPaths()) r(function() getOption("repos")) r_vanilla(function() getOption("repos")) \dontshow{\}) # examplesIf} } \seealso{ Other callr functions: \code{\link{r_copycat}()}, \code{\link{r}()} } \concept{callr functions} callr/man/r_session_debug.Rd0000644000176200001440000000424213725376411015576 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/r-session.R \name{r_session_debug} \alias{r_session_debug} \title{Interactive debugging of persistent R sessions} \description{ The \code{r_session$debug()} method is an interactive debugger to inspect the stack of the background process after an error. } \details{ \verb{$debug()} starts a REPL (Read-Eval-Print-Loop), that evaluates R expressions in the subprocess. It is similar to \code{\link[=browser]{browser()}} and \code{\link[=debugger]{debugger()}} and also has some extra commands: \itemize{ \item \code{.help} prints a short help message. \item \code{.where} prints the complete stack trace of the error. (The same as the \verb{$traceback()} method. \item \verb{.inspect } switches the "focus" to frame \verb{}. Frame 0 is the global environment, so \verb{.inspect 0} will switch back to that. } To exit the debugger, press the usual interrupt key, i.e. \code{CTRL+c} or \code{ESC} in some GUIs. Here is an example session that uses \verb{$debug()} (some output is omitted for brevity):\preformatted{# ---------------------------------------------------------------------- > rs <- r_session$new() > rs$run(function() knitr::knit("no-such-file")) Error in rs_run(self, private, func, args) : callr subprocess failed: cannot open the connection > rs$debug() Debugging in process 87361, press CTRL+C (ESC) to quit. Commands: .where -- print stack trace .inspect -- inspect a frame, 0 resets to .GlobalEnv .help -- print this message -- run in frame or .GlobalEnv 3: file(con, "r") 2: readLines(input2, encoding = "UTF-8", warn = FALSE) 1: knitr::knit("no-such-file") at #1 RS 87361 > .inspect 1 RS 87361 (frame 1) > ls() [1] "encoding" "envir" "ext" "in.file" "input" "input.dir" [7] "input2" "ocode" "oconc" "oenvir" "oopts" "optc" [13] "optk" "otangle" "out.purl" "output" "quiet" "tangle" [19] "text" RS 87361 (frame 1) > input [1] "no-such-file" RS 87361 (frame 1) > file.exists(input) [1] FALSE RS 87361 (frame 1) > # # ---------------------------------------------------------------------- } } callr/man/rcmd_process.Rd0000644000176200001440000003071214037526556015114 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/rcmd-process.R \name{rcmd_process} \alias{rcmd_process} \title{External \verb{R CMD} Process} \description{ An \verb{R CMD *} command that runs in the background. This is an R6 class that extends the \link[processx:process]{processx::process} class. } \examples{ \dontshow{if (FALSE) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} options <- rcmd_process_options(cmd = "config", cmdargs = "CC") rp <- rcmd_process$new(options) rp$wait() rp$read_output_lines() \dontshow{\}) # examplesIf} } \section{Super class}{ \code{\link[processx:process]{processx::process}} -> \code{rcmd_process} } \section{Methods}{ \subsection{Public methods}{ \itemize{ \item \href{#method-new}{\code{rcmd_process$new()}} \item \href{#method-finalize}{\code{rcmd_process$finalize()}} \item \href{#method-clone}{\code{rcmd_process$clone()}} } } \if{html}{ \out{
Inherited methods} \itemize{ \item \out{}\href{../../processx/html/process.html#method-as_ps_handle}{\code{processx::process$as_ps_handle()}}\out{} \item \out{}\href{../../processx/html/process.html#method-format}{\code{processx::process$format()}}\out{} \item \out{}\href{../../processx/html/process.html#method-get_cmdline}{\code{processx::process$get_cmdline()}}\out{} \item \out{}\href{../../processx/html/process.html#method-get_cpu_times}{\code{processx::process$get_cpu_times()}}\out{} \item \out{}\href{../../processx/html/process.html#method-get_error_connection}{\code{processx::process$get_error_connection()}}\out{} \item \out{}\href{../../processx/html/process.html#method-get_error_file}{\code{processx::process$get_error_file()}}\out{} \item \out{}\href{../../processx/html/process.html#method-get_exe}{\code{processx::process$get_exe()}}\out{} \item \out{}\href{../../processx/html/process.html#method-get_exit_status}{\code{processx::process$get_exit_status()}}\out{} \item \out{}\href{../../processx/html/process.html#method-get_input_connection}{\code{processx::process$get_input_connection()}}\out{} \item \out{}\href{../../processx/html/process.html#method-get_input_file}{\code{processx::process$get_input_file()}}\out{} \item \out{}\href{../../processx/html/process.html#method-get_memory_info}{\code{processx::process$get_memory_info()}}\out{} \item \out{}\href{../../processx/html/process.html#method-get_name}{\code{processx::process$get_name()}}\out{} \item \out{}\href{../../processx/html/process.html#method-get_output_connection}{\code{processx::process$get_output_connection()}}\out{} \item \out{}\href{../../processx/html/process.html#method-get_output_file}{\code{processx::process$get_output_file()}}\out{} \item \out{}\href{../../processx/html/process.html#method-get_pid}{\code{processx::process$get_pid()}}\out{} \item \out{}\href{../../processx/html/process.html#method-get_poll_connection}{\code{processx::process$get_poll_connection()}}\out{} \item \out{}\href{../../processx/html/process.html#method-get_result}{\code{processx::process$get_result()}}\out{} \item \out{}\href{../../processx/html/process.html#method-get_start_time}{\code{processx::process$get_start_time()}}\out{} \item \out{}\href{../../processx/html/process.html#method-get_status}{\code{processx::process$get_status()}}\out{} \item \out{}\href{../../processx/html/process.html#method-get_username}{\code{processx::process$get_username()}}\out{} \item \out{}\href{../../processx/html/process.html#method-get_wd}{\code{processx::process$get_wd()}}\out{} \item \out{}\href{../../processx/html/process.html#method-has_error_connection}{\code{processx::process$has_error_connection()}}\out{} \item \out{}\href{../../processx/html/process.html#method-has_input_connection}{\code{processx::process$has_input_connection()}}\out{} \item \out{}\href{../../processx/html/process.html#method-has_output_connection}{\code{processx::process$has_output_connection()}}\out{} \item \out{}\href{../../processx/html/process.html#method-has_poll_connection}{\code{processx::process$has_poll_connection()}}\out{} \item \out{}\href{../../processx/html/process.html#method-interrupt}{\code{processx::process$interrupt()}}\out{} \item \out{}\href{../../processx/html/process.html#method-is_alive}{\code{processx::process$is_alive()}}\out{} \item \out{}\href{../../processx/html/process.html#method-is_incomplete_error}{\code{processx::process$is_incomplete_error()}}\out{} \item \out{}\href{../../processx/html/process.html#method-is_incomplete_output}{\code{processx::process$is_incomplete_output()}}\out{} \item \out{}\href{../../processx/html/process.html#method-is_supervised}{\code{processx::process$is_supervised()}}\out{} \item \out{}\href{../../processx/html/process.html#method-kill}{\code{processx::process$kill()}}\out{} \item \out{}\href{../../processx/html/process.html#method-kill_tree}{\code{processx::process$kill_tree()}}\out{} \item \out{}\href{../../processx/html/process.html#method-poll_io}{\code{processx::process$poll_io()}}\out{} \item \out{}\href{../../processx/html/process.html#method-print}{\code{processx::process$print()}}\out{} \item \out{}\href{../../processx/html/process.html#method-read_all_error}{\code{processx::process$read_all_error()}}\out{} \item \out{}\href{../../processx/html/process.html#method-read_all_error_lines}{\code{processx::process$read_all_error_lines()}}\out{} \item \out{}\href{../../processx/html/process.html#method-read_all_output}{\code{processx::process$read_all_output()}}\out{} \item \out{}\href{../../processx/html/process.html#method-read_all_output_lines}{\code{processx::process$read_all_output_lines()}}\out{} \item \out{}\href{../../processx/html/process.html#method-read_error}{\code{processx::process$read_error()}}\out{} \item \out{}\href{../../processx/html/process.html#method-read_error_lines}{\code{processx::process$read_error_lines()}}\out{} \item \out{}\href{../../processx/html/process.html#method-read_output}{\code{processx::process$read_output()}}\out{} \item \out{}\href{../../processx/html/process.html#method-read_output_lines}{\code{processx::process$read_output_lines()}}\out{} \item \out{}\href{../../processx/html/process.html#method-resume}{\code{processx::process$resume()}}\out{} \item \out{}\href{../../processx/html/process.html#method-signal}{\code{processx::process$signal()}}\out{} \item \out{}\href{../../processx/html/process.html#method-supervise}{\code{processx::process$supervise()}}\out{} \item \out{}\href{../../processx/html/process.html#method-suspend}{\code{processx::process$suspend()}}\out{} \item \out{}\href{../../processx/html/process.html#method-wait}{\code{processx::process$wait()}}\out{} \item \out{}\href{../../processx/html/process.html#method-write_input}{\code{processx::process$write_input()}}\out{} } \out{
} } \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-new}{}}} \subsection{Method \code{new()}}{ Start an \verb{R CMD} process. \subsection{Usage}{ \if{html}{\out{
}}\preformatted{rcmd_process$new(options)}\if{html}{\out{
}} } \subsection{Arguments}{ \if{html}{\out{
}} \describe{ \item{\code{options}}{A list of options created via \code{\link[=rcmd_process_options]{rcmd_process_options()}}.} } \if{html}{\out{
}} } \subsection{Returns}{ A new \code{rcmd_process} object. } } \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-finalize}{}}} \subsection{Method \code{finalize()}}{ Clean up the temporary files created for an \verb{R CMD} process. \subsection{Usage}{ \if{html}{\out{
}}\preformatted{rcmd_process$finalize()}\if{html}{\out{
}} } } \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-clone}{}}} \subsection{Method \code{clone()}}{ The objects of this class are cloneable with this method. \subsection{Usage}{ \if{html}{\out{
}}\preformatted{rcmd_process$clone(deep = FALSE)}\if{html}{\out{
}} } \subsection{Arguments}{ \if{html}{\out{
}} \describe{ \item{\code{deep}}{Whether to make a deep clone.} } \if{html}{\out{
}} } } } callr/man/r_session_options.Rd0000644000176200001440000000350214023433766016200 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/r-session.R \name{r_session_options} \alias{r_session_options} \title{Create options for an \link{r_session} object} \usage{ r_session_options(...) } \arguments{ \item{...}{Options to override, named arguments.} } \value{ Named list of options. The current options are: \itemize{ \item \code{libpath}: Library path for the subprocess. By default the same as the \emph{current} library path. I.e. \emph{not} necessarily the library path of a fresh R session.) \item \code{repos}: \code{repos} option for the subprocess. By default the current value of the main process. \item \code{stdout}: Standard output of the sub-process. This can be \code{NULL} or a pipe: \code{"|"}. If it is a pipe then the output of the subprocess is not included in the responses, but you need to poll and read it manually. This is for exports. \item \code{stderr}: Similar to \code{stdout}, but for the standard error. \item \code{error}: See 'Error handling' in \code{\link[=r]{r()}}. \item \code{cmdargs}: See the same argument of \code{\link[=r]{r()}}. (Its default might be different, though.) \item \code{system_profile}: See the same argument of \code{\link[=r]{r()}}. \item \code{user_profile}: See the same argument of \code{\link[=r]{r()}}. \item \code{env}: See the same argument of \code{\link[=r]{r()}}. \item \code{load_hook}: \code{NULL}, or code (quoted) to run in the sub-process at start up. (I.e. not for every single \code{run()} call.) \item \code{extra}: List of extra arguments to pass to \link[processx:process]{processx::process}. } Call \code{r_session_options()} to see the default values. \code{r_session_options()} might contain undocumented entries, you cannot change these. } \description{ Create options for an \link{r_session} object } \examples{ r_session_options() } callr/man/r_process_options.Rd0000644000176200001440000000147313725376411016201 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/options.R \name{r_process_options} \alias{r_process_options} \title{Create options for an \link{r_process} object} \usage{ r_process_options(...) } \arguments{ \item{...}{Options to override, named arguments.} } \value{ A list of options. \code{r_process_options()} creates a set of options to initialize a new object from the \code{r_process} class. Its arguments must be named, the names are used as option names. The options correspond to (some of) the arguments of the \code{\link[=r]{r()}} function. At least the \code{func} option must be specified, this is the R function to run in the background. } \description{ Create options for an \link{r_process} object } \examples{ ## List all options and their default values: r_process_options() } callr/man/default_repos.Rd0000644000176200001440000000107213725376411015256 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/utils.R \name{default_repos} \alias{default_repos} \title{Default value for the \code{repos} option in callr subprocesses} \usage{ default_repos() } \value{ Named character vector, the default value of the \code{repos} option in callr subprocesses. } \description{ callr sets the \code{repos} option in subprocesses, to make sure that a CRAN mirror is set up. This is because the subprocess cannot bring up the menu of CRAN mirrors for the user to choose from. } \examples{ default_repos() } callr/man/r.Rd0000644000176200001440000002127414037526556012675 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/eval.R, R/presets.R \name{r} \alias{r} \alias{r_safe} \title{Evaluate an expression in another R session} \usage{ r( func, args = list(), libpath = .libPaths(), repos = default_repos(), stdout = NULL, stderr = NULL, poll_connection = TRUE, error = getOption("callr.error", "error"), cmdargs = c("--slave", "--no-save", "--no-restore"), show = FALSE, callback = NULL, block_callback = NULL, spinner = show && interactive(), system_profile = FALSE, user_profile = "project", env = rcmd_safe_env(), timeout = Inf, package = FALSE, arch = "same", ... ) r_safe( func, args = list(), libpath = .libPaths(), repos = default_repos(), stdout = NULL, stderr = NULL, poll_connection = TRUE, error = getOption("callr.error", "error"), cmdargs = c("--slave", "--no-save", "--no-restore"), show = FALSE, callback = NULL, block_callback = NULL, spinner = show && interactive(), system_profile = FALSE, user_profile = "project", env = rcmd_safe_env(), timeout = Inf, package = FALSE, arch = "same", ... ) } \arguments{ \item{func}{Function object to call in the new R process. The function should be self-contained and only refer to other functions and use variables explicitly from other packages using the \code{::} notation. By default the environment of the function is set to \code{.GlobalEnv} before passing it to the child process. (See the \code{package} option if you want to keep the environment.) Because of this, it is good practice to create an anonymous function and pass that to \code{callr}, instead of passing a function object from a (base or other) package. In particular\preformatted{r(.libPaths) } does not work, because \code{.libPaths} is defined in a special environment, but\preformatted{r(function() .libPaths()) } works just fine.} \item{args}{Arguments to pass to the function. Must be a list.} \item{libpath}{The library path.} \item{repos}{The \code{repos} option. If \code{NULL}, then no \code{repos} option is set. This options is only used if \code{user_profile} or \code{system_profile} is set \code{FALSE}, as it is set using the system or the user profile.} \item{stdout}{The name of the file the standard output of the child R process will be written to. If the child process runs with the \code{--slave} option (the default), then the commands are not echoed and will not be shown in the standard output. Also note that you need to call \code{print()} explicitly to show the output of the command(s).} \item{stderr}{The name of the file the standard error of the child R process will be written to. In particular \code{message()} sends output to the standard error. If nothing was sent to the standard error, then this file will be empty. This argument can be the same file as \code{stdout}, in which case they will be correctly interleaved. If this is the string \code{"2>&1"}, then standard error is redirected to standard output.} \item{poll_connection}{Whether to have a control connection to the process. This is used to transmit messages from the subprocess to the main process.} \item{error}{What to do if the remote process throws an error. See details below.} \item{cmdargs}{Command line arguments to pass to the R process. Note that \code{c("-f", rscript)} is appended to this, \code{rscript} is the name of the script file to run. This contains a call to the supplied function and some error handling code.} \item{show}{Logical, whether to show the standard output on the screen while the child process is running. Note that this is independent of the \code{stdout} and \code{stderr} arguments. The standard error is not shown currently.} \item{callback}{A function to call for each line of the standard output and standard error from the child process. It works together with the \code{show} option; i.e. if \code{show = TRUE}, and a callback is provided, then the output is shown of the screen, and the callback is also called.} \item{block_callback}{A function to call for each block of the standard output and standard error. This callback is not line oriented, i.e. multiple lines or half a line can be passed to the callback.} \item{spinner}{Whether to show a calming spinner on the screen while the child R session is running. By default it is shown if \code{show = TRUE} and the R session is interactive.} \item{system_profile}{Whether to use the system profile file.} \item{user_profile}{Whether to use the user's profile file. If this is \code{"project"}, then only the profile from the working directory is used, but the \code{R_PROFILE_USER} environment variable and the user level profile are not. See also "Security considerations" below.} \item{env}{Environment variables to set for the child process.} \item{timeout}{Timeout for the function call to finish. It can be a \link[base:difftime]{base::difftime} object, or a real number, meaning seconds. If the process does not finish before the timeout period expires, then a \code{system_command_timeout_error} error is thrown. \code{Inf} means no timeout.} \item{package}{Whether to keep the environment of \code{func} when passing it to the other package. Possible values are: \itemize{ \item \code{FALSE}: reset the environment to \code{.GlobalEnv}. This is the default. \item \code{TRUE}: keep the environment as is. \item \code{pkg}: set the environment to the \code{pkg} package namespace. }} \item{arch}{Architecture to use in the child process, for multi-arch builds of R. By default the same as the main process. See \code{\link[=supported_archs]{supported_archs()}}. If it contains a forward or backward slash character, then it is taken as the path to the R executable. Note that on Windows you need the path to \code{Rterm.exe}.} \item{...}{Extra arguments are passed to \code{\link[processx:run]{processx::run()}}.} } \value{ Value of the evaluated expression. } \description{ From \code{callr} version 2.0.0, \code{r()} is equivalent to \code{r_safe()}, and tries to set up a less error prone execution environment. In particular: \itemize{ \item Ensures that at least one reasonable CRAN mirror is set up. \item Adds some command line arguments to avoid saving \code{.RData} files, etc. \item Ignores the system and user profiles (by default). \item Sets various environment variables: \code{CYGWIN} to avoid warnings about DOS-style paths, \code{R_TESTS} to avoid issues when \code{callr} is invoked from unit tests, \code{R_BROWSER} and \code{R_PDFVIEWER} to avoid starting a browser or a PDF viewer. See \code{\link[=rcmd_safe_env]{rcmd_safe_env()}}. } } \details{ The \code{r()} function from before 2.0.0 is called \code{\link[=r_copycat]{r_copycat()}} now. } \section{Error handling}{ \code{callr} handles errors properly. If the child process throws an error, then \code{callr} throws an error with the same error message in the main process. The \code{error} expert argument may be used to specify a different behavior on error. The following values are possible: \itemize{ \item \code{error} is the default behavior: throw an error in the main process, with a prefix and the same error message as in the subprocess. \item \code{stack} also throws an error in the main process, but the error is of a special kind, class \code{callr_error}, and it contains both the original error object, and the call stack of the child, as written out by \code{\link[utils:debugger]{utils::dump.frames()}}. This is now deprecated, because the error thrown for \code{"error"} has the same information. \item \code{debugger} is similar to \code{stack}, but in addition to returning the complete call stack, it also start up a debugger in the child call stack, via \code{\link[utils:debugger]{utils::debugger()}}. } The default error behavior can be also set using the \code{callr.error} option. This is useful to debug code that uses \code{callr}. callr uses parent errors, to keep the stacks of the main process and the subprocess(es) in the same error object. } \section{Security considerations}{ \code{callr} makes a copy of the user's \code{.Renviron} file and potentially of the local or user \code{.Rprofile}, in the session temporary directory. Avoid storing sensitive information such as passwords, in your environment file or your profile, otherwise this information will get scattered in various files, at least temporarily, until the subprocess finishes. You can use the keyring package to avoid passwords in plain files. } \examples{ \dontshow{if (FALSE) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} # Workspace is empty r(function() ls()) # library path is the same by default r(function() .libPaths()) .libPaths() \dontshow{\}) # examplesIf} } \seealso{ Other callr functions: \code{\link{r_copycat}()}, \code{\link{r_vanilla}()} } \concept{callr functions} callr/man/rscript_process.Rd0000644000176200001440000003067014037526556015660 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/rscript.R \name{rscript_process} \alias{rscript_process} \title{External \code{Rscript} process} \description{ An \verb{Rscript script.R} command that runs in the background. This is an R6 class that extends the \link[processx:process]{processx::process} class. } \examples{ \dontshow{if (FALSE) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} options <- rscript_process_options(script = "script.R") rp <- rscript_process$new(options) rp$wait() rp$read_output_lines() \dontshow{\}) # examplesIf} } \section{Super class}{ \code{\link[processx:process]{processx::process}} -> \code{rscript_process} } \section{Methods}{ \subsection{Public methods}{ \itemize{ \item \href{#method-new}{\code{rscript_process$new()}} \item \href{#method-finalize}{\code{rscript_process$finalize()}} \item \href{#method-clone}{\code{rscript_process$clone()}} } } \if{html}{ \out{
Inherited methods} \itemize{ \item \out{}\href{../../processx/html/process.html#method-as_ps_handle}{\code{processx::process$as_ps_handle()}}\out{} \item \out{}\href{../../processx/html/process.html#method-format}{\code{processx::process$format()}}\out{} \item \out{}\href{../../processx/html/process.html#method-get_cmdline}{\code{processx::process$get_cmdline()}}\out{} \item \out{}\href{../../processx/html/process.html#method-get_cpu_times}{\code{processx::process$get_cpu_times()}}\out{} \item \out{}\href{../../processx/html/process.html#method-get_error_connection}{\code{processx::process$get_error_connection()}}\out{} \item \out{}\href{../../processx/html/process.html#method-get_error_file}{\code{processx::process$get_error_file()}}\out{} \item \out{}\href{../../processx/html/process.html#method-get_exe}{\code{processx::process$get_exe()}}\out{} \item \out{}\href{../../processx/html/process.html#method-get_exit_status}{\code{processx::process$get_exit_status()}}\out{} \item \out{}\href{../../processx/html/process.html#method-get_input_connection}{\code{processx::process$get_input_connection()}}\out{} \item \out{}\href{../../processx/html/process.html#method-get_input_file}{\code{processx::process$get_input_file()}}\out{} \item \out{}\href{../../processx/html/process.html#method-get_memory_info}{\code{processx::process$get_memory_info()}}\out{} \item \out{}\href{../../processx/html/process.html#method-get_name}{\code{processx::process$get_name()}}\out{} \item \out{}\href{../../processx/html/process.html#method-get_output_connection}{\code{processx::process$get_output_connection()}}\out{} \item \out{}\href{../../processx/html/process.html#method-get_output_file}{\code{processx::process$get_output_file()}}\out{} \item \out{}\href{../../processx/html/process.html#method-get_pid}{\code{processx::process$get_pid()}}\out{} \item \out{}\href{../../processx/html/process.html#method-get_poll_connection}{\code{processx::process$get_poll_connection()}}\out{} \item \out{}\href{../../processx/html/process.html#method-get_result}{\code{processx::process$get_result()}}\out{} \item \out{}\href{../../processx/html/process.html#method-get_start_time}{\code{processx::process$get_start_time()}}\out{} \item \out{}\href{../../processx/html/process.html#method-get_status}{\code{processx::process$get_status()}}\out{} \item \out{}\href{../../processx/html/process.html#method-get_username}{\code{processx::process$get_username()}}\out{} \item \out{}\href{../../processx/html/process.html#method-get_wd}{\code{processx::process$get_wd()}}\out{} \item \out{}\href{../../processx/html/process.html#method-has_error_connection}{\code{processx::process$has_error_connection()}}\out{} \item \out{}\href{../../processx/html/process.html#method-has_input_connection}{\code{processx::process$has_input_connection()}}\out{} \item \out{}\href{../../processx/html/process.html#method-has_output_connection}{\code{processx::process$has_output_connection()}}\out{} \item \out{}\href{../../processx/html/process.html#method-has_poll_connection}{\code{processx::process$has_poll_connection()}}\out{} \item \out{}\href{../../processx/html/process.html#method-interrupt}{\code{processx::process$interrupt()}}\out{} \item \out{}\href{../../processx/html/process.html#method-is_alive}{\code{processx::process$is_alive()}}\out{} \item \out{}\href{../../processx/html/process.html#method-is_incomplete_error}{\code{processx::process$is_incomplete_error()}}\out{} \item \out{}\href{../../processx/html/process.html#method-is_incomplete_output}{\code{processx::process$is_incomplete_output()}}\out{} \item \out{}\href{../../processx/html/process.html#method-is_supervised}{\code{processx::process$is_supervised()}}\out{} \item \out{}\href{../../processx/html/process.html#method-kill}{\code{processx::process$kill()}}\out{} \item \out{}\href{../../processx/html/process.html#method-kill_tree}{\code{processx::process$kill_tree()}}\out{} \item \out{}\href{../../processx/html/process.html#method-poll_io}{\code{processx::process$poll_io()}}\out{} \item \out{}\href{../../processx/html/process.html#method-print}{\code{processx::process$print()}}\out{} \item \out{}\href{../../processx/html/process.html#method-read_all_error}{\code{processx::process$read_all_error()}}\out{} \item \out{}\href{../../processx/html/process.html#method-read_all_error_lines}{\code{processx::process$read_all_error_lines()}}\out{} \item \out{}\href{../../processx/html/process.html#method-read_all_output}{\code{processx::process$read_all_output()}}\out{} \item \out{}\href{../../processx/html/process.html#method-read_all_output_lines}{\code{processx::process$read_all_output_lines()}}\out{} \item \out{}\href{../../processx/html/process.html#method-read_error}{\code{processx::process$read_error()}}\out{} \item \out{}\href{../../processx/html/process.html#method-read_error_lines}{\code{processx::process$read_error_lines()}}\out{} \item \out{}\href{../../processx/html/process.html#method-read_output}{\code{processx::process$read_output()}}\out{} \item \out{}\href{../../processx/html/process.html#method-read_output_lines}{\code{processx::process$read_output_lines()}}\out{} \item \out{}\href{../../processx/html/process.html#method-resume}{\code{processx::process$resume()}}\out{} \item \out{}\href{../../processx/html/process.html#method-signal}{\code{processx::process$signal()}}\out{} \item \out{}\href{../../processx/html/process.html#method-supervise}{\code{processx::process$supervise()}}\out{} \item \out{}\href{../../processx/html/process.html#method-suspend}{\code{processx::process$suspend()}}\out{} \item \out{}\href{../../processx/html/process.html#method-wait}{\code{processx::process$wait()}}\out{} \item \out{}\href{../../processx/html/process.html#method-write_input}{\code{processx::process$write_input()}}\out{} } \out{
} } \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-new}{}}} \subsection{Method \code{new()}}{ Create a new \code{Rscript} process. \subsection{Usage}{ \if{html}{\out{
}}\preformatted{rscript_process$new(options)}\if{html}{\out{
}} } \subsection{Arguments}{ \if{html}{\out{
}} \describe{ \item{\code{options}}{A list of options created via \code{\link[=rscript_process_options]{rscript_process_options()}}.} } \if{html}{\out{
}} } } \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-finalize}{}}} \subsection{Method \code{finalize()}}{ Clean up after an \code{Rsctipt} process, remove temporary files. \subsection{Usage}{ \if{html}{\out{
}}\preformatted{rscript_process$finalize()}\if{html}{\out{
}} } } \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-clone}{}}} \subsection{Method \code{clone()}}{ The objects of this class are cloneable with this method. \subsection{Usage}{ \if{html}{\out{
}}\preformatted{rscript_process$clone(deep = FALSE)}\if{html}{\out{
}} } \subsection{Arguments}{ \if{html}{\out{
}} \describe{ \item{\code{deep}}{Whether to make a deep clone.} } \if{html}{\out{
}} } } } callr/man/r_bg.Rd0000644000176200001440000001230314037526556013336 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/eval-bg.R \name{r_bg} \alias{r_bg} \title{Evaluate an expression in another R session, in the background} \usage{ r_bg( func, args = list(), libpath = .libPaths(), repos = default_repos(), stdout = "|", stderr = "|", poll_connection = TRUE, error = getOption("callr.error", "error"), cmdargs = c("--slave", "--no-save", "--no-restore"), system_profile = FALSE, user_profile = "project", env = rcmd_safe_env(), supervise = FALSE, package = FALSE, arch = "same", ... ) } \arguments{ \item{func}{Function object to call in the new R process. The function should be self-contained and only refer to other functions and use variables explicitly from other packages using the \code{::} notation. By default the environment of the function is set to \code{.GlobalEnv} before passing it to the child process. (See the \code{package} option if you want to keep the environment.) Because of this, it is good practice to create an anonymous function and pass that to \code{callr}, instead of passing a function object from a (base or other) package. In particular\preformatted{r(.libPaths) } does not work, because \code{.libPaths} is defined in a special environment, but\preformatted{r(function() .libPaths()) } works just fine.} \item{args}{Arguments to pass to the function. Must be a list.} \item{libpath}{The library path.} \item{repos}{The \code{repos} option. If \code{NULL}, then no \code{repos} option is set. This options is only used if \code{user_profile} or \code{system_profile} is set \code{FALSE}, as it is set using the system or the user profile.} \item{stdout}{The name of the file the standard output of the child R process will be written to. If the child process runs with the \code{--slave} option (the default), then the commands are not echoed and will not be shown in the standard output. Also note that you need to call \code{print()} explicitly to show the output of the command(s).} \item{stderr}{The name of the file the standard error of the child R process will be written to. In particular \code{message()} sends output to the standard error. If nothing was sent to the standard error, then this file will be empty. This argument can be the same file as \code{stdout}, in which case they will be correctly interleaved. If this is the string \code{"2>&1"}, then standard error is redirected to standard output.} \item{poll_connection}{Whether to have a control connection to the process. This is used to transmit messages from the subprocess to the main process.} \item{error}{What to do if the remote process throws an error. See details below.} \item{cmdargs}{Command line arguments to pass to the R process. Note that \code{c("-f", rscript)} is appended to this, \code{rscript} is the name of the script file to run. This contains a call to the supplied function and some error handling code.} \item{system_profile}{Whether to use the system profile file.} \item{user_profile}{Whether to use the user's profile file. If this is \code{"project"}, then only the profile from the working directory is used, but the \code{R_PROFILE_USER} environment variable and the user level profile are not. See also "Security considerations" below.} \item{env}{Environment variables to set for the child process.} \item{supervise}{Whether to register the process with a supervisor. If \code{TRUE}, the supervisor will ensure that the process is killed when the R process exits.} \item{package}{Whether to keep the environment of \code{func} when passing it to the other package. Possible values are: \itemize{ \item \code{FALSE}: reset the environment to \code{.GlobalEnv}. This is the default. \item \code{TRUE}: keep the environment as is. \item \code{pkg}: set the environment to the \code{pkg} package namespace. }} \item{arch}{Architecture to use in the child process, for multi-arch builds of R. By default the same as the main process. See \code{\link[=supported_archs]{supported_archs()}}. If it contains a forward or backward slash character, then it is taken as the path to the R executable. Note that on Windows you need the path to \code{Rterm.exe}.} \item{...}{Extra arguments are passed to the \link[processx:process]{processx::process} constructor.} } \value{ An \code{r_process} object, which inherits from \link{process}, so all \code{process} methods can be called on it, and in addition it also has a \code{get_result()} method to collect the result. } \description{ Starts evaluating an R function call in a background R process, and returns immediately. } \section{Security considerations}{ \code{callr} makes a copy of the user's \code{.Renviron} file and potentially of the local or user \code{.Rprofile}, in the session temporary directory. Avoid storing sensitive information such as passwords, in your environment file or your profile, otherwise this information will get scattered in various files, at least temporarily, until the subprocess finishes. You can use the keyring package to avoid passwords in plain files. } \examples{ \dontshow{if (FALSE) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} rx <- r_bg(function() 1 + 2) # wait until it is done rx$wait() rx$is_alive() rx$get_result() \dontshow{\}) # examplesIf} } callr/man/rcmd_copycat.Rd0000644000176200001440000000375013725376411015076 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/rcmd.R \name{rcmd_copycat} \alias{rcmd_copycat} \title{Call and \verb{R CMD} command, while mimicking the current R session} \usage{ rcmd_copycat( cmd, cmdargs = character(), libpath = .libPaths(), repos = getOption("repos"), env = character(), ... ) } \arguments{ \item{cmd}{Command to run. See \code{R --help} from the command line for the various commands. In the current version of R (3.2.4) these are: \code{BATCH}, \code{COMPILE}, \code{SHLIB}, \code{INSTALL}, \code{REMOVE}, \code{build}, \code{check}, \code{LINK}, \code{Rprof}, \code{Rdconv}, \code{Rd2pdf}, \code{Rd2txt}, \code{Stangle}, \code{Sweave}, \code{Rdiff}, \code{config}, \code{javareconf}, \code{rtags}.} \item{cmdargs}{Command line arguments.} \item{libpath}{The library path.} \item{repos}{The \code{repos} option. If \code{NULL}, then no \code{repos} option is set. This options is only used if \code{user_profile} or \code{system_profile} is set \code{FALSE}, as it is set using the system or the user profile.} \item{env}{Environment variables to set for the child process.} \item{...}{Additional arguments are passed to \code{\link[=rcmd]{rcmd()}}.} } \description{ This function is similar to \code{\link[=rcmd]{rcmd()}}, but it has slightly different defaults: \itemize{ \item The \code{repos} options is unchanged. \item No extra environment variables are defined. } } \section{Security considerations}{ \code{callr} makes a copy of the user's \code{.Renviron} file and potentially of the local or user \code{.Rprofile}, in the session temporary directory. Avoid storing sensitive information such as passwords, in your environment file or your profile, otherwise this information will get scattered in various files, at least temporarily, until the subprocess finishes. You can use the keyring package to avoid passwords in plain files. } \seealso{ Other R CMD commands: \code{\link{rcmd_bg}()}, \code{\link{rcmd}()} } \concept{R CMD commands} callr/man/rcmd_bg.Rd0000644000176200001440000000621313725376411014021 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/rcmd-bg.R \name{rcmd_bg} \alias{rcmd_bg} \title{Run an \verb{R CMD} command in the background} \usage{ rcmd_bg( cmd, cmdargs = character(), libpath = .libPaths(), stdout = "|", stderr = "|", poll_connection = TRUE, repos = default_repos(), system_profile = FALSE, user_profile = "project", env = rcmd_safe_env(), wd = ".", supervise = FALSE, ... ) } \arguments{ \item{cmd}{Command to run. See \code{R --help} from the command line for the various commands. In the current version of R (3.2.4) these are: \code{BATCH}, \code{COMPILE}, \code{SHLIB}, \code{INSTALL}, \code{REMOVE}, \code{build}, \code{check}, \code{LINK}, \code{Rprof}, \code{Rdconv}, \code{Rd2pdf}, \code{Rd2txt}, \code{Stangle}, \code{Sweave}, \code{Rdiff}, \code{config}, \code{javareconf}, \code{rtags}.} \item{cmdargs}{Command line arguments.} \item{libpath}{The library path.} \item{stdout}{Optionally a file name to send the standard output to.} \item{stderr}{Optionally a file name to send the standard error to. It may be the same as \code{stdout}, in which case standard error is redirected to standard output. It can also be the special string \code{"2>&1"}, in which case standard error will be redirected to standard output.} \item{poll_connection}{Whether to have a control connection to the process. This is used to transmit messages from the subprocess to the parent.} \item{repos}{The \code{repos} option. If \code{NULL}, then no \code{repos} option is set. This options is only used if \code{user_profile} or \code{system_profile} is set \code{FALSE}, as it is set using the system or the user profile.} \item{system_profile}{Whether to use the system profile file.} \item{user_profile}{Whether to use the user's profile file. If this is \code{"project"}, then only the profile from the working directory is used, but the \code{R_PROFILE_USER} environment variable and the user level profile are not. See also "Security considerations" below.} \item{env}{Environment variables to set for the child process.} \item{wd}{Working directory to use for running the command. Defaults to the current working directory.} \item{supervise}{Whether to register the process with a supervisor. If \code{TRUE}, the supervisor will ensure that the process is killed when the R process exits.} \item{...}{Extra arguments are passed to the \link[processx:process]{processx::process} constructor.} } \value{ It returns a \link{process} object. } \description{ The child process is started in the background, and the function return immediately. } \section{Security considerations}{ \code{callr} makes a copy of the user's \code{.Renviron} file and potentially of the local or user \code{.Rprofile}, in the session temporary directory. Avoid storing sensitive information such as passwords, in your environment file or your profile, otherwise this information will get scattered in various files, at least temporarily, until the subprocess finishes. You can use the keyring package to avoid passwords in plain files. } \seealso{ Other R CMD commands: \code{\link{rcmd_copycat}()}, \code{\link{rcmd}()} } \concept{R CMD commands} callr/man/new_callr_error.Rd0000644000176200001440000000117013725376411015600 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/error.R \name{new_callr_error} \alias{new_callr_error} \title{Create an error object} \usage{ new_callr_error(out, msg = NULL) } \arguments{ \item{out}{The object returned by \code{\link[=run]{run()}}.} \item{msg}{An extra message to add to the error message.} } \description{ There are two kinds of errors, both have class \code{callr_error}: \enumerate{ \item the first one is thrown after a timeout: \code{callr_timeout_error}. \item the second one is thrown after an R error (in the other session): \code{callr_status_error}. } } \keyword{internal} callr/man/rcmd_process_options.Rd0000644000176200001440000000165613725376411016670 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/options.R \name{rcmd_process_options} \alias{rcmd_process_options} \title{Create options for an \link{rcmd_process} object} \usage{ rcmd_process_options(...) } \arguments{ \item{...}{Options to override, named arguments.} } \value{ A list of options. \code{rcmd_process_options()} creates a set of options to initialize a new object from the \code{rcmd_process} class. Its arguments must be named, the names are used as option names. The options correspond to (some of) the arguments of the \code{\link[=rcmd]{rcmd()}} function. At least the \code{cmd} option must be specified, to select the \verb{R CMD} subcommand to run. Typically \code{cmdargs} is specified as well, to supply more arguments to \verb{R CMD}. } \description{ Create options for an \link{rcmd_process} object } \examples{ ## List all options and their default values: rcmd_process_options() } callr/man/r_copycat.Rd0000644000176200001440000000605413737555020014410 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/presets.R \name{r_copycat} \alias{r_copycat} \title{Run an R process that mimics the current R process} \usage{ r_copycat( func, args = list(), libpath = .libPaths(), repos = getOption("repos"), cmdargs = "--slave", system_profile = TRUE, user_profile = TRUE, env = character(), ... ) } \arguments{ \item{func}{Function object to call in the new R process. The function should be self-contained and only refer to other functions and use variables explicitly from other packages using the \code{::} notation. By default the environment of the function is set to \code{.GlobalEnv} before passing it to the child process. (See the \code{package} option if you want to keep the environment.) Because of this, it is good practice to create an anonymous function and pass that to \code{callr}, instead of passing a function object from a (base or other) package. In particular\preformatted{r(.libPaths) } does not work, because \code{.libPaths} is defined in a special environment, but\preformatted{r(function() .libPaths()) } works just fine.} \item{args}{Arguments to pass to the function. Must be a list.} \item{libpath}{The library path.} \item{repos}{The \code{repos} option. If \code{NULL}, then no \code{repos} option is set. This options is only used if \code{user_profile} or \code{system_profile} is set \code{FALSE}, as it is set using the system or the user profile.} \item{cmdargs}{Command line arguments to pass to the R process. Note that \code{c("-f", rscript)} is appended to this, \code{rscript} is the name of the script file to run. This contains a call to the supplied function and some error handling code.} \item{system_profile}{Whether to use the system profile file.} \item{user_profile}{Whether to use the user's profile file. If this is \code{"project"}, then only the profile from the working directory is used, but the \code{R_PROFILE_USER} environment variable and the user level profile are not. See also "Security considerations" below.} \item{env}{Environment variables to set for the child process.} \item{...}{Additional arguments are passed to \code{\link[=r]{r()}}.} } \description{ Differences to \code{\link[=r]{r()}}: \itemize{ \item No extra repositories are set up. \item The \code{--no-save}, \code{--no-restore} command line arguments are not used. (But \code{--slave} still is.) \item The system profile and the user profile are loaded. \item No extra environment variables are set up. } } \section{Security considerations}{ \code{callr} makes a copy of the user's \code{.Renviron} file and potentially of the local or user \code{.Rprofile}, in the session temporary directory. Avoid storing sensitive information such as passwords, in your environment file or your profile, otherwise this information will get scattered in various files, at least temporarily, until the subprocess finishes. You can use the keyring package to avoid passwords in plain files. } \seealso{ Other callr functions: \code{\link{r_vanilla}()}, \code{\link{r}()} } \concept{callr functions} callr/man/convert_and_check_my_args.Rd0000644000176200001440000000152613725376411017606 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/check.R \name{convert_and_check_my_args} \alias{convert_and_check_my_args} \title{Convert and check function arguments} \usage{ convert_and_check_my_args(options) } \arguments{ \item{options}{List of options.} } \description{ This function is used for all variants of \code{r} and \code{rcmd}. An argument name is only used to refer to one kind of object, to make this possible. } \details{ The benefit of having a single \code{options} object is to avoid passing around a lot of arguments all the time. The benefit of making this object internal (i.e. that the \code{r}, etc. functions have multiple arguments instead of a single \code{options} list), is that documentation and usage is more user friendly (e.g. command- completion works in the editor. } \keyword{internal} callr/man/rscript_process_options.Rd0000644000176200001440000000154013725376411017421 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/options.R \name{rscript_process_options} \alias{rscript_process_options} \title{Create options for an \link{rscript_process} object} \usage{ rscript_process_options(...) } \arguments{ \item{...}{Options to override, named arguments.} } \value{ A list of options. \code{rscript_process_options()} creates a set of options to initialize a new object from the \code{rscript_process} class. Its arguments must be named, the names are used as option names. The options correspond to (some of) the arguments of the \code{\link[=rscript]{rscript()}} function. At least the \code{script} option must be specified, the script file to run. } \description{ Create options for an \link{rscript_process} object } \examples{ ## List all options and their default values: rscript_process_options() } callr/man/get_result.Rd0000644000176200001440000000145713725376411014606 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/result.R \name{get_result} \alias{get_result} \title{Read the result object from the output file, or the error} \usage{ get_result(output, options) } \arguments{ \item{options}{The context, including all parameters.} \item{out}{List of the output object from \code{\link[=run]{run()}} and the name of the result file to read. For the error file, \code{.error} is appended to this.} } \value{ If no error happened, the result is returned. Otherwise we handle the error. } \description{ Even if an error happens, the output file might still exist, because \code{\link[=saveRDS]{saveRDS()}} creates the file before evaluating its object argument. So we need to check for the error file to decide if an error happened. } \keyword{internal} callr/man/rscript.Rd0000644000176200001440000001016713725376411014115 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/rscript.R \name{rscript} \alias{rscript} \title{Run an R script} \usage{ rscript( script, cmdargs = character(), libpath = .libPaths(), repos = default_repos(), stdout = NULL, stderr = NULL, poll_connection = TRUE, echo = FALSE, show = TRUE, callback = NULL, block_callback = NULL, spinner = FALSE, system_profile = FALSE, user_profile = "project", env = rcmd_safe_env(), timeout = Inf, wd = ".", fail_on_status = TRUE, color = TRUE, ... ) } \arguments{ \item{script}{Path of the script to run.} \item{cmdargs}{Command line arguments.} \item{libpath}{The library path.} \item{repos}{The \code{repos} option. If \code{NULL}, then no \code{repos} option is set. This options is only used if \code{user_profile} or \code{system_profile} is set \code{FALSE}, as it is set using the system or the user profile.} \item{stdout}{Optionally a file name to send the standard output to.} \item{stderr}{Optionally a file name to send the standard error to. It may be the same as \code{stdout}, in which case standard error is redirected to standard output. It can also be the special string \code{"2>&1"}, in which case standard error will be redirected to standard output.} \item{poll_connection}{Whether to have a control connection to the process. This is used to transmit messages from the subprocess to the parent.} \item{echo}{Whether to echo the complete command run by \code{rcmd}.} \item{show}{Logical, whether to show the standard output on the screen while the child process is running. Note that this is independent of the \code{stdout} and \code{stderr} arguments. The standard error is not shown currently.} \item{callback}{A function to call for each line of the standard output and standard error from the child process. It works together with the \code{show} option; i.e. if \code{show = TRUE}, and a callback is provided, then the output is shown of the screen, and the callback is also called.} \item{block_callback}{A function to call for each block of the standard output and standard error. This callback is not line oriented, i.e. multiple lines or half a line can be passed to the callback.} \item{spinner}{Whether to show a calming spinner on the screen while the child R session is running. By default it is shown if \code{show = TRUE} and the R session is interactive.} \item{system_profile}{Whether to use the system profile file.} \item{user_profile}{Whether to use the user's profile file. If this is \code{"project"}, then only the profile from the working directory is used, but the \code{R_PROFILE_USER} environment variable and the user level profile are not. See also "Security considerations" below.} \item{env}{Environment variables to set for the child process.} \item{timeout}{Timeout for the function call to finish. It can be a \link[base:difftime]{base::difftime} object, or a real number, meaning seconds. If the process does not finish before the timeout period expires, then a \code{system_command_timeout_error} error is thrown. \code{Inf} means no timeout.} \item{wd}{Working directory to use for running the command. Defaults to the current working directory.} \item{fail_on_status}{Whether to throw an R error if the command returns with a non-zero status code. By default no error is thrown.} \item{color}{Whether to use terminal colors in the child process, assuming they are active in the parent process.} \item{...}{Extra arguments are passed to \code{\link[processx:run]{processx::run()}}.} } \description{ It uses the \code{Rscript} program corresponding to the current R version, to run the script. It streams \code{stdout} and \code{stderr} of the process. } \section{Security considerations}{ \code{callr} makes a copy of the user's \code{.Renviron} file and potentially of the local or user \code{.Rprofile}, in the session temporary directory. Avoid storing sensitive information such as passwords, in your environment file or your profile, otherwise this information will get scattered in various files, at least temporarily, until the subprocess finishes. You can use the keyring package to avoid passwords in plain files. } callr/man/reexports.Rd0000644000176200001440000000075513725376436014473 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/processx-forward.R \docType{import} \name{reexports} \alias{reexports} \alias{run} \alias{process} \alias{poll} \title{Objects exported from other packages} \keyword{internal} \description{ These objects are imported from other packages. Follow the links below to see their documentation. \describe{ \item{processx}{\code{\link[processx]{poll}}, \code{\link[processx]{process}}, \code{\link[processx]{run}}} }} callr/man/r_session.Rd0000644000176200001440000005700614037526556014442 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/r-session.R \name{r_session} \alias{r_session} \title{External R Session} \description{ A permanent R session that runs in the background. This is an R6 class that extends the \link[processx:process]{processx::process} class. The process is started at the creation of the object, and then it can be used to evaluate R function calls, one at a time. } \examples{ \dontshow{if (FALSE) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} rs <- r_ression$new() rs$run(function() 1 + 2) rs$call(function() Sys.sleep(1)) rs$get_state() rs$poll_process(-1) rs$get_state() rs$read() \dontshow{\}) # examplesIf} } \section{Super class}{ \code{\link[processx:process]{processx::process}} -> \code{r_session} } \section{Public fields}{ \if{html}{\out{
}} \describe{ \item{\code{status}}{Status codes returned by \code{read()}.} } \if{html}{\out{
}} } \section{Methods}{ \subsection{Public methods}{ \itemize{ \item \href{#method-new}{\code{r_session$new()}} \item \href{#method-run}{\code{r_session$run()}} \item \href{#method-run_with_output}{\code{r_session$run_with_output()}} \item \href{#method-call}{\code{r_session$call()}} \item \href{#method-poll_process}{\code{r_session$poll_process()}} \item \href{#method-get_state}{\code{r_session$get_state()}} \item \href{#method-get_running_time}{\code{r_session$get_running_time()}} \item \href{#method-read}{\code{r_session$read()}} \item \href{#method-close}{\code{r_session$close()}} \item \href{#method-traceback}{\code{r_session$traceback()}} \item \href{#method-debug}{\code{r_session$debug()}} \item \href{#method-attach}{\code{r_session$attach()}} \item \href{#method-finalize}{\code{r_session$finalize()}} \item \href{#method-print}{\code{r_session$print()}} \item \href{#method-clone}{\code{r_session$clone()}} } } \if{html}{ \out{
Inherited methods} \itemize{ \item \out{}\href{../../processx/html/process.html#method-as_ps_handle}{\code{processx::process$as_ps_handle()}}\out{} \item \out{}\href{../../processx/html/process.html#method-format}{\code{processx::process$format()}}\out{} \item \out{}\href{../../processx/html/process.html#method-get_cmdline}{\code{processx::process$get_cmdline()}}\out{} \item \out{}\href{../../processx/html/process.html#method-get_cpu_times}{\code{processx::process$get_cpu_times()}}\out{} \item \out{}\href{../../processx/html/process.html#method-get_error_connection}{\code{processx::process$get_error_connection()}}\out{} \item \out{}\href{../../processx/html/process.html#method-get_error_file}{\code{processx::process$get_error_file()}}\out{} \item \out{}\href{../../processx/html/process.html#method-get_exe}{\code{processx::process$get_exe()}}\out{} \item \out{}\href{../../processx/html/process.html#method-get_exit_status}{\code{processx::process$get_exit_status()}}\out{} \item \out{}\href{../../processx/html/process.html#method-get_input_connection}{\code{processx::process$get_input_connection()}}\out{} \item \out{}\href{../../processx/html/process.html#method-get_input_file}{\code{processx::process$get_input_file()}}\out{} \item \out{}\href{../../processx/html/process.html#method-get_memory_info}{\code{processx::process$get_memory_info()}}\out{} \item \out{}\href{../../processx/html/process.html#method-get_name}{\code{processx::process$get_name()}}\out{} \item \out{}\href{../../processx/html/process.html#method-get_output_connection}{\code{processx::process$get_output_connection()}}\out{} \item \out{}\href{../../processx/html/process.html#method-get_output_file}{\code{processx::process$get_output_file()}}\out{} \item \out{}\href{../../processx/html/process.html#method-get_pid}{\code{processx::process$get_pid()}}\out{} \item \out{}\href{../../processx/html/process.html#method-get_poll_connection}{\code{processx::process$get_poll_connection()}}\out{} \item \out{}\href{../../processx/html/process.html#method-get_result}{\code{processx::process$get_result()}}\out{} \item \out{}\href{../../processx/html/process.html#method-get_start_time}{\code{processx::process$get_start_time()}}\out{} \item \out{}\href{../../processx/html/process.html#method-get_status}{\code{processx::process$get_status()}}\out{} \item \out{}\href{../../processx/html/process.html#method-get_username}{\code{processx::process$get_username()}}\out{} \item \out{}\href{../../processx/html/process.html#method-get_wd}{\code{processx::process$get_wd()}}\out{} \item \out{}\href{../../processx/html/process.html#method-has_error_connection}{\code{processx::process$has_error_connection()}}\out{} \item \out{}\href{../../processx/html/process.html#method-has_input_connection}{\code{processx::process$has_input_connection()}}\out{} \item \out{}\href{../../processx/html/process.html#method-has_output_connection}{\code{processx::process$has_output_connection()}}\out{} \item \out{}\href{../../processx/html/process.html#method-has_poll_connection}{\code{processx::process$has_poll_connection()}}\out{} \item \out{}\href{../../processx/html/process.html#method-interrupt}{\code{processx::process$interrupt()}}\out{} \item \out{}\href{../../processx/html/process.html#method-is_alive}{\code{processx::process$is_alive()}}\out{} \item \out{}\href{../../processx/html/process.html#method-is_incomplete_error}{\code{processx::process$is_incomplete_error()}}\out{} \item \out{}\href{../../processx/html/process.html#method-is_incomplete_output}{\code{processx::process$is_incomplete_output()}}\out{} \item \out{}\href{../../processx/html/process.html#method-is_supervised}{\code{processx::process$is_supervised()}}\out{} \item \out{}\href{../../processx/html/process.html#method-kill}{\code{processx::process$kill()}}\out{} \item \out{}\href{../../processx/html/process.html#method-kill_tree}{\code{processx::process$kill_tree()}}\out{} \item \out{}\href{../../processx/html/process.html#method-poll_io}{\code{processx::process$poll_io()}}\out{} \item \out{}\href{../../processx/html/process.html#method-read_all_error}{\code{processx::process$read_all_error()}}\out{} \item \out{}\href{../../processx/html/process.html#method-read_all_error_lines}{\code{processx::process$read_all_error_lines()}}\out{} \item \out{}\href{../../processx/html/process.html#method-read_all_output}{\code{processx::process$read_all_output()}}\out{} \item \out{}\href{../../processx/html/process.html#method-read_all_output_lines}{\code{processx::process$read_all_output_lines()}}\out{} \item \out{}\href{../../processx/html/process.html#method-read_error}{\code{processx::process$read_error()}}\out{} \item \out{}\href{../../processx/html/process.html#method-read_error_lines}{\code{processx::process$read_error_lines()}}\out{} \item \out{}\href{../../processx/html/process.html#method-read_output}{\code{processx::process$read_output()}}\out{} \item \out{}\href{../../processx/html/process.html#method-read_output_lines}{\code{processx::process$read_output_lines()}}\out{} \item \out{}\href{../../processx/html/process.html#method-resume}{\code{processx::process$resume()}}\out{} \item \out{}\href{../../processx/html/process.html#method-signal}{\code{processx::process$signal()}}\out{} \item \out{}\href{../../processx/html/process.html#method-supervise}{\code{processx::process$supervise()}}\out{} \item \out{}\href{../../processx/html/process.html#method-suspend}{\code{processx::process$suspend()}}\out{} \item \out{}\href{../../processx/html/process.html#method-wait}{\code{processx::process$wait()}}\out{} \item \out{}\href{../../processx/html/process.html#method-write_input}{\code{processx::process$write_input()}}\out{} } \out{
} } \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-new}{}}} \subsection{Method \code{new()}}{ creates a new R background process. It can wait for the process to start up (\code{wait = TRUE}), or return immediately, i.e. before the process is actually ready to run. In the latter case you may call the \code{poll_process()} method to make sure it is ready. \subsection{Usage}{ \if{html}{\out{
}}\preformatted{r_session$new(options = r_session_options(), wait = TRUE, wait_timeout = 3000)}\if{html}{\out{
}} } \subsection{Arguments}{ \if{html}{\out{
}} \describe{ \item{\code{options}}{A list of options created via \code{\link[=r_session_options]{r_session_options()}}.} \item{\code{wait}}{Whether to wait for the R process to start and be ready for running commands.} \item{\code{wait_timeout}}{Timeout for waiting for the R process to start, in milliseconds.} } \if{html}{\out{
}} } \subsection{Returns}{ An \code{r_session} object. } } \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-run}{}}} \subsection{Method \code{run()}}{ Similar to \code{\link[=r]{r()}}, but runs the function in a permanent background R session. It throws an error if the function call generated an error in the child process. \subsection{Usage}{ \if{html}{\out{
}}\preformatted{r_session$run(func, args = list(), package = FALSE)}\if{html}{\out{
}} } \subsection{Arguments}{ \if{html}{\out{
}} \describe{ \item{\code{func}}{Function object to call in the background R process. Please read the notes for the similar argument of \code{\link[=r]{r()}}.} \item{\code{args}}{Arguments to pass to the function. Must be a list.} \item{\code{package}}{Whether to keep the environment of \code{func} when passing it to the other package. Possible values are: \itemize{ \item \code{FALSE}: reset the environment to \code{.GlobalEnv}. This is the default. \item \code{TRUE}: keep the environment as is. \item \code{pkg}: set the environment to the \code{pkg} package namespace. }} } \if{html}{\out{
}} } \subsection{Returns}{ The return value of the R expression. } } \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-run_with_output}{}}} \subsection{Method \code{run_with_output()}}{ Similar to \verb{$run()}, but returns the standard output and error of the child process as well. It does not throw on errors, but returns a non-\code{NULL} \code{error} member in the result list. \subsection{Usage}{ \if{html}{\out{
}}\preformatted{r_session$run_with_output(func, args = list(), package = FALSE)}\if{html}{\out{
}} } \subsection{Arguments}{ \if{html}{\out{
}} \describe{ \item{\code{func}}{Function object to call in the background R process. Please read the notes for the similar argument of \code{\link[=r]{r()}}.} \item{\code{args}}{Arguments to pass to the function. Must be a list.} \item{\code{package}}{Whether to keep the environment of \code{func} when passing it to the other package. Possible values are: \itemize{ \item \code{FALSE}: reset the environment to \code{.GlobalEnv}. This is the default. \item \code{TRUE}: keep the environment as is. \item \code{pkg}: set the environment to the \code{pkg} package namespace. }} } \if{html}{\out{
}} } \subsection{Returns}{ A list with the following entries. \itemize{ \item \code{result}: The value returned by \code{func}. On error this is \code{NULL}. \item \code{stdout}: The standard output of the process while evaluating \item \code{stderr}: The standard error of the process while evaluating the \code{func} call. \item \code{error}: On error it contains an error object, that contains the error thrown in the subprocess. Otherwise it is \code{NULL}. \item \code{code}, \code{message}: These fields are used by call internally and you can ignore them. } } } \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-call}{}}} \subsection{Method \code{call()}}{ Starts running a function in the background R session, and returns immediately. To check if the function is done, call the \code{poll_process()} method. \subsection{Usage}{ \if{html}{\out{
}}\preformatted{r_session$call(func, args = list(), package = FALSE)}\if{html}{\out{
}} } \subsection{Arguments}{ \if{html}{\out{
}} \describe{ \item{\code{func}}{Function object to call in the background R process. Please read the notes for the similar argument of \code{\link[=r]{r()}}.} \item{\code{args}}{Arguments to pass to the function. Must be a list.} \item{\code{package}}{Whether to keep the environment of \code{func} when passing it to the other package. Possible values are: \itemize{ \item \code{FALSE}: reset the environment to \code{.GlobalEnv}. This is the default. \item \code{TRUE}: keep the environment as is. \item \code{pkg}: set the environment to the \code{pkg} package namespace. }} } \if{html}{\out{
}} } } \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-poll_process}{}}} \subsection{Method \code{poll_process()}}{ Poll the R session with a timeout. If the session has finished the computation, it returns with \code{"ready"}. If the timeout is reached, it returns with \code{"timeout"}. \subsection{Usage}{ \if{html}{\out{
}}\preformatted{r_session$poll_process(timeout)}\if{html}{\out{
}} } \subsection{Arguments}{ \if{html}{\out{
}} \describe{ \item{\code{timeout}}{Timeout period in milliseconds.} } \if{html}{\out{
}} } \subsection{Returns}{ Character string \code{"ready"} or \code{"timeout"}. } } \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-get_state}{}}} \subsection{Method \code{get_state()}}{ Return the state of the R session. \subsection{Usage}{ \if{html}{\out{
}}\preformatted{r_session$get_state()}\if{html}{\out{
}} } \subsection{Returns}{ Possible values: \itemize{ \item \code{"starting"}: starting up, \item \code{"idle"}: ready to compute, \item \code{"busy"}: computing right now, \item \code{"finished"}: the R process has finished. } } } \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-get_running_time}{}}} \subsection{Method \code{get_running_time()}}{ Returns the elapsed time since the R process has started, and the elapsed time since the current computation has started. The latter is \code{NA} if there is no active computation. \subsection{Usage}{ \if{html}{\out{
}}\preformatted{r_session$get_running_time()}\if{html}{\out{
}} } \subsection{Returns}{ Named vector of \code{POSIXct} objects. The names are \code{"total"} and \code{"current"}. } } \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-read}{}}} \subsection{Method \code{read()}}{ Reads an event from the child process, if there is one available. Events might signal that the function call has finished, or they can be progress report events. This is a low level function that you only need to use if you want to process events (messages) from the R session manually. \subsection{Usage}{ \if{html}{\out{
}}\preformatted{r_session$read()}\if{html}{\out{
}} } \subsection{Returns}{ \code{NULL} if no events are available. Otherwise a named list, which is also a \code{callr_session_result} object. The list always has a \code{code} entry which is the type of the event. See also \code{r_session$public_fields$status} for symbolic names of the event types. \itemize{ \item \code{200}: (\code{DONE}) The computation is done, and the event includes the result, in the same form as for the \code{run()} method. \item \code{201}: (\code{STARTED}) An R session that was in 'starting' state is ready to go. \item \code{202}: (\code{ATTACH_DONE}) Used by the \code{attach()} method. \item \code{301}: (\code{MSG}) A message from the subprocess. The message is a condition object with class \code{callr_message}. (It typically has other classes, e.g. \code{cli_message} for output from the cli package.) \item \code{500}: (\code{EXITED}) The R session finished cleanly. This means that the evaluated expression quit R. \item \code{501}: (\code{CRASHED}) The R session crashed or was killed. \item \code{502}: (\code{CLOSED}) The R session closed its end of the connection that callr uses for communication. } } } \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-close}{}}} \subsection{Method \code{close()}}{ Terminate the current computation and the R process. The session object will be in \code{"finished"} state after this. \subsection{Usage}{ \if{html}{\out{
}}\preformatted{r_session$close(grace = 1000)}\if{html}{\out{
}} } \subsection{Arguments}{ \if{html}{\out{
}} \describe{ \item{\code{grace}}{Grace period in milliseconds, to wait for the subprocess to exit cleanly, after its standard input is closed. If the process is still running after this period, it will be killed.} } \if{html}{\out{
}} } } \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-traceback}{}}} \subsection{Method \code{traceback()}}{ The \code{traceback()} method can be used after an error in the R subprocess. It is equivalent to the \code{\link[base:traceback]{base::traceback()}} call, in the subprocess. \subsection{Usage}{ \if{html}{\out{
}}\preformatted{r_session$traceback()}\if{html}{\out{
}} } \subsection{Returns}{ The same output as from \code{\link[base:traceback]{base::traceback()}} } } \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-debug}{}}} \subsection{Method \code{debug()}}{ Interactive debugger to inspect the dumped frames in the subprocess, after an error. See more at \link{r_session_debug}. \subsection{Usage}{ \if{html}{\out{
}}\preformatted{r_session$debug()}\if{html}{\out{
}} } } \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-attach}{}}} \subsection{Method \code{attach()}}{ Experimental function that provides a REPL (Read-Eval-Print-Loop) to the subprocess. \subsection{Usage}{ \if{html}{\out{
}}\preformatted{r_session$attach()}\if{html}{\out{
}} } } \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-finalize}{}}} \subsection{Method \code{finalize()}}{ Finalizer that is called when garbage collecting an \code{r_session} object, to clean up temporary files. \subsection{Usage}{ \if{html}{\out{
}}\preformatted{r_session$finalize()}\if{html}{\out{
}} } } \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-print}{}}} \subsection{Method \code{print()}}{ Print method for an \code{r_session}. \subsection{Usage}{ \if{html}{\out{
}}\preformatted{r_session$print(...)}\if{html}{\out{
}} } \subsection{Arguments}{ \if{html}{\out{
}} \describe{ \item{\code{...}}{Arguments are not used currently.} } \if{html}{\out{
}} } } \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-clone}{}}} \subsection{Method \code{clone()}}{ The objects of this class are cloneable with this method. \subsection{Usage}{ \if{html}{\out{
}}\preformatted{r_session$clone(deep = FALSE)}\if{html}{\out{
}} } \subsection{Arguments}{ \if{html}{\out{
}} \describe{ \item{\code{deep}}{Whether to make a deep clone.} } \if{html}{\out{
}} } } } callr/man/rcmd_safe_env.Rd0000644000176200001440000000300614023345544015207 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/rcmd.R \name{rcmd_safe_env} \alias{rcmd_safe_env} \title{\code{rcmd_safe_env} returns a set of environment variables that are more appropriate for \code{\link[=rcmd_safe]{rcmd_safe()}}. It is exported to allow manipulating these variables (e.g. add an extra one), before passing them to the \code{\link[=rcmd]{rcmd()}} functions.} \usage{ rcmd_safe_env() } \value{ A named character vector of environment variables. } \description{ It currently has the following variables: \itemize{ \item \code{CYGWIN="nodosfilewarning"}: On Windows, do not warn about MS-DOS style file names. \item \code{R_TESTS=""} This variable is set by \verb{R CMD check}, and makes the child R process load a startup file at startup, from the current working directory, that is assumed to be the \verb{/test} directory of the package being checked. If the current working directory is changed to something else (as it typically is by \code{testthat}, then R cannot start. Setting it to the empty string ensures that \code{callr} can be used from unit tests. \item \code{R_BROWSER="false"}: typically we don't want to start up a browser from the child R process. \item \code{R_PDFVIEWER="false"}: similarly for the PDF viewer. } } \details{ Note that \code{callr} also sets the \code{R_ENVIRON}, \code{R_ENVIRON_USER}, \code{R_PROFILE} and \code{R_PROFILE_USER} environment variables appropriately, unless these are set by the user in the \code{env} argument of the \code{r}, etc. calls. } callr/man/supported_archs.Rd0000644000176200001440000000102314026375646015627 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/utils.R \name{supported_archs} \alias{supported_archs} \title{Find supported sub-architectures for the current R installation} \usage{ supported_archs() } \value{ Character vector of supported architectures. If the current R build is not a multi-architecture build, then an empty string scalar is returned. } \description{ This function uses a heuristic, which might fail, so its result should be taken as a best guess. } \examples{ supported_archs() } callr/man/r_process.Rd0000644000176200001440000003235514037526556014435 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/r-process.R \name{r_process} \alias{r_process} \title{External R Process} \description{ An R process that runs in the background. This is an R6 class that extends the \link[processx:process]{processx::process} class. The process starts in the background, evaluates an R function call, and then quits. } \examples{ \dontshow{if (FALSE) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} ## List all options and their default values: r_process_options() ## Start an R process in the background, wait for it, get result opts <- r_process_options(func = function() 1 + 1) rp <- r_process$new(opts) rp$wait() rp$get_result() \dontshow{\}) # examplesIf} } \section{Super class}{ \code{\link[processx:process]{processx::process}} -> \code{r_process} } \section{Methods}{ \subsection{Public methods}{ \itemize{ \item \href{#method-new}{\code{r_process$new()}} \item \href{#method-get_result}{\code{r_process$get_result()}} \item \href{#method-finalize}{\code{r_process$finalize()}} \item \href{#method-clone}{\code{r_process$clone()}} } } \if{html}{ \out{
Inherited methods} \itemize{ \item \out{}\href{../../processx/html/process.html#method-as_ps_handle}{\code{processx::process$as_ps_handle()}}\out{} \item \out{}\href{../../processx/html/process.html#method-format}{\code{processx::process$format()}}\out{} \item \out{}\href{../../processx/html/process.html#method-get_cmdline}{\code{processx::process$get_cmdline()}}\out{} \item \out{}\href{../../processx/html/process.html#method-get_cpu_times}{\code{processx::process$get_cpu_times()}}\out{} \item \out{}\href{../../processx/html/process.html#method-get_error_connection}{\code{processx::process$get_error_connection()}}\out{} \item \out{}\href{../../processx/html/process.html#method-get_error_file}{\code{processx::process$get_error_file()}}\out{} \item \out{}\href{../../processx/html/process.html#method-get_exe}{\code{processx::process$get_exe()}}\out{} \item \out{}\href{../../processx/html/process.html#method-get_exit_status}{\code{processx::process$get_exit_status()}}\out{} \item \out{}\href{../../processx/html/process.html#method-get_input_connection}{\code{processx::process$get_input_connection()}}\out{} \item \out{}\href{../../processx/html/process.html#method-get_input_file}{\code{processx::process$get_input_file()}}\out{} \item \out{}\href{../../processx/html/process.html#method-get_memory_info}{\code{processx::process$get_memory_info()}}\out{} \item \out{}\href{../../processx/html/process.html#method-get_name}{\code{processx::process$get_name()}}\out{} \item \out{}\href{../../processx/html/process.html#method-get_output_connection}{\code{processx::process$get_output_connection()}}\out{} \item \out{}\href{../../processx/html/process.html#method-get_output_file}{\code{processx::process$get_output_file()}}\out{} \item \out{}\href{../../processx/html/process.html#method-get_pid}{\code{processx::process$get_pid()}}\out{} \item \out{}\href{../../processx/html/process.html#method-get_poll_connection}{\code{processx::process$get_poll_connection()}}\out{} \item \out{}\href{../../processx/html/process.html#method-get_start_time}{\code{processx::process$get_start_time()}}\out{} \item \out{}\href{../../processx/html/process.html#method-get_status}{\code{processx::process$get_status()}}\out{} \item \out{}\href{../../processx/html/process.html#method-get_username}{\code{processx::process$get_username()}}\out{} \item \out{}\href{../../processx/html/process.html#method-get_wd}{\code{processx::process$get_wd()}}\out{} \item \out{}\href{../../processx/html/process.html#method-has_error_connection}{\code{processx::process$has_error_connection()}}\out{} \item \out{}\href{../../processx/html/process.html#method-has_input_connection}{\code{processx::process$has_input_connection()}}\out{} \item \out{}\href{../../processx/html/process.html#method-has_output_connection}{\code{processx::process$has_output_connection()}}\out{} \item \out{}\href{../../processx/html/process.html#method-has_poll_connection}{\code{processx::process$has_poll_connection()}}\out{} \item \out{}\href{../../processx/html/process.html#method-interrupt}{\code{processx::process$interrupt()}}\out{} \item \out{}\href{../../processx/html/process.html#method-is_alive}{\code{processx::process$is_alive()}}\out{} \item \out{}\href{../../processx/html/process.html#method-is_incomplete_error}{\code{processx::process$is_incomplete_error()}}\out{} \item \out{}\href{../../processx/html/process.html#method-is_incomplete_output}{\code{processx::process$is_incomplete_output()}}\out{} \item \out{}\href{../../processx/html/process.html#method-is_supervised}{\code{processx::process$is_supervised()}}\out{} \item \out{}\href{../../processx/html/process.html#method-kill}{\code{processx::process$kill()}}\out{} \item \out{}\href{../../processx/html/process.html#method-kill_tree}{\code{processx::process$kill_tree()}}\out{} \item \out{}\href{../../processx/html/process.html#method-poll_io}{\code{processx::process$poll_io()}}\out{} \item \out{}\href{../../processx/html/process.html#method-print}{\code{processx::process$print()}}\out{} \item \out{}\href{../../processx/html/process.html#method-read_all_error}{\code{processx::process$read_all_error()}}\out{} \item \out{}\href{../../processx/html/process.html#method-read_all_error_lines}{\code{processx::process$read_all_error_lines()}}\out{} \item \out{}\href{../../processx/html/process.html#method-read_all_output}{\code{processx::process$read_all_output()}}\out{} \item \out{}\href{../../processx/html/process.html#method-read_all_output_lines}{\code{processx::process$read_all_output_lines()}}\out{} \item \out{}\href{../../processx/html/process.html#method-read_error}{\code{processx::process$read_error()}}\out{} \item \out{}\href{../../processx/html/process.html#method-read_error_lines}{\code{processx::process$read_error_lines()}}\out{} \item \out{}\href{../../processx/html/process.html#method-read_output}{\code{processx::process$read_output()}}\out{} \item \out{}\href{../../processx/html/process.html#method-read_output_lines}{\code{processx::process$read_output_lines()}}\out{} \item \out{}\href{../../processx/html/process.html#method-resume}{\code{processx::process$resume()}}\out{} \item \out{}\href{../../processx/html/process.html#method-signal}{\code{processx::process$signal()}}\out{} \item \out{}\href{../../processx/html/process.html#method-supervise}{\code{processx::process$supervise()}}\out{} \item \out{}\href{../../processx/html/process.html#method-suspend}{\code{processx::process$suspend()}}\out{} \item \out{}\href{../../processx/html/process.html#method-wait}{\code{processx::process$wait()}}\out{} \item \out{}\href{../../processx/html/process.html#method-write_input}{\code{processx::process$write_input()}}\out{} } \out{
} } \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-new}{}}} \subsection{Method \code{new()}}{ Start a new R process in the background. \subsection{Usage}{ \if{html}{\out{
}}\preformatted{r_process$new(options)}\if{html}{\out{
}} } \subsection{Arguments}{ \if{html}{\out{
}} \describe{ \item{\code{options}}{A list of options created via \code{\link[=r_process_options]{r_process_options()}}.} } \if{html}{\out{
}} } \subsection{Returns}{ A new \code{r_process} object. } } \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-get_result}{}}} \subsection{Method \code{get_result()}}{ Return the result, an R object, from a finished background R process. If the process has not finished yet, it throws an error. (You can use \code{wait()} method (see \link[processx:process]{processx::process}) to wait for the process to finish, optionally with a timeout.) You can also use \code{\link[processx:poll]{processx::poll()}} to wait for the end of the process, together with other processes or events. \subsection{Usage}{ \if{html}{\out{
}}\preformatted{r_process$get_result()}\if{html}{\out{
}} } \subsection{Returns}{ The return value of the R expression evaluated in the R process. } } \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-finalize}{}}} \subsection{Method \code{finalize()}}{ Clean up temporary files once an R process has finished and its handle is garbage collected. \subsection{Usage}{ \if{html}{\out{
}}\preformatted{r_process$finalize()}\if{html}{\out{
}} } } \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-clone}{}}} \subsection{Method \code{clone()}}{ The objects of this class are cloneable with this method. \subsection{Usage}{ \if{html}{\out{
}}\preformatted{r_process$clone(deep = FALSE)}\if{html}{\out{
}} } \subsection{Arguments}{ \if{html}{\out{
}} \describe{ \item{\code{deep}}{Whether to make a deep clone.} } \if{html}{\out{
}} } } } callr/man/rcmd.Rd0000644000176200001440000001267114037526556013362 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/rcmd.R \name{rcmd} \alias{rcmd} \alias{rcmd_safe} \title{Run an \verb{R CMD} command} \usage{ rcmd( cmd, cmdargs = character(), libpath = .libPaths(), repos = default_repos(), stdout = NULL, stderr = NULL, poll_connection = TRUE, echo = FALSE, show = FALSE, callback = NULL, block_callback = NULL, spinner = show && interactive(), system_profile = FALSE, user_profile = "project", env = rcmd_safe_env(), timeout = Inf, wd = ".", fail_on_status = FALSE, ... ) rcmd_safe( cmd, cmdargs = character(), libpath = .libPaths(), repos = default_repos(), stdout = NULL, stderr = NULL, poll_connection = TRUE, echo = FALSE, show = FALSE, callback = NULL, block_callback = NULL, spinner = show && interactive(), system_profile = FALSE, user_profile = "project", env = rcmd_safe_env(), timeout = Inf, wd = ".", fail_on_status = FALSE, ... ) } \arguments{ \item{cmd}{Command to run. See \code{R --help} from the command line for the various commands. In the current version of R (3.2.4) these are: \code{BATCH}, \code{COMPILE}, \code{SHLIB}, \code{INSTALL}, \code{REMOVE}, \code{build}, \code{check}, \code{LINK}, \code{Rprof}, \code{Rdconv}, \code{Rd2pdf}, \code{Rd2txt}, \code{Stangle}, \code{Sweave}, \code{Rdiff}, \code{config}, \code{javareconf}, \code{rtags}.} \item{cmdargs}{Command line arguments.} \item{libpath}{The library path.} \item{repos}{The \code{repos} option. If \code{NULL}, then no \code{repos} option is set. This options is only used if \code{user_profile} or \code{system_profile} is set \code{FALSE}, as it is set using the system or the user profile.} \item{stdout}{Optionally a file name to send the standard output to.} \item{stderr}{Optionally a file name to send the standard error to. It may be the same as \code{stdout}, in which case standard error is redirected to standard output. It can also be the special string \code{"2>&1"}, in which case standard error will be redirected to standard output.} \item{poll_connection}{Whether to have a control connection to the process. This is used to transmit messages from the subprocess to the parent.} \item{echo}{Whether to echo the complete command run by \code{rcmd}.} \item{show}{Logical, whether to show the standard output on the screen while the child process is running. Note that this is independent of the \code{stdout} and \code{stderr} arguments. The standard error is not shown currently.} \item{callback}{A function to call for each line of the standard output and standard error from the child process. It works together with the \code{show} option; i.e. if \code{show = TRUE}, and a callback is provided, then the output is shown of the screen, and the callback is also called.} \item{block_callback}{A function to call for each block of the standard output and standard error. This callback is not line oriented, i.e. multiple lines or half a line can be passed to the callback.} \item{spinner}{Whether to show a calming spinner on the screen while the child R session is running. By default it is shown if \code{show = TRUE} and the R session is interactive.} \item{system_profile}{Whether to use the system profile file.} \item{user_profile}{Whether to use the user's profile file. If this is \code{"project"}, then only the profile from the working directory is used, but the \code{R_PROFILE_USER} environment variable and the user level profile are not. See also "Security considerations" below.} \item{env}{Environment variables to set for the child process.} \item{timeout}{Timeout for the function call to finish. It can be a \link[base:difftime]{base::difftime} object, or a real number, meaning seconds. If the process does not finish before the timeout period expires, then a \code{system_command_timeout_error} error is thrown. \code{Inf} means no timeout.} \item{wd}{Working directory to use for running the command. Defaults to the current working directory.} \item{fail_on_status}{Whether to throw an R error if the command returns with a non-zero status code. By default no error is thrown.} \item{...}{Extra arguments are passed to \code{\link[processx:run]{processx::run()}}.} } \value{ A list with the command line \verb{$command}), standard output (\verb{$stdout}), standard error (\code{stderr}), exit status (\verb{$status}) of the external \verb{R CMD} command, and whether a timeout was reached (\verb{$timeout}). } \description{ Run an \verb{R CMD} command form within R. This will usually start another R process, from a shell script. } \details{ Starting from \code{callr} 2.0.0, \code{rcmd()} has safer defaults, the same as the \code{rcmd_safe()} default values. Use \code{\link[=rcmd_copycat]{rcmd_copycat()}} for the old defaults. } \section{Security considerations}{ \code{callr} makes a copy of the user's \code{.Renviron} file and potentially of the local or user \code{.Rprofile}, in the session temporary directory. Avoid storing sensitive information such as passwords, in your environment file or your profile, otherwise this information will get scattered in various files, at least temporarily, until the subprocess finishes. You can use the keyring package to avoid passwords in plain files. } \examples{ \dontshow{if (FALSE) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} rcmd("config", "CC") \dontshow{\}) # examplesIf} } \seealso{ Other R CMD commands: \code{\link{rcmd_bg}()}, \code{\link{rcmd_copycat}()} } \concept{R CMD commands} callr/man/callr.Rd0000644000176200001440000000154214013513445013510 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/package.R \docType{package} \name{callr} \alias{callr} \alias{callr-package} \title{Call R from R} \description{ It is sometimes useful to perform a computation in a separate R process, without affecting the current R process at all. This packages does exactly that. } \seealso{ Useful links: \itemize{ \item \url{https://callr.r-lib.org} \item \url{https://github.com/r-lib/callr#readme} \item Report bugs at \url{https://github.com/r-lib/callr/issues} } } \author{ \strong{Maintainer}: Gábor Csárdi \email{csardi.gabor@gmail.com} (\href{https://orcid.org/0000-0001-7098-9676}{ORCID}) [copyright holder] Authors: \itemize{ \item Winston Chang } Other contributors: \itemize{ \item RStudio [copyright holder, funder] \item Mango Solutions [copyright holder, funder] } } callr/DESCRIPTION0000644000176200001440000000226414037561432013066 0ustar liggesusersPackage: callr Title: Call R from R Version: 3.7.0 Authors@R: c( person("Gábor", "Csárdi", role = c("aut", "cre", "cph"), email = "csardi.gabor@gmail.com", comment = c(ORCID = "0000-0001-7098-9676")), person("Winston", "Chang", role = "aut"), person("RStudio", role = c("cph", "fnd")), person("Mango Solutions", role = c("cph", "fnd"))) Description: It is sometimes useful to perform a computation in a separate R process, without affecting the current R process at all. This packages does exactly that. License: MIT + file LICENSE URL: https://callr.r-lib.org, https://github.com/r-lib/callr#readme BugReports: https://github.com/r-lib/callr/issues RoxygenNote: 7.1.1.9001 Imports: processx (>= 3.5.0), R6, utils Suggests: cli, covr, ps, rprojroot, spelling, testthat, withr (>= 2.3.0) Encoding: UTF-8 Language: en-US NeedsCompilation: no Packaged: 2021-04-20 12:39:23 UTC; gaborcsardi Author: Gábor Csárdi [aut, cre, cph] (), Winston Chang [aut], RStudio [cph, fnd], Mango Solutions [cph, fnd] Maintainer: Gábor Csárdi Repository: CRAN Date/Publication: 2021-04-20 14:20:10 UTC callr/tests/0000755000176200001440000000000013737653217012527 5ustar liggesuserscallr/tests/testthat/0000755000176200001440000000000014037561432014356 5ustar liggesuserscallr/tests/testthat/test-rcmd-bg.R0000644000176200001440000000121213612550263016763 0ustar liggesusers context("rcmd_bg") test_that("rcmd_bg runs", { out1 <- gsub("\r?\n", "", rcmd("config", "CC")$stdout) x <- rcmd_bg("config", "CC") x$wait() out2 <- x$read_output_lines() expect_equal(out1, out2) expect_equal(x$get_exit_status(), 0) rm(x) gc() }) test_that("r_cmd can be killed", { cat("Sys.sleep(5)", file = tmp <- tempfile()) on.exit(try_silently(unlink(tmp)), add = TRUE) x <- rcmd_bg("BATCH", tmp) expect_true(x$is_alive()) x$kill() expect_false(x$is_alive()) rm(x) gc() }) test_that("r_cmd errors", { x <- rcmd_bg("config", "axaxaxaxax") x$wait() expect_equal(x$get_exit_status(), 1) rm(x) gc() }) callr/tests/testthat/test-rcmd.R0000644000176200001440000000502414025755433016407 0ustar liggesusers context("rcmd") test_that("rcmd works", { expect_equal(rcmd("config", "CC")$status, 0) expect_match(rcmd("config", "CC")$stdout, ".") gc() }) test_that("rcmd show works", { expect_output(rcmd("config", "CC", show = TRUE), ".") gc() }) test_that("rcmd echo works", { expect_output(rcmd("config", "CC", echo = TRUE), "config\\s+CC") gc() }) test_that("rcmd_safe", { expect_equal(rcmd_safe("config", "CC")$status, 0) gc() }) test_that("wd argument", { tmp <- tempfile(fileext = ".R") tmpout <- paste0(tmp, "out") cat("print(getwd())", file = tmp) mywd <- getwd() rcmd("BATCH", c(tmp, tmpout), wd = tempdir()) expect_equal(mywd, getwd()) expect_match( paste(readLines(tmpout), collapse = "\n"), basename(tempdir()) ) gc() }) test_that("fail_on_status", { rand <- tempfile() expect_error( withr::with_dir( tempdir(), rcmd("BATCH", rand, fail_on_status = TRUE)), "System command .* failed|System command error", class = "system_command_status_error" ) expect_silent( out <- withr::with_dir( tempdir(), rcmd("BATCH", rand, fail_on_status = FALSE)) ) expect_true(out$status != 0) gc() }) test_that("command is included in result", { res <- rcmd_safe("config", "CC") expect_false(is.null(res$command)) expect_true(is.character(res$command)) gc() }) test_that("stderr -> stdout", { lib <- test_temp_dir() pkg <- test_temp_dir() file.copy(test_path("fixtures/D1"), file.path(pkg, "DESCRIPTION")) out <- rcmd("INSTALL", c("-l", lib, pkg)) expect_match(out$stdout, "No man pages found", useBytes = TRUE) expect_match(out$stderr, "installing help indices") out2 <- rcmd("INSTALL", c("-l", lib, pkg), stderr = "2>&1") expect_equal(out2$status, 0L) expect_match( out2$stdout, "installing.*No man pages found.*testing if installed package") expect_equal(out2$stderr, "") out3 <- test_temp_file(create = FALSE) rcmd("INSTALL", c("-l", lib, pkg), stdout = out3, stderr = out3) expect_match( readChar(out3, nchars = file.info(out3)$size), "installing.*No man pages found.*testing if installed package") gc() }) test_that("cleans up temp files", { skip_on_cran() rc <- function() { library(callr) scriptfile <- tempfile(fileext = ".R") old <- dir(tempdir(), pattern = "^callr-") result <- callr::rcmd("config", "CC") new <- setdiff(dir(tempdir(), "^callr-"), old) list(result = result, new = new) } out <- r(rc) expect_identical(out$result$status, 0L) expect_identical(out$new, character()) }) callr/tests/testthat/test-presets.R0000644000176200001440000000153613612550263017146 0ustar liggesusers context("presets") test_that("r", { withr::with_options( list(repos = "foobar"), expect_equal( r_copycat(function() getOption("repos"), user_profile = FALSE, system_profile = FALSE ), "foobar" ) ) gc() }) ## Need to supply libpath for covr... test_that("r_vanilla", { expect_equal( r_vanilla(function() getOption("repos"), libpath = .libPaths()), c(CRAN = "@CRAN@") ) gc() }) test_that("r_safe", { expect_equal( r_safe(function() Sys.getenv("R_TESTS")), "" ) gc() }) ## https://github.com/r-lib/callr/issues/66 test_that("names of getOption('repos') are preserved", { repos <- withr::with_options( list(repos = c(foo = "bar")), callr::r(function() getOption("repos")) ) expect_false(is.null(names(repos))) expect_identical("foo", names(repos)[[1]]) gc() }) callr/tests/testthat/fixtures/0000755000176200001440000000000013612550263016225 5ustar liggesuserscallr/tests/testthat/fixtures/script.R0000644000176200001440000000004313612550263017651 0ustar liggesusers cat("stdout\n") message("stderr") callr/tests/testthat/fixtures/D10000644000176200001440000000102213612550263016407 0ustar liggesusersPackage: Foobar Title: Foo Foo Bar Bar Version: 1.0.1 Authors@R: c( person("Gábor", "Csárdi", role = c("aut", "cre", "cph"), email = "csardi.gabor@gmail.com", comment = c(ORCID = "0000-0001-7098-9676"))) Description: Enim laborum enim non aliquip tempor voluptate consectetur reprehenderit pariatur velit laborum sit culpa cillum. Tempor ullamco tempor aliqua aliquip commodo non cupidatat non culpa. Dolor fugiat aute et consectetur in. License: MIT + file LICENSE LazyData: true Encoding: UTF-8 callr/tests/testthat/fixtures/csomag/0000755000176200001440000000000013612550263017476 5ustar liggesuserscallr/tests/testthat/fixtures/csomag/NAMESPACE0000644000176200001440000000000013612550263020703 0ustar liggesuserscallr/tests/testthat/fixtures/csomag/DESCRIPTION0000644000176200001440000000054113612550263021204 0ustar liggesusersPackage: csomag Title: Test Package for 'callr' Version: 0.0.0.9999 Authors@R: c( person("Gábor", "Csárdi", role = c("aut", "cre", "cph"), email = "csardi.gabor@gmail.com", comment = c(ORCID = "0000-0001-7098-9676"))) Description: Test package for 'callr'. License: MIT + file LICENSE LazyData: true Encoding: UTF-8 Language: en-US callr/tests/testthat/fixtures/csomag/R/0000755000176200001440000000000013612550263017677 5ustar liggesuserscallr/tests/testthat/fixtures/csomag/R/libpath.R0000644000176200001440000000075513612550263021454 0ustar liggesusers file_from_env <- function(var) { if (file.exists(f <- Sys.getenv(var))) readLines(f) } if (Sys.getenv("CALLR_DUMP_HERE") != "") { saveRDS( list( env = Sys.getenv(), libpaths = .libPaths(), search = search(), R_ENVIRON = file_from_env("R_ENVIRON"), R_ENVIRON_USER = file_from_env("R_ENVIRON_USER"), R_PROFILE = file_from_env("R_PROFILE"), R_PROFILE_USER = file_from_env("R_PROFILE_USER") ), file = Sys.getenv("CALLR_DUMP_HERE") ) } callr/tests/testthat/fixtures/script2.R0000644000176200001440000000011213612550263017730 0ustar liggesuserscat("out1") message("err1", appendLF = FALSE) cat("out2") message("err2") callr/tests/testthat/fixtures/simple.txt0000644000176200001440000000002113612550263020250 0ustar liggesuserssimple text file callr/tests/testthat/test-callback.R0000644000176200001440000000103513612550263017207 0ustar liggesusers context("callbacks") test_that("show option works", { expect_output( r(function() print("hello"), show = TRUE), "hello" ) gc() }) test_that("callbacks work", { out <- NULL r(function() cat("hello\n"), callback = function(x) out <<- x) expect_equal(out, "hello") gc() }) test_that("show and callbacks at the same time", { out <- NULL expect_output( r( function() cat("hello\n"), show = TRUE, callback = function(x) out <<- x ), "hello" ) expect_equal(out, "hello") gc() }) callr/tests/testthat/test-libpath.R0000644000176200001440000001404714037524577017120 0ustar liggesusers context("library path setup") test_that(".Library", { expect_equal( .Library, r(function() .Library) ) gc() }) test_that(".Library.site", { expect_equal( .Library.site, r(function() .Library.site) ) gc() }) test_that(".libPaths()", { dir.create(tmp <- tempfile()) on.exit(unlink(tmp, recursive = TRUE)) lp <- withr::with_libpaths( tmp, action = "prefix", r(function() { .libPaths() }) ) expect_true(normalizePath(tmp) %in% normalizePath(lp)) gc() }) test_that("if .Renviron overrides R_PROFILE", { ## But we still need to use the proper lib path, as set in the fake ## profile skip_in_covr() cat("Sys.setenv(FOO='nope')\n", file = tmp_prof <- tempfile()) cat("R_PROFILE=\"", tmp_prof, "\"\n", file = tmp_env <- tempfile(), sep = "") cat("R_PROFILE_USER=\"", tmp_prof, "\"\n", file = tmp_env, sep = "", append = TRUE) cat("FOO=bar\n", file = tmp_env, sep = "", append = TRUE) dir.create(tmp_lib <- tempfile()) on.exit(unlink(c(tmp_prof, tmp_env, tmp_lib), recursive = TRUE)) lp <- withr::with_envvar( c(R_ENVIRON = tmp_env), withr::with_libpaths( tmp_lib, action = "prefix", r(function() list(.libPaths(), Sys.getenv("FOO"), Sys.getenv("R_PROFILE"))) ) ) expect_true(normalizePath(tmp_lib) %in% normalizePath(lp[[1]])) expect_equal(lp[[2]], "bar") gc() }) test_that("libpath in system(), empty .Renviron", { # We remove the library with covr from the lib path, so this # cannot work in a subprocess. skip_in_covr() dir.create(tmpdrop <- tempfile("drop")) dir.create(tmpkeep <- tempfile("keep")) on.exit(unlink(c(tmpdrop, tmpkeep), recursive = TRUE), add = TRUE) tmpenv <- withr::local_tempfile() cat("", file = tmpenv) withr::local_envvar(c(R_ENVIRON_USER = tmpenv)) withr::local_libpaths(tmpdrop, action = "prefix") test_paths(tmpdrop, tmpkeep) ## To close FDs gc() }) test_that("libpath in system, R_LIBS in .Renviron", { # We remove the library with covr from the lib path, so this # cannot work in a subprocess. skip_in_covr() dir.create(tmpdrop <- tempfile("drop")) dir.create(tmpkeep <- tempfile("keep")) on.exit(unlink(c(tmpdrop, tmpkeep), recursive = TRUE), add = TRUE) tmpenv <- withr::local_tempfile() cat("R_LIBS=\"", tmpdrop, "\"\n", sep = "", file = tmpenv) withr::local_envvar(c(R_ENVIRON_USER = tmpenv)) withr::local_libpaths(tmpdrop, action = "prefix") # Since we add tmpdrop in .Renviron, it will only be dropped by --vanilla test_paths(tmpdrop, tmpkeep, system_drop = NULL) ## To close FDs gc() }) test_that("libpath in system, R_LIBS", { # We remove the library with covr from the lib path, so this # cannot work in a subprocess. skip_in_covr() dir.create(tmpdrop <- tempfile("drop")) dir.create(tmpkeep <- tempfile("keep")) on.exit(unlink(c(tmpdrop, tmpkeep), recursive = TRUE), add = TRUE) tmpenv <- withr::local_tempfile() cat("", file = tmpenv) withr::local_envvar(c(R_ENVIRON_USER = tmpenv, R_LIBS=tmpdrop)) withr::local_libpaths(tmpdrop, action = "prefix") # R_LIBS is used by both R and R --vanilla, so it will not be dropped test_paths(tmpdrop, tmpkeep, system_drop = NULL, system_vanilla_drop = NULL) ## To close FDs gc() }) test_that("libpath in system, R_LIBS and .Renviron", { # We remove the library with covr from the lib path, so this # cannot work in a subprocess. skip_in_covr() dir.create(tmpdrop <- tempfile("drop")) dir.create(tmpkeep <- tempfile("keep")) on.exit(unlink(c(tmpdrop, tmpkeep), recursive = TRUE), add = TRUE) tmpenv <- withr::local_tempfile() cat("R_LIBS=\"", tmpdrop, "\"\n", sep = "", file = tmpenv) withr::local_envvar(c(R_ENVIRON_USER = tmpenv, R_LIBS=tmpdrop)) withr::local_libpaths(tmpdrop, action = "prefix") # R_LIBS is used by both R and R --vanilla, so it will not be dropped test_paths(tmpdrop, tmpkeep, system_drop = NULL, system_vanilla_drop = NULL) ## To close FDs gc() }) test_that("libpath in system, if subprocess changes R_LIBS", { dir.create(tmpkeep <- tempfile("keep")) on.exit(unlink(tmpkeep, recursive = TRUE), add = TRUE) rbin <- setup_r_binary_and_args(list())$bin rbin <- shQuote(rbin) f <- function(rbin, new) { Sys.setenv(R_LIBS = new) Sys.setenv(R_ENVIRON_USER = "no_such_file") system(paste( rbin, "--no-site-file --no-init-file --no-save --no-restore --slave", "-e \".libPaths()\""), intern = TRUE) } out <- callr::r(f, list(rbin = rbin, new = tmpkeep)) expect_true(any(grepl(basename(normalizePath(tmpkeep)), out))) ## Close FDs gc() }) test_that("libpath in system, if subprocess changes R_LIBS #2", { if (.Platform$OS.type != "unix") skip("Unix only") dir.create(tmpkeep <- tempfile("keep")) on.exit(unlink(tmpkeep, recursive = TRUE), add = TRUE) rbin <- setup_r_binary_and_args(list())$bin f <- function(rbin, new) { Sys.setenv(R_LIBS = new) Sys.setenv(R_ENVIRON_USER = "no_such_file") env <- paste0("R_LIBS=", shQuote(new)) tmp <- tempfile() on.exit(unlink(tmp), add = TRUE) system2( rbin, c("--no-site-file", "--no-init-file", "--no-save", "--no-restore", "--slave", "-e", "'.libPaths()'"), env = env, stdout = tmp ) readLines(tmp) } out <- callr::r(f, list(rbin = rbin, new = tmpkeep)) expect_true(any(grepl(basename(normalizePath(tmpkeep)), out))) ## Close FDs gc() }) test_that("setting profile/environ variables in 'env'", { # See https://github.com/r-lib/callr/issues/193 skip_in_covr() profile <- tempfile() on.exit(unlink(profile), add = TRUE) cat("foo <- '11'\n", file = profile) envfile <- tempfile() on.exit(unlink(envfile), add = TRUE) cat("MY_ENV2=MILES2\n", file = envfile) ret <- callr::r( function() { c(Sys.getenv("MY_ENV"), Sys.getenv("R_PROFILE_USER"), exists("foo"), Sys.getenv("MY_ENV2") ) }, user_profile = TRUE, env = c( "R_PROFILE_USER" = profile, "R_ENVIRON_USER" = envfile, "MY_ENV" = "MILES" ) ) expect_equal(ret, c("MILES", profile, "TRUE", "MILES2")) }) callr/tests/testthat/test-eval.R0000644000176200001440000001404414025755433016413 0ustar liggesusers context("r") test_that("basic r", { expect_equal(r(function() 1 + 1), 2) expect_equal(r(function(x) 1 + x, list(5)), 6) gc() }) test_that("the same R version is called", { expect_equal(r(function() R.version), R.version) gc() }) test_that("standard output", { tmp <- tempfile() on.exit(unlink(tmp), add = TRUE) r(function() cat("hello\n"), stdout = tmp) expect_equal(readLines(tmp), "hello") gc() }) test_that("standard error", { tmp <- tempfile() on.exit(unlink(tmp), add = TRUE) r(function() message("hello"), stderr = tmp) expect_equal(readLines(tmp), "hello") gc() }) test_that("standard output and standard error", { tmp_out <- tempfile("stdout-") tmp_err <- tempfile("stderr-") on.exit(unlink(c(tmp_out, tmp_err)), add = TRUE) expect_silent( r(function() { cat("hello world!\n"); message("hello again!") }, stdout = tmp_out, stderr = tmp_err) ) expect_equal(readLines(tmp_out), "hello world!") expect_equal(readLines(tmp_err), "hello again!") gc() }) test_that("cmdargs argument", { o1 <- tempfile() o2 <- tempfile() on.exit(unlink(c(o1, o2)), add = TRUE) r(function() ls(), stdout = o1) r(function() ls(), stdout = o2, cmdargs = character()) expect_true(length(readLines(o2)) > length(readLines(o1))) gc() }) test_that("env", { expect_equal( r(function() Sys.getenv("CALLR_FOOBAR"), env = c(CALLR_FOOBAR = "indeed") ), "indeed" ) gc() }) test_that("stdout and stderr in the same file", { tmp <- tempfile() on.exit(unlink(tmp), add = TRUE) r(function() { cat("hello1\n") Sys.sleep(0.1) message("hello2") Sys.sleep(0.1) cat("hello3\n") }, stdout = tmp, stderr = tmp ) expect_equal(readLines(tmp), paste0("hello", 1:3)) gc() }) test_that("profiles are used as requested", { do <- function(system, user) { tmp1 <- tempfile() tmp2 <- tempfile() on.exit(unlink(c(tmp1, tmp2)), add = TRUE) cat("Sys.setenv(FOO = 'bar')\n", file = tmp1) cat("Sys.setenv(NAH = 'doh')\n", file = tmp2) withr::with_envvar(list(R_PROFILE = tmp1, R_PROFILE_USER = tmp2), { res <- r( function() c(Sys.getenv("FOO", ""), Sys.getenv("NAH", "")), system_profile = system, user_profile = user) }) } ## None res <- do(FALSE, FALSE) expect_equal(res, c("", "")) ## System res <- do(TRUE, FALSE) expect_equal(res, c("bar", "")) ## User res <- do(FALSE, TRUE) expect_equal(res, c("", "doh")) ## Both res <- do(TRUE, TRUE) expect_equal(res, c("bar", "doh")) gc() }) test_that(".Renviron is used, but lib path is set over it", { dir.create(tmp <- tempfile()) on.exit(unlink(tmp, recursive = TRUE), add = TRUE) withr::with_dir(tmp, { ## Create .Renviron file dir.create("not") dir.create("yes") cat("R_LIBS=\"", file.path(getwd(), "not"), "\"\n", sep = "", file = ".Renviron") cat("FOO=bar\n", file = ".Renviron", append = TRUE) ## R CMD check sets R_ENVIRON and R_ENVIRON_USER to empty, ## so we need to recover that. withr::with_envvar(c(R_ENVIRON_USER=".Renviron"), { res <- r( function() list(.libPaths(), Sys.getenv("FOO")), libpath = c(file.path(getwd(), "yes"), .libPaths()) ) }) }) expect_equal(basename(res[[1]][1]), "yes") expect_equal(res[[2]], "bar") gc() }) test_that("errors are printed on stderr", { ## See https://github.com/r-lib/callr/issues/80 f <- function() { print("send to stdout") message("send to stderr") stop("send to stderr 2") } expect_error( r(f, stdout = out <- tempfile(), stderr = err <- tempfile())) on.exit(unlink(c(out, err), recursive = TRUE), add = TRUE) expect_false(any(grepl("send to stderr", readLines(out)))) expect_true(any(grepl("send to stderr 2", readLines(err)))) gc() }) test_that("stdout and stderr are interleaved correctly", { f <- function() { cat("stdout", file = stdout()) cat("stderr", file = stderr()) cat("stdout2\n", file = stdout()) } on.exit(unlink(out, recursive = TRUE), add = TRUE) r(f, stdout = out <- tempfile(), stderr = "2>&1") expect_equal(readLines(out), "stdoutstderrstdout2") unlink(out) r(f, stdout = out, stderr = out) expect_equal(readLines(out), "stdoutstderrstdout2") gc() }) test_that("callr messages do not cause problems", { do <- function() { cnd <- structure( list(message = "foobar"), class = c("callr_message", "condition") ) signalCondition(cnd) signalCondition(cnd) signalCondition(cnd) cat("stdout\n") message("stderr") "hi" } out <- tempfile() err <- tempfile() on.exit(unlink(c(out, err)), add = TRUE) ret <- callr::r(do, stdout = out, stderr = err, poll_connection = FALSE) expect_equal(ret, "hi") expect_equal(readLines(out), "stdout") expect_equal(readLines(err), "stderr") }) test_that("cleans up temp files", { skip_on_cran() rsc <- function() { library(callr) old <- dir(tempdir(), pattern = "^callr-") result <- callr::r(function() 1+1) unloadNamespace("callr") new <- setdiff(dir(tempdir(), "^callr-"), old) list(result = result, new = new) } out <- r(rsc) expect_identical(out$result, 2) expect_identical(out$new, character()) }) test_that("local .Rprofile is loaded", { tmp <- tempfile() on.exit(unlink(tmp, recursive = TRUE), add = TRUE) dir.create(tmp) wd <- getwd() on.exit(setwd(wd), add = TRUE) setwd(tmp) cat("aa <- 123\n", file = ".Rprofile") out <- callr::r(function() aa) expect_equal(out, 123) }) test_that("local .Rprofile is not loaded from actual wd", { tmp <- tempfile() on.exit(unlink(tmp, recursive = TRUE), add = TRUE) dir.create(tmp) wd <- getwd() on.exit(setwd(wd), add = TRUE) setwd(tmp) dir.create(wd2 <- tempfile()) on.exit(unlink(wd2, recursive = TRUE), add = TRUE) cat("aa <- 123\n", file = ".Rprofile") out <- callr::r(function() ls(.GlobalEnv), wd = wd2) expect_equal(out, character()) }) test_that("symbolic arguments are protected", { expect_equal( callr::r(function(x) x, list(x = quote(foobar))), quote(foobar) ) }) callr/tests/testthat/test-clean-subprocess.R0000644000176200001440000000315013737560325020733 0ustar liggesusers context("clean-subprocess") test_that("r() does not load anything", { skip_in_covr() pkgs <- withr::with_envvar( clean_envvars(), r(without_env(function() loadedNamespaces()))) if (length(pkgs) > 1) print(pkgs) ## Some R versions still load compiler... expect_true(all(pkgs %in% c("base", "compiler"))) }) test_that("r_bg() does not load anything", { skip_in_covr() p <- withr::with_envvar( clean_envvars(), r_bg(without_env(function() loadedNamespaces()))) on.exit(p$kill(), add = TRUE) p$wait(3000) pkgs <- p$get_result() if (length(pkgs) > 1) print(pkgs) ## Some R versions still load compiler... expect_true(all(pkgs %in% c("base", "compiler"))) }) test_that("r_session does not load anything", { skip_in_covr() rs <- withr::with_envvar(clean_envvars(), r_session$new()) on.exit(rs$close(), add = TRUE) pkgs <- rs$run(without_env(function() loadedNamespaces())) if (length(pkgs) > 1) print(pkgs) ## Some R versions still load compiler... expect_true(all(pkgs %in% c("base", "compiler"))) gc() }) test_that("r() does not create objects in global env", { vars <- r(function() ls(.GlobalEnv)) expect_identical(vars, character()) }) test_that("r_bg() does not create objects in global env", { p <- r_bg(function() ls(.GlobalEnv)) on.exit(p$kill(), add = TRUE) p$wait(3000) vars <- p$get_result() expect_identical(vars, character()) }) test_that("r_session does not create objects in global env", { rs <- r_session$new() on.exit(rs$close(), add = TRUE) vars <- rs$run(function() ls(.GlobalEnv)) expect_identical(vars, character()) rs$close() gc() }) callr/tests/testthat/test-r-bg.R0000644000176200001440000000340714025755433016314 0ustar liggesusers context("r_bg") test_that("r_bg runs", { x <- r_bg(function() 1 + 1) x$wait() expect_identical(x$get_result(), 2) rm(x) gc() }) test_that("r_bg takes arguments", { x <- r_bg(function(x) x + 10, args = list(32)) x$wait() expect_identical(x$get_result(), 42) rm(x) gc() }) test_that("r_bg can be killed", { x <- r_bg(function() Sys.sleep(2)) x$kill() expect_false(x$is_alive()) expect_error(x$get_result()) rm(x) gc() }) test_that("r_bg can get the error back", { x <- r_bg(function() 1 + "A", error = "error") x$wait() expect_error(x$get_result(), "non-numeric argument to binary operator") rm(x) gc() }) test_that("can read standard output", { x <- r_bg(function() cat("Hello world!\n")) x$wait() expect_equal(x$read_output_lines(), "Hello world!") rm(x) gc() }) test_that("can read standard error", { x <- r_bg(function() message("Hello world!")) x$wait() expect_equal(x$read_error_lines(), "Hello world!") rm(x) gc() }) test_that("can read stdout and stderr", { x <- r_bg(function() { cat("Hello world!\n"); message("Again!") }) x$wait() expect_equal(x$read_output_lines(), "Hello world!") expect_equal(x$read_error_lines(), "Again!") rm(x) gc() }) test_that("cleans up temporary files", { skip_on_cran() rbg <- function() { library(callr) old <- dir(tempdir(), pattern = "^callr-") rp <- callr::r_bg(function() 1+1) on.exit(tryCatch(rp$kill, error = function(e) NULL), add = TRUE) rp$wait(5000) rp$kill() result <- rp$get_result() rm(rp) gc() gc() unloadNamespace("callr") new <- setdiff(dir(tempdir(), "^callr-"), old) list(result = result, new = new) } out <- r(rbg) expect_identical(out$result, 2) expect_identical(out$new, character()) }) callr/tests/testthat/test-bugs.R0000644000176200001440000000035214037516452016420 0ustar liggesusers context("various bug fixes") test_that("repos is a list, #82", { expect_true(withr::with_options( list(repos = list(CRAN = "https://cloud.r-project.org")), callr::r(function() inherits(getOption("repos"), "list")) )) }) callr/tests/testthat/test-spelling.R0000644000176200001440000000056413612550263017276 0ustar liggesusers context("spelling") test_that("spell check", { skip_on_cran() skip_in_covr() pkg_dir <- test_package_root() results <- spelling::spell_check_package(pkg_dir) if (nrow(results)) { output <- sprintf( "Potential spelling errors: %s\n", paste(results$word, collapse = ", ")) stop(output, call. = FALSE) } else { expect_true(TRUE) } }) callr/tests/testthat/test-error.R0000644000176200001440000001465514037037433016621 0ustar liggesusers context("errors") test_that("error is propagated", { expect_error( r(function() 1 + "A", error = "error"), "non-numeric argument to binary operator" ) gc() }) test_that("error object is passed", { err <- NULL tryCatch( r(function() 1 + "A", error = "error"), error = function(e) err <<- e ) expect_true(inherits(err, "rlib_error")) gc() }) test_that("error stack is passed", { err <- NULL tryCatch( r( function() { f <- function() g() g <- function() 1 + "A" f() }, error = "stack" ), error = function(e) err <<- e ) expect_true("call" %in% names(err)) expect_true(inherits(err, "error")) expect_true(inherits(err, "callr_error")) expect_equal(length(err$stack), 3) gc() }) test_that("error behavior can be set using option", { err <- NULL withr::with_options(c(callr.error = "error"), { tryCatch( r(function() 1 + "A"), error = function(e) err <<- e ) }) expect_true(inherits(err, "rlib_error")) err <- NULL withr::with_options(c(callr.error = "stack"), { tryCatch( r( function() { f <- function() g() g <- function() 1 + "A" f() } ), error = function(e) err <<- e ) }) expect_true("call" %in% names(err)) expect_true(inherits(err, "error")) expect_true(inherits(err, "callr_error")) expect_equal(length(err$stack), 3) gc() }) test_that("parent errors", { withr::local_options(list("callr.error" = "error")) err <- tryCatch( r(function() 1 + "A"), error = function(e) e) expect_s3_class(err, "rlib_error") expect_s3_class(err$parent, "callr_remote_error") expect_s3_class(err$parent$error, "simpleError") expect_match( conditionMessage(err), "callr subprocess failed: non-numeric argument") expect_match( conditionMessage(err$parent), "non-numeric argument") }) test_that("parent errors, another level", { withr::local_options(list("callr.error" = "error")) err <- tryCatch( callr::r(function() { withr::local_options(list("callr.error" = "error")) callr::r(function() 1 + "A") }), error = function(e) e) expect_s3_class(err, "rlib_error") expect_s3_class(err$parent, "rlib_error") expect_s3_class(err$parent$parent, "callr_remote_error") expect_s3_class(err$parent$parent$error, "simpleError") expect_match( conditionMessage(err), "callr subprocess failed: callr subprocess failed: non-numeric") expect_match( conditionMessage(err$parent), "callr subprocess failed: non-numeric argument") expect_match( conditionMessage(err$parent$parent), "non-numeric argument") }) test_that("error traces are merged", { skip_on_cran() sf <- tempfile(fileext = ".R") op <- sub("\\.R$", ".rds", sf) so <- paste0(sf, "out") se <- paste0(sf, "err") on.exit(unlink(c(sf, op, so, se), recursive = TRUE), add = TRUE) expr <- substitute({ h <- function() callr::r(function() 1 + "a") options(rlib_error_handler = function(c) { saveRDS(c, file = `__op__`) # quit after the first, because the other one is caught here as well q() }) h() }, list("__op__" = op)) cat(deparse(expr), file = sf, sep = "\n") callr::rscript(sf, stdout = so, stderr = se) cond <- readRDS(op) expect_s3_class(cond, "rlib_error") expect_s3_class(cond$parent, "error") expect_s3_class(cond$trace, "rlib_trace") expect_equal(length(cond$trace$nframes), 2) expect_true(cond$trace$nframe[1] < cond$trace$nframe[2]) expect_match(cond$trace$messages[[1]], "subprocess failed: non-numeric") expect_match(cond$trace$messages[[2]], "non-numeric argument") }) test_that("errors in r_bg() are merged", { skip_on_cran() withr::local_options(list("callr.error" = "error")) p <- r_bg(function() 1 + "A") on.exit(p$kill(), add = TRUE) p$wait(2000) err <- tryCatch( p$get_result(), error = function(e) e) expect_s3_class(err, "rlib_error") expect_s3_class(err$parent, "callr_remote_error") expect_s3_class(err$parent$error, "simpleError") expect_match( conditionMessage(err), "callr subprocess failed: non-numeric argument") expect_match( conditionMessage(err$parent), "non-numeric argument") }) test_that("errors in r_process are merged", { skip_on_cran() withr::local_options(list("callr.error" = "error")) opts <- r_process_options(func = function() 1 + "A") p <- r_process$new(opts) on.exit(p$kill(), add = TRUE) p$wait(2000) err <- tryCatch( p$get_result(), error = function(e) e) expect_s3_class(err, "rlib_error") expect_s3_class(err$parent, "callr_remote_error") expect_s3_class(err$parent$error, "simpleError") expect_match( conditionMessage(err), "callr subprocess failed: non-numeric argument") expect_match( conditionMessage(err$parent), "non-numeric argument") }) test_that("errors in r_session$run() are merged", { rs <- r_session$new() on.exit(rs$kill(), add = TRUE) err1 <- tryCatch( rs$run(function() 1 + "A"), error = function(e) e) err2 <- tryCatch( rs$run(function() 1 + "A"), error = function(e) e) err <- list(err1, err2) for (i in seq_along(err)) { expect_s3_class(err[[i]], "rlib_error") expect_s3_class(err[[i]]$parent, "callr_remote_error") expect_s3_class(err[[i]]$parent$error, "simpleError") expect_match( conditionMessage(err[[i]]), "callr subprocess failed: non-numeric argument") expect_match( conditionMessage(err[[i]]$parent), "non-numeric argument") } }) test_that("errors in r_session$call() are merged", { rs <- r_session$new() on.exit(rs$kill(), add = TRUE) rs$call(function() 1 + "A") rs$poll_process(2000) err1 <- rs$read()$error rs$call(function() 1 + "A") rs$poll_process(2000) err2 <- rs$read()$error err <- list(err1, err2) for (i in seq_along(err)) { expect_s3_class(err[[i]], "rlib_error") expect_s3_class(err[[i]]$parent, "callr_remote_error") expect_s3_class(err[[i]]$parent$error, "simpleError") expect_match( conditionMessage(err[[i]]), "callr subprocess failed: non-numeric argument") expect_match( conditionMessage(err[[i]]$parent), "non-numeric argument") } }) test_that("child error is not modified", { err <- tryCatch(callr::r(function() stop("foobar")), error = function(e) e) expect_identical( class(err$parent$error), c("simpleError", "error", "condition") ) expect_null(err$parent$error$trace) }) callr/tests/testthat/test-r-session.R0000644000176200001440000001714314037426724017412 0ustar liggesusers context("r_session") test_that("regular use", { rs <- r_session$new() on.exit(rs$kill()) expect_equal(rs$get_state(), "idle") ## Clean session expect_identical(rs$run(function() ls(.GlobalEnv)), character()) ## Start a command rs$call(function() 42) ## Get result res <- read_next(rs) expect_equal(res$result, 42) expect_null(res$error) expect_equal(rs$get_state(), "idle") ## Run another command, with arguments rs$call(function(x, y) x + y, list(x = 42, y = 42)) ## Get result res <- read_next(rs) expect_equal(res$result, 84) expect_equal(rs$get_state(), "idle") ## Close rs$close() expect_equal(rs$get_state(), "finished") expect_false(rs$is_alive()) }) test_that("run", { rs <- r_session$new() on.exit(rs$kill()) expect_equal(rs$run_with_output(function() 42)$result, 42) expect_equal(rs$run_with_output(function() 42)$result, 42) expect_equal( rs$run_with_output(function(x, y) { x + y }, list(x = 42, y = 42))$result, 84) ## Finish rs$close() expect_equal(rs$get_state(), "finished") expect_false(rs$is_alive()) }) test_that("get stdout/stderr from file", { rs <- r_session$new() on.exit(rs$kill()) eol <- function(x) { paste0(x, if (.Platform$OS.type == "windows") "\r\n" else "\n") } for (i in 1:10) { res <- rs$run_with_output(function() { cat("foo\n"); message("bar"); 42 }) expect_equal( res[c("result", "stdout", "stderr")], list(result = 42, stdout = eol("foo"), stderr = eol("bar"))) res <- rs$run_with_output(function() { cat("bar\n"); message("foo"); 43 }) expect_equal( res[c("result", "stdout", "stderr")], list(result = 43, stdout = eol("bar"), stderr = eol("foo"))) } rs$close() }) test_that("stdout/stderr from pipe", { skip_on_cran() opt <- r_session_options(stdout = "|", stderr = "|") rs <- r_session$new(opt) on.exit(rs$kill()) ## Sometimes a NULL slips through.... this is bug to be fixed rs$read_output_lines() res <- rs$run_with_output(function() { cat("foo\n"); message("bar"); 42 }) expect_equal( res[c("result", "stdout", "stderr")], list(result = 42, stdout = NULL, stderr = NULL)) res <- rs$run_with_output(function() { cat("bar\n"); message("foo"); 43 }) expect_equal( res[c("result", "stdout", "stderr")], list(result = 43, stdout = NULL, stderr = NULL)) processx::poll(list(rs$get_output_connection()), 1000) expect_equal(rs$read_output_lines(n = 1), "foo") processx::poll(list(rs$get_output_connection()), 1000) expect_equal(rs$read_output_lines(n = 1), "bar") processx::poll(list(rs$get_error_connection()), 1000) expect_equal(rs$read_error_lines(n = 1), "bar") processx::poll(list(rs$get_error_connection()), 1000) expect_equal(rs$read_error_lines(n = 1), "foo") rs$close() }) test_that("interrupt", { rs <- r_session$new() on.exit(rs$kill()) rs$call(function() Sys.sleep(5)) Sys.sleep(0.5) rs$interrupt() rs$poll_process(1000) res <- rs$read() expect_s3_class(res$error, "rlib_error") expect_s3_class(res$error$parent$error, "interrupt") rs$close() }) test_that("messages", { f <- function() { x <- structure( list(code = 301, message = "ahoj"), class = c("callr_message", "condition")) signalCondition(x) 22 } msg <- NULL cb <- function(x) msg <<- x rs <- r_session$new() on.exit(rs$kill()) withCallingHandlers( expect_silent(res <- rs$run_with_output(f)), callr_message = function(e) msg <<- e ) expect_equal(res$result, 22) expect_equal( msg, structure( list(code = 301, message = "ahoj", muffle = "callr_r_session_muffle"), class = c("callr_message", "condition"))) rs$close() }) test_that("messages with R objects", { obj <- list(a = 1, b = 2) f <- function(obj) { x <- structure( c(list(code = 301), obj), class = c("foobar_class", "callr_message", "condition")) signalCondition(x) 22 } msg <- NULL cb <- function(x) msg <<- x rs <- r_session$new() on.exit(rs$kill()) withCallingHandlers( expect_silent(res <- rs$run_with_output(f, args = list(obj))), foobar_class = function(e) msg <<- e ) expect_equal(res$result, 22) exp <- exp2 <- structure( list(code = 301, a = 1, b = 2), class = c("foobar_class", "callr_message", "condition")) exp2$muffle <- "callr_r_session_muffle" expect_equal(msg, exp2) rs$call(f, args = list(obj)) rs$poll_process(2000) expect_equal( rs$read(), structure( list(code = 301, message = exp), class = "callr_session_result")) rs$poll_process(2000) expect_equal(rs$read()$result, 22) rs$close() }) test_that("run throws", { rs <- r_session$new() on.exit(rs$kill()) expect_error(rs$run(function() stop("foobar")), "foobar") rs$close() }) test_that("exit", { rs <- r_session$new() on.exit(rs$kill(), add = TRUE) err <- tryCatch( res <- rs$run(function() q()), error = function(x) x) deadline <- Sys.time() + 3 while (rs$is_alive() && Sys.time() < deadline) Sys.sleep(0.05) expect_true(Sys.time() < deadline) expect_false(rs$is_alive()) expect_equal(rs$get_state(), "finished") rs$close() }) test_that("crash", { skip_on_cran() rs <- r_session$new() on.exit(rs$kill(), add = TRUE) err <- tryCatch( rs$run(function() get("crash", asNamespace("callr"))()), error = function(e) e) expect_true( grepl("crashed with exit code", conditionMessage(err)) || grepl("R session closed the process connection", conditionMessage(err)) || grepl("Invalid (uninitialized or closed?) connection object", conditionMessage(err), fixed = TRUE) ) expect_false(rs$is_alive()) expect_equal(rs$get_state(), "finished") rs$close() rs <- r_session$new() on.exit(rs$kill(), add = TRUE) res <- rs$run_with_output(function() { cat("o\n"); message("e"); get("crash", asNamespace("callr"))() }) expect_null(res$result) expect_s3_class(res$error, "error") ## This is a race, and sometimes we don't get the stdout/stderr ## on Windows if (os_platform() != "windows") expect_equal(res$stdout, "o\n") if (os_platform() != "windows") expect_equal(substr(res$stderr, 1, 2), "e\n") expect_false(rs$is_alive()) expect_equal(rs$get_state(), "finished") rs$close() }) test_that("custom load hook", { opts <- r_session_options(load_hook = quote(options(foobar = "baz"))) rs <- r_session$new(opts) on.exit(rs$kill(), add = TRUE) res <- rs$run_with_output(function() getOption("foobar")) expect_null(res$error) expect_identical(res$result, "baz") expect_equal(res$stdout, "") expect_equal(res$stderr, "") rs$close() }) test_that("traceback", { rs <- r_session$new() on.exit(rs$kill(), add = TRUE) do <- function() { f <- function() g() g <- function() stop("oops") f() } expect_error(rs$run(do), "oops") expect_output(tb <- rs$traceback(), "1: \"?f()\"?") expect_match(c(tb[[3]]), "f()", fixed = TRUE) }) test_that("error in the load hook", { opts <- r_session_options(load_hook = quote(stop("oops"))) expect_error({ rs <- r_session$new(opts) on.exit(rs$kill(), add = TRUE) }) rs2 <- r_session$new(opts, wait = FALSE) on.exit(rs2$kill(), add = TRUE) processx::poll(list(rs2$get_poll_connection()), 3000) msg <- rs2$read() expect_true(msg$code %in% c(501L, 502L)) expect_match(msg$stderr, "oops") }) test_that("fds are not leaked", { rs <- r_session$new() on.exit(rs$kill(), add = TRUE) old <- rs$run(function() ps::ps_num_fds()) for (i in 1:10) { rs$run(function() 1 + 1) } new <- rs$run(function() ps::ps_num_fds()) # Allow 2 new fds, just in case expect_true(new - old <= 2) }) callr/tests/testthat/test-r-session-messages.R0000644000176200001440000000432213741314770021210 0ustar liggesusers context("r_session messages") test_that("callr_message, then error", { rs <- r_session$new() on.exit(rs$kill(), add = TRUE) do <- function() { msg <- structure(list(message = "hi"), class = c("callr_message", "condition")) signalCondition(msg) signalCondition(msg) stop("nah-ah") } msg <- err <- NULL tryCatch( withCallingHandlers( rs$run(do), callr_message = function(m) msg <<- c(msg, list(m))), error = function(e) err <<- e) expect_s3_class(msg[[1]], "callr_message") expect_equal(conditionMessage(msg[[1]]), "hi") expect_s3_class(msg[[2]], "callr_message") expect_equal(conditionMessage(msg[[2]]), "hi") expect_s3_class(err, "error") expect_match(conditionMessage(err), "nah-ah") expect_true(rs$is_alive()) expect_equal(rs$get_state(), "idle") expect_identical(rs$read_error_lines(), character()) rs$close() }) test_that("message handlers", { rs <- r_session$new() on.exit(rs$kill(), add = TRUE) do <- function() { msg <- structure(list(message = "hi"), class = c("myclass", "callr_message", "condition")) signalCondition(msg) } cond <- NULL withr::with_options( list(callr.condition_handler_myclass = function(x) { cond <<- x }), rs$run(do) ) expect_s3_class(cond, "myclass") expect_equal(cond$message, "hi") rs$close() }) test_that("large messages", { rs <- r_session$new() on.exit(rs$close(), add = TRUE) do <- function() { msg <- structure(list(message = paste(1:150000, sep = " ")), class = c("myclass", "callr_message", "condition")) signalCondition(msg) for (i in 1:5) { msg <- structure(list(message = paste("message", i)), class = c("myclass", "callr_message", "condition")) signalCondition(msg) } } cond <- list() withr::with_options( list(callr.condition_handler_myclass = function(x) { cond <<- c(cond, list(x)) }), rs$run(do) ) expect_equal(length(cond), 6) expect_s3_class(cond[[1]], "myclass") expect_equal(cond[[1]]$message, paste(1:150000, sep = " ")) for (i in 1:5) { expect_equal(cond[[i + 1]]$message, paste("message", i)) } rs$close() }) callr/tests/testthat/test-timeout.R0000644000176200001440000000045713612550263017150 0ustar liggesusers context("timeout") test_that("r with timeout", { tic <- Sys.time() e <- tryCatch( r(function() Sys.sleep(5), timeout = 1), error = function(e) e ) tac <- Sys.time() expect_true("callr_timeout_error" %in% class(e)) expect_true(tac - tic < as.difftime(4, units = "secs")) gc() }) callr/tests/testthat/helper.R0000644000176200001440000001012414037516170015755 0ustar liggesusers try_silently <- function(expr) { tryCatch( expr, error = function(x) "error", warning = function(x) "warning", message = function(x) "message" ) } read_next <- function(x, timeout = 3000) { pr <- x$poll_process(timeout) if (any(pr == "ready")) { x$read() } else { stop("R session is not ready, timed out...") } } has_locale <- function(l) { has <- TRUE tryCatch( withr::with_locale(c(LC_CTYPE = l), "foobar"), warning = function(w) has <<- FALSE, error = function(e) has <<- FALSE ) has } test_paths <- function(callr_drop, callr_keep, system_drop = callr_drop, system_keep = callr_keep, system_vanilla_drop = system_drop, system_vanilla_keep = system_keep) { # env vars we manipulate and need to restore in the child process envs <- c("R_ENVIRON", "R_ENVIRON_USER", "R_PROFILE", "R_PROFILE_USER", "R_LIBS", "R_LIBS_USER", "R_LIBS_SITE") # env vars that should not be set, in addition to the CALLR_*_BAK ones xenvs <- c("CALLR_CHILD_R_LIBS", "CALLR_CHILD_R_LIBS_SITE", "CALLR_CHILD_R_LIBS_USER") check_env <- function(subenv) { miss <- paste0("CALLR_", envs, "_BAK") expect_equal(Sys.getenv()[envs], subenv[envs]) expect_false(any(miss %in% names(subenv))) } fc <- function() { lib <- normalizePath(.libPaths()) list(env = Sys.getenv(), lib = lib) } out <- callr::r(fc , libpath = callr_keep) expect_equal( out$lib, unique(normalizePath(c(callr_keep, .Library.site, .Library))) ) check_env(out$env) rbin <- setup_r_binary_and_args(list())$bin rbin <- shQuote(rbin) f1 <- function(rbin) { lib <- system(paste(rbin, "-q -e \".libPaths()\""), intern = TRUE) list(env = Sys.getenv(), lib = lib) } out <- callr::r(f1, list(rbin = rbin), libpath = system_keep) if (length(system_keep)) { expect_false(any(grepl(basename(normalizePath(system_keep)), out))) } if (length(system_drop)) { expect_false(any(grepl(basename(normalizePath(system_drop)), out))) } check_env(out$env) fvanilla <- function(rbin) { lib <- system(paste(rbin, "--vanilla -q -e \".libPaths()\""), intern = TRUE) list(env = Sys.getenv(), lib = lib) } outvanilla <- callr::r( fvanilla, list(rbin = rbin), libpath = system_vanilla_keep ) if (length(system_vanilla_keep)) { expect_false( any(grepl(basename(normalizePath(system_vanilla_keep)), outvanilla)) ) } if (length(system_vanilla_drop)) { expect_false( any(grepl(basename(normalizePath(system_vanilla_drop)), outvanilla)) ) } check_env(out$env) } test_temp_file <- function(fileext = "", pattern = "test-file-", envir = parent.frame(), create = TRUE) { tmp <- tempfile(pattern = pattern, fileext = fileext) if (identical(envir, .GlobalEnv)) { message("Temporary files will _not_ be cleaned up") } else { withr::defer( try(unlink(tmp, recursive = TRUE, force = TRUE), silent = TRUE), envir = envir) } if (create) { cat("", file = tmp) normalizePath(tmp) } else { tmp } } test_temp_dir <- function(pattern = "test-dir-", envir = parent.frame()) { tmp <- test_temp_file(pattern = pattern, envir = envir, create = FALSE) dir.create(tmp, recursive = TRUE, showWarnings = FALSE) normalizePath(tmp) } expect_error <- function(..., class = "error") { testthat::expect_error(..., class = class) } test_package_root <- function() { x <- tryCatch( rprojroot::find_package_root_file(), error = function(e) NULL) if (!is.null(x)) return(x) pkg <- testthat::testing_package() x <- tryCatch( rprojroot::find_package_root_file( path = file.path("..", "..", "00_pkg_src", pkg)), error = function(e) NULL) if (!is.null(x)) return(x) stop("Cannot find package root") } in_covr <- function() { Sys.getenv("R_COVR") == "true" } skip_in_covr <- function() { if (in_covr()) skip("In covr") } clean_envvars <- function() { c(R_DEFAULT_PACKAGES = "NULL", R_ENABLE_JIT = "0") } without_env <- function(f) { environment(f) <- .GlobalEnv f } callr/tests/testthat/test-rcmd-process.R0000644000176200001440000000153613612550263020062 0ustar liggesusers context("rcmd_process") test_that("create and run rcmd_process", { opts <- rcmd_process_options(cmd = "config", cmdargs = "CC") x <- rcmd_process$new(opts) x$wait() expect_equal(x$get_exit_status(), 0) out <- x$read_output_lines() expect_match(out, ".") rm(x) gc() }) test_that("cleans up temp files", { skip_on_cran() rc <- function() { library(callr) scriptfile <- tempfile(fileext = ".R") old <- dir(tempdir(), pattern = "^callr-") rp <- rcmd_bg("config", "CC") on.exit(tryCatch(rp$kill, error = function(e) NULL), add = TRUE) rp$wait(5000) result <- rp$get_exit_status() rp$kill() rm(rp) gc() gc() new <- setdiff(dir(tempdir(), "^callr-"), old) list(result = result, new = new) } out <- r(rc) expect_identical(out$result, 0L) expect_identical(out$new, character()) }) callr/tests/testthat/test-options.R0000644000176200001440000000043713612550263017153 0ustar liggesusers context("options") test_that("error for unknown options", { expect_error( r_process_options(func = function() {}, foo = "bar"), "Unknown option" ) expect_error( r_process_options(func = function() {}, foo = "bar", bar = "foo"), "Unknown options" ) gc() }) callr/tests/testthat/test-utils.R0000644000176200001440000000300613636410367016621 0ustar liggesusers context("utils") test_that("is_complete_expression", { do_tests <- function() { expect_true(is_complete_expression("")) expect_true(is_complete_expression("1")) expect_true(is_complete_expression("1+1")) expect_true(is_complete_expression("foo + \n bar")) expect_true(is_complete_expression("1 1")) expect_false(is_complete_expression("1+")) expect_false(is_complete_expression("1+1+")) expect_false(is_complete_expression("1+\n2+")) } do_tests() if (has_locale("de_DE")) { withr::with_envvar(c(LANGUAGE = "de_DE"), do_tests()) } }) test_that("default_repos", { def <- "https://cloud.r-project.org" withr::with_options(list(repos = NULL), expect_equal( default_repos(), c(CRAN = def))) withr::with_options(list(repos = character()), expect_equal( default_repos(), c(CRAN = def))) withr::with_options(list(repos = list()), expect_equal( default_repos(), list(CRAN = def))) withr::with_options(list(repos = c(foo = "bar")), expect_equal( default_repos(), c(foo = "bar", CRAN = def))) withr::with_options(list(repos = list(foo = "bar")), expect_equal( default_repos(), list(foo = "bar", CRAN = def))) withr::with_options(list(repos = c(foo = "bar", CRAN = "set")), expect_equal( default_repos(), c(foo = "bar", CRAN = "set"))) withr::with_options(list(repos = c(foo = "bar", CRAN = "@CRAN@")), expect_equal( default_repos(), c(foo = "bar", CRAN = def))) }) callr/tests/testthat/test-messages.R0000644000176200001440000000113713737303300017261 0ustar liggesusers context("messages") test_that("messages in callr::r do not crash session", { ret <- r(function() { cli::cli_text("fooobar"); 1 + 1 }) expect_identical(ret, 2) gc() }) test_that("messages in callr::r_bg do not crash session", { skip_in_covr() # TODO: what wrong with this on Windows? skip_on_cran() rx <- r_bg(function() { cli::cli_text("fooobar"); 1 + 1 }) rx$wait(5000) rx$kill() expect_equal(rx$get_exit_status(), 0) expect_equal(rx$get_result(), 2) processx::processx_conn_close(rx$get_output_connection()) processx::processx_conn_close(rx$get_error_connection()) gc() }) callr/tests/testthat/test-archs.R0000644000176200001440000000311214026375646016563 0ustar liggesusers test_that("r() to the other arch", { skip_on_cran() archs <- supported_archs() if (length(archs) < 1) return(expect_true(TRUE)) ret <- unlist(lapply( archs, function(a) r(function() .Platform$r_arch, arch = a) )) expect_equal(ret, archs) }) test_that("r_bg() to the other arch", { skip_on_cran() archs <- supported_archs() if (length(archs) < 1) return(expect_true(TRUE)) procs <- lapply(archs, function(a) { r_bg(function() .Platform$r_arch, arch = a) }) on.exit(lapply(procs, function(p) p$kill()), add = TRUE) for (p in procs) p$wait(3000) for (p in procs) expect_false(p$is_alive()) res <- unlist(lapply(procs, function(p) p$get_result())) expect_equal(res, archs) }) test_that("r_process to the other arch", { skip_on_cran() archs <- supported_archs() if (length(archs) < 1) return(expect_true(TRUE)) procs <- lapply(archs, function(a) { opts <- r_process_options( func = function() .Platform$r_arch, arch = a ) r_process$new(opts) }) on.exit(lapply(procs, function(p) p$kill()), add = TRUE) for (p in procs) p$wait(3000) for (p in procs) expect_false(p$is_alive()) res <- unlist(lapply(procs, function(p) p$get_result())) expect_equal(res, archs) }) test_that("r_session to the other arch", { skip_on_cran() archs <- supported_archs() if (length(archs) < 1) return(expect_true(TRUE)) ret <- unlist(lapply(archs, function(a) { opts <- r_session_options(arch = a) rs <- r_session$new(opts) on.exit(rs$close(), add = TRUE) rs$run(function() .Platform$r_arch) })) expect_equal(ret, archs) }) callr/tests/testthat/test-r-process.R0000644000176200001440000000032413612550263017370 0ustar liggesusers context("r_process") test_that("create and run r_process", { options <- r_process_options(func = function() 1 + 1) x <- r_process$new(options) x$wait() expect_equal(x$get_result(), 2) rm(x) gc() }) callr/tests/testthat/test-quit.R0000644000176200001440000000125113612550263016435 0ustar liggesusers context("calling quit()") test_that("quit() in the function", { x <- r(function() quit()) expect_null(x) expect_error( r(function() quit(status = 2)), "non-zero status", class = "callr_status_error") gc() }) test_that("quit() in bg process", { p1 <- r_bg(function() quit()) on.exit(p1$kill()) p1$wait() expect_null(p1$get_result()) close(p1$get_output_connection()) close(p1$get_error_connection()) p2 <- r_bg(function() quit(status = 2)) on.exit(p2$kill(), add = TRUE) p2$wait() expect_error( p2$get_result(), "non-zero status", class = "callr_status_error") close(p2$get_output_connection()) close(p2$get_error_connection()) }) callr/tests/testthat/test-rscript.R0000644000176200001440000000442013612550263017142 0ustar liggesusers context("rscript") test_that("rscript", { out <- rscript("fixtures/script.R", show = FALSE) expect_equal(out$status, 0L) expect_equal( out$stdout, if (os_platform() == "windows") "stdout\r\n" else "stdout\n") expect_equal( out$stderr, if (os_platform() == "windows") "stderr\r\n" else "stderr\n") gc() }) test_that("rscript_process", { px <- rscript_process$new( rscript_process_options(script = "fixtures/script.R")) on.exit(try(px$kill(), silent = TRUE), add = TRUE) px$wait(5000) expect_equal(px$get_exit_status(), 0) expect_equal(px$read_output_lines(), "stdout") expect_equal(px$read_error_lines(), "stderr") rm(px); gc() }) test_that("stderr -> stdout", { out <- rscript("fixtures/script2.R", show = FALSE, stderr = "2>&1") exp <- if (os_platform() == "windows") { "out1err1out2err2\r\n" } else { "out1err1out2err2\n" } expect_equal(out$stdout, exp) expect_equal(out$stderr, "") out2 <- test_temp_file(create = FALSE) rscript("fixtures/script2.R", show = FALSE, stdout = out2, stderr = out2) expect_equal(readLines(out2), "out1err1out2err2") gc() }) test_that("cleans up temporary files", { skip_on_cran() rsc <- function() { library(callr) scriptfile <- tempfile(fileext = ".R") cat("cat('foobar')\n", file = scriptfile) old <- dir(tempdir(), pattern = "^callr-") result <- callr::rscript(scriptfile) new <- setdiff(dir(tempdir(), "^callr-"), old) list(result = result, new = new) } out <- r(rsc) expect_identical(out$result$stdout, "foobar") expect_identical(out$new, character()) }) test_that("bg process cleans up temporary files", { skip_on_cran() rsc <- function() { library(callr) scriptfile <- tempfile(fileext = ".R") cat("cat('foobar')\n", file = scriptfile) old <- dir(tempdir(), pattern = "^callr-") opts <- rscript_process_options(script = scriptfile) rp <- rscript_process$new(opts) on.exit(tryCatch(rp$kill, error = function(e) NULL), add = TRUE) rp$wait(5000) result <- rp$read_output() rp$kill() rm(rp) gc() gc() new <- setdiff(dir(tempdir(), "^callr-"), old) list(result = result, new = new) } out <- r(rsc) expect_identical(out$result, "foobar") expect_identical(out$new, character()) }) callr/tests/testthat/test-function-env.R0000644000176200001440000000365113737563431020105 0ustar liggesusers test_that("r()", { # not passed by default error <- tryCatch( r(callr:::remove_source, list(r)), error = function(e) e ) expect_match(conditionMessage(error), "could not find function") # keep it out <- r(callr:::remove_source, list(r), package = TRUE) expect_true(is.function(out)) expect_equal(environmentName(environment(out)), "callr") # set it explicitly to package env out <- r(function(x) remove_source(x), list(r), package = "callr") expect_true(is.function(out)) expect_equal(environmentName(environment(out)), "callr") }) test_that("r_bg()", { # not passed by default p1 <- r_bg(callr:::remove_source, list(r)) on.exit(p1$kill(), add = TRUE) p2 <- r_bg(callr:::remove_source, list(r), package = TRUE) on.exit(p2$kill(), add = TRUE) p3 <- r_bg(function(x) remove_source(x), list(r), package = "callr") on.exit(p3$kill(), add = TRUE) p1$wait(3000) p1$kill() error <- tryCatch(p1$get_result(), error = function(e) e) expect_match(conditionMessage(error), "could not find function") p2$wait(3000) p2$kill() out <- p2$get_result() expect_true(is.function(out)) expect_equal(environmentName(environment(out)), "callr") p3$wait(3000) p3$kill() out <- p3$get_result() expect_true(is.function(out)) expect_equal(environmentName(environment(out)), "callr") }) test_that("r_session", { rs <- r_session$new() on.exit(rs$kill(), add = TRUE) error <- tryCatch( rs$run(callr:::remove_source, list(r)), error = function(e) e ) expect_match(conditionMessage(error), "could not find function") # keep it out <- rs$run(callr:::remove_source, list(r), package = TRUE) expect_true(is.function(out)) expect_equal(environmentName(environment(out)), "callr") # set it explicitly to package env out <- rs$run(function(x) remove_source(x), list(r), package = "callr") expect_true(is.function(out)) expect_equal(environmentName(environment(out)), "callr") }) callr/tests/testthat.R0000644000176200001440000000014013737653217014505 0ustar liggesuserslibrary(testthat) library(callr) if (Sys.getenv("NOT_CRAN", "") == "true") test_check("callr") callr/R/0000755000176200001440000000000014037524577011566 5ustar liggesuserscallr/R/script.R0000644000176200001440000001244714037332737013220 0ustar liggesusers make_vanilla_script_expr <- function(expr_file, res, error, pre_hook = NULL, post_hook = NULL, messages = FALSE) { ## Code to handle errors in the child ## This will inserted into the main script err <- if (error == "error") { substitute({ callr_data <- as.environment("tools:callr")$`__callr_data__` err <- callr_data$err assign(".Traceback", .traceback(4), envir = callr_data) dump.frames("__callr_dump__") assign(".Last.dump", .GlobalEnv$`__callr_dump__`, envir = callr_data) rm("__callr_dump__", envir = .GlobalEnv) # callr_remote_error does have conditionMessage and conditionCall # methods that refer to $error, but in the subprocess callr is not # loaded, maybe, and these methods are not defined. So we do add # the message and call of the original error e$call <- deparse(conditionCall(e), nlines = 6) e2 <- err$new_error(conditionMessage(e), call. = conditionCall(e)) class(e2) <- c("callr_remote_error", class(e2)) e2$error <- e # To find the frame of the evaluated function, we search for # do.call in the stack, and then skip one more frame, the other # do.call. This method only must change if the eval code changes, # obviously. Also, it might fail if the pre-hook has do.call() at # the top level. calls <- sys.calls() dcframe <- which(vapply( calls, function(x) length(x) >= 1 && identical(x[[1]], quote(do.call)), logical(1)))[1] if (!is.na(dcframe)) e2$`_ignore` <- list(c(1, dcframe + 1L)) e2$`_pid` <- Sys.getpid() e2$`_timestamp` <- Sys.time() if (inherits(e, "rlib_error_2_0")) e2$parent <- e$parent e2 <- err$add_trace_back(e2, embed = FALSE) saveRDS(list("error", e2), file = paste0(`__res__`, ".error")) }, list(`__res__` = res) ) } else if (error %in% c("stack", "debugger")) { substitute( { callr_data <- as.environment("tools:callr")$`__callr_data__` assign(".Traceback", .traceback(4), envir = callr_data) dump.frames("__dump__") # nocov start saveRDS( list(`__type__`, e, .GlobalEnv$`__dump__`), file = paste0(`__res__`, ".error") ) # nocov end }, list( "__type__" = error, "__res__" = res ) ) } else { throw(new_error("Unknown `error` argument: `", error, "`")) } if (messages) { message <- function() { substitute({ pxlib <- as.environment("tools:callr")$`__callr_data__`$pxlib if (is.null(e$code)) e$code <- "301" msg <- paste0("base64::", pxlib$base64_encode(serialize(e, NULL))) data <- paste0(e$code, " ", nchar(msg), "\n", msg) pxlib$write_fd(3L, data) if (inherits(e, "cli_message") && !is.null(findRestart("cli_message_handled"))) { invokeRestart("cli_message_handled") } else if (inherits(e, "message") && !is.null(findRestart("muffleMessage"))) { invokeRestart("muffleMessage") } }) } } else { message <- function() substitute(signalCondition(e)) } ## The function to run and its arguments are saved as a list: ## list(fun, args). args itself is a list. ## So the first do.call will create the call: do.call(fun, args) ## The second do.call will perform fun(args). ## ## The c() is needed because the first .GlobalEnv is itself ## an argument to the do.call within the do.call. ## ## It is important that we do not create any temporary variables, ## the function is called from an empty global environment. substitute( { tryCatch( # nocov start withCallingHandlers( { `__pre_hook__` saveRDS( do.call( do.call, c(readRDS(`__expr_file__`), list(envir = .GlobalEnv, quote = TRUE)), envir = .GlobalEnv, quote = TRUE ), file = `__res__` ) flush(stdout()) flush(stderr()) `__post_hook__` invisible() }, error = function(e) { `__error__` }, interrupt = function(e) { `__error__` }, callr_message = function(e) { try(`__message__`) } ), ## We need to `stop()` here again, otherwise the error message ## is not printed to stderr. See ## https://github.com/r-lib/callr/issues/80 ## However, on R 3.1 and R 3.2 throwing an error here ## will crash the R process. With `try()` the error is still ## printed to stderr, but no real error is thrown. error = function(e) { `__post_hook__`; try(stop(e)) }, interrupt = function(e) { `__post_hook__`; e } ) # nocov end }, list(`__error__` = err, `__expr_file__` = expr_file, `__res__` = res, `__pre_hook__` = pre_hook, `__post_hook__` = post_hook, `__message__` = message()) ) } make_vanilla_script_file <- function(expr_file, res, error) { expr <- make_vanilla_script_expr(expr_file, res, error) script <- deparse(expr) tmp <- tempfile("callr-scr-") cat(script, file = tmp, sep = "\n") tmp } callr/R/rcmd-process.R0000644000176200001440000000310013620756716014303 0ustar liggesusers #' External `R CMD` Process #' #' @description #' An `R CMD *` command that runs in the background. This is an R6 class #' that extends the [processx::process] class. #' #' @examplesIf FALSE #' options <- rcmd_process_options(cmd = "config", cmdargs = "CC") #' rp <- rcmd_process$new(options) #' rp$wait() #' rp$read_output_lines() #' @export rcmd_process <- R6::R6Class( "rcmd_process", inherit = processx::process, public = list( #' @description #' Start an `R CMD` process. #' @param options A list of options created via #' [rcmd_process_options()]. #' @return A new `rcmd_process` object. initialize = function(options) rcmdp_init(self, private, super, options), #' @description #' Clean up the temporary files created for an `R CMD` process. finalize = function() { unlink(private$options$tmp_files, recursive = TRUE) if ("finalize" %in% ls(super)) super$finalize() } ), private = list( options = NULL ) ) rcmdp_init <- function(self, private, super, options) { ## This contains the context that we set up in steps options <- convert_and_check_my_args(options) options <- setup_context(options) options <- setup_rcmd_binary_and_args(options) private$options <- options oldwd <- getwd() setwd(options$wd) on.exit(setwd(oldwd), add = TRUE) with_envvar( options$env, do.call(super$initialize, c(list(options$bin, options$real_cmdargs, stdout = options$stdout, stderr = options$stderr, poll_connection = options$poll_connection), options$extra)) ) invisible(self) } callr/R/check.R0000644000176200001440000000467514026375646013001 0ustar liggesusers #' Convert and check function arguments #' #' This function is used for all variants of `r` and `rcmd`. An argument #' name is only used to refer to one kind of object, to make this possible. #' #' The benefit of having a single `options` object is to avoid passing #' around a lot of arguments all the time. #' #' The benefit of making this object internal (i.e. that the `r`, etc. #' functions have multiple arguments instead of a single `options` list), #' is that documentation and usage is more user friendly (e.g. command- #' completion works in the editor. #' #' @param options List of options. #' #' @keywords internal convert_and_check_my_args <- function(options) { has <- function(x) x %in% names(options) no <- function(x) ! has(x) ## Conversions options <- within(options, { if (has("libpath")) libpath <- as.character(libpath) if (has("repos")) repos <- repos if (has("stdout") && !is.null(stdout)) { stdout <- as.character(stdout) } if (has("stderr") && !is.null(stderr)) { stderr <- as.character(stderr) } if (has("error")) error <- error[1] if (has("cmdargs")) cmdargs <- as.character(cmdargs) if (has("timeout") && !inherits(timeout, "difftime")) { timeout <- as.difftime( as.double(timeout), units = "secs" ) } if (no("wd")) wd <- "." if (no("echo")) echo <- FALSE if (no("fail_on_status")) fail_on_status <- FALSE if (no("tmp_files")) tmp_files <- character() if (no("package")) package <- FALSE if (no("arch")) arch <- "same" }) ## Checks with(options, stopifnot( no("func") || is.function(func), no("func") || is.list(args), is.character(libpath), no("stdout") || is.null(stdout) || is_string(stdout), no("stderr") || is.null(stderr) || is_string(stderr), no("error") || is_string(error), is.character(cmdargs), no("echo") || is_flag(echo), no("show") || is_flag(show), no("callback") || is.null(callback) || is.function(callback), no("block_callback") || is.null(block_callback) || is.function(block_callback), no("spinner") || is_flag(spinner), is_flag(system_profile), is_flag(user_profile) || identical(user_profile, "project"), is.character(env), no("timeout") || (length(timeout) == 1 && !is.na(timeout)), no("wd") || is_string(wd), no("fail_on_status") || is_flag(fail_on_status), is_string(package) || is_flag(package), is_string(arch) )) options } callr/R/utils.R0000644000176200001440000000744114037511626013046 0ustar liggesusers #' Default value for the `repos` option in callr subprocesses #' #' callr sets the `repos` option in subprocesses, to make sure that #' a CRAN mirror is set up. This is because the subprocess cannot bring #' up the menu of CRAN mirrors for the user to choose from. #' #' @return Named character vector, the default value of the `repos` #' option in callr subprocesses. #' #' @export #' @examples #' default_repos() default_repos <- function() { opt <- getOption("repos") was_list <- is.list(opt) if (! "CRAN" %in% names(opt) || opt[["CRAN"]] == "@CRAN@") { opt[["CRAN"]] <- "https://cloud.r-project.org" } if (!was_list) opt <- unlist(opt) opt } remove_source <- function(x) { if (is.function(x)) { body(x) <- remove_source(body(x)) x } else if (is.call(x)) { attr(x, "srcref") <- NULL attr(x, "wholeSrcref") <- NULL attr(x, "srcfile") <- NULL x[] <- lapply(x, remove_source) x } else { x } } `%||%` <- function(l, r) if (is.null(l)) r else l is.named <- function(x) { length(names(x)) == length(x) && all(names(x) != "") } set_envvar <- function(envs) { if (length(envs) == 0) return() stopifnot(is.named(envs)) old <- Sys.getenv(names(envs), names = TRUE, unset = NA) set <- !is.na(envs) both_set <- set & !is.na(old) if (any(set)) do.call("Sys.setenv", as.list(envs[set])) if (any(!set)) Sys.unsetenv(names(envs)[!set]) invisible(old) } with_envvar <- function(new, code) { old <- set_envvar(new) on.exit(set_envvar(old)) force(code) } os_platform <- function() .Platform$OS.type enumerate <- function(x) { if (length(x) == 0) { "" } else if (length(x) == 1) { x } else { l <- length(x) paste0(paste(x[-l], collapse = ", "), " and ", x[[l]]) } } ## Thanks to Romain for the idea! ## https://github.com/romainfrancois/trump/blob/ ## 7845b83343afa356e4259c054e7c9a910034f170/R/trump.R crash <- function() { get("attach")( structure(list(), class = "UserDefinedDatabase") ) } is_flag <- function(x) { is.logical(x) && length(x) == 1 && !is.na(x) } is_string <- function(x) { is.character(x) && length(x) == 1 && !is.na(x) } read_all <- function(filename) { con <- file(filename, open = "rb") on.exit(close(con), add = TRUE) res <- raw(0) while (length(more <- readBin(con, what = "raw", 10000)) && length(more)) { res <- c(res, more) } rawToChar(res) } is_complete_expression <- function(x) { err <- NULL tryCatch(parse(text = x), error = function(e) err <<- e) if (is.null(err)) return(TRUE) exp <- tryCatch(parse(text = "1+"), error = function(e) e$message) exp1 <- strsplit(exp, "\n")[[1]][[1]] msg <- sub("^.*:\\s*([^:]+)$", "\\1", exp1, perl = TRUE) ! grepl(msg, conditionMessage(err), fixed = TRUE) } bold <- function(x) { tryCatch( cli::style_bold(x), error = function(e) x ) } update_history <- function(cmd) { tmp <- tempfile("callr-hst-") on.exit(unlink(tmp, recursive = TRUE)) utils::savehistory(tmp) cat(cmd, "\n", sep = "", file = tmp, append = TRUE) utils::loadhistory(tmp) } #' Find supported sub-architectures for the current R installation #' #' This function uses a heuristic, which might fail, so its result #' should be taken as a best guess. #' #' @return Character vector of supported architectures. If the #' current R build is not a multi-architecture build, then an empty #' string scalar is returned. #' #' @export #' @examples #' supported_archs() supported_archs <- function() { oldwd <- getwd() on.exit(setwd(oldwd), add = TRUE) setwd(file.path(R.home(), "bin")) archs <- list.dirs(recursive = FALSE) archs <- sub("^[.]?[/\\\\]", "", archs) archs <- setdiff(archs, "exec") if (length(archs) == 0) { if (nzchar(.Platform$r_arch)) { archs <- .Platform$r_arch } else { archs <- Sys.getenv("R_ARCH") } } archs } callr/R/r-session.R0000644000176200001440000007534214037333067013636 0ustar liggesusers #' External R Session #' #' @description #' A permanent R session that runs in the background. This is an R6 class #' that extends the [processx::process] class. #' #' The process is started at the creation of the object, and then it can #' be used to evaluate R function calls, one at a time. #' #' @param func Function object to call in the background R process. #' Please read the notes for the similar argument of [r()]. #' @param args Arguments to pass to the function. Must be a list. #' @param package Whether to keep the environment of `func` when passing #' it to the other package. Possible values are: #' * `FALSE`: reset the environment to `.GlobalEnv`. This is the default. #' * `TRUE`: keep the environment as is. #' * `pkg`: set the environment to the `pkg` package namespace. #' #' @examplesIf FALSE #' rs <- r_ression$new() #' #' rs$run(function() 1 + 2) #' #' rs$call(function() Sys.sleep(1)) #' rs$get_state() #' #' rs$poll_process(-1) #' rs$get_state() #' rs$read() #' @export r_session <- R6::R6Class( "r_session", inherit = processx::process, public = list( #' @field status #' Status codes returned by `read()`. status = list( DONE = 200L, STARTED = 201L, ATTACH_DONE = 202L, MSG = 301L, EXITED = 500L, CRASHED = 501L, CLOSED = 502L ), #' @description #' creates a new R background process. It can wait for the process to #' start up (`wait = TRUE`), or return immediately, i.e. before #' the process is actually ready to run. In the latter case you may call #' the `poll_process()` method to make sure it is ready. #' #' @param options A list of options created via [r_session_options()]. #' @param wait Whether to wait for the R process to start and be ready #' for running commands. #' @param wait_timeout Timeout for waiting for the R process to start, #' in milliseconds. #' @return An `r_session` object. initialize = function(options = r_session_options(), wait = TRUE, wait_timeout = 3000) rs_init(self, private, super, options, wait, wait_timeout), #' @description #' Similar to [r()], but runs the function in a permanent background #' R session. It throws an error if the function call generated an #' error in the child process. #' @return The return value of the R expression. run = function(func, args = list(), package = FALSE) rs_run(self, private, func, args, package), #' @description #' Similar to `$run()`, but returns the standard output and error of #' the child process as well. It does not throw on errors, but #' returns a non-`NULL` `error` member in the result list. #' #' @return A list with the following entries. #' * `result`: The value returned by `func`. On error this is `NULL`. #' * `stdout`: The standard output of the process while evaluating # the `func` call, #' * `stderr`: The standard error of the process while evaluating #' the `func` call. #' * `error`: On error it contains an error object, that contains the #' error thrown in the subprocess. Otherwise it is `NULL`. #' * `code`, `message`: These fields are used by call internally and #' you can ignore them. run_with_output = function(func, args = list(), package = FALSE) rs_run_with_output(self, private, func, args, package), #' @description #' Starts running a function in the background R session, and #' returns immediately. To check if the function is done, call the #' `poll_process()` method. call = function(func, args = list(), package = FALSE) rs_call(self, private, func, args, package), #' @description #' Poll the R session with a timeout. If the session has finished the #' computation, it returns with `"ready"`. If the timeout #' is reached, it returns with `"timeout"`. #' #' @param timeout Timeout period in milliseconds. #' @return Character string `"ready"` or `"timeout"`. poll_process = function(timeout) rs_poll_process(self, private, timeout), #' @description #' Return the state of the R session. #' #' @return Possible values: #' * `"starting"`: starting up, #' * `"idle"`: ready to compute, #' * `"busy"`: computing right now, #' * `"finished"`: the R process has finished. get_state = function() rs_get_state(self, private), #' @description #' Returns the elapsed time since the R process has started, and the #' elapsed time since the current computation has started. The latter #' is `NA` if there is no active computation. #' @return Named vector of `POSIXct` objects. The names are `"total"` #' and `"current"`. get_running_time = function() rs_get_running_time(self, private), #' @description #' Reads an event from the child process, if there is one available. #' Events might signal that the function call has finished, or they #' can be progress report events. #' #' This is a low level function that you only need to use if you #' want to process events (messages) from the R session manually. #' #' @return `NULL` if no events are available. Otherwise a named list, #' which is also a `callr_session_result` object. The list always has #' a `code` entry which is the type of the event. See also #' `r_session$public_fields$status` for symbolic names of the #' event types. #' * `200`: (`DONE`) The computation is done, and the event includes #' the result, in the same form as for the `run()` method. #' * `201`: (`STARTED`) An R session that was in 'starting' state is #' ready to go. #' * `202`: (`ATTACH_DONE`) Used by the `attach()` method. #' * `301`: (`MSG`) A message from the subprocess. The message is a #' condition object with class `callr_message`. (It typically has #' other classes, e.g. `cli_message` for output from the cli #' package.) #' * `500`: (`EXITED`) The R session finished cleanly. This means #' that the evaluated expression quit R. #' * `501`: (`CRASHED`) The R session crashed or was killed. #' * `502`: (`CLOSED`) The R session closed its end of the connection #' that callr uses for communication. read = function() rs_read(self, private), #' @description #' Terminate the current computation and the R process. #' The session object will be in `"finished"` state after this. #' @param grace Grace period in milliseconds, to wait for the #' subprocess to exit cleanly, after its standard input is closed. #' If the process is still running after this period, it will be #' killed. close = function(grace = 1000) rs_close(self, private, grace), #' @description #' The `traceback()` method can be used after an error in the R #' subprocess. It is equivalent to the [base::traceback()] call, in #' the subprocess. #' @return The same output as from [base::traceback()] traceback = function() rs_traceback(self, private), #' @description #' Interactive debugger to inspect the dumped frames in the subprocess, #' after an error. See more at [r_session_debug]. debug = function() rs_debug(self, private), #' @description Experimental function that provides a REPL #' (Read-Eval-Print-Loop) to the subprocess. attach = function() rs_attach(self, private), #' @description #' Finalizer that is called when garbage collecting an `r_session` #' object, to clean up temporary files. finalize = function() { unlink(private$tmp_output_file) unlink(private$tmp_error_file) unlink(private$options$tmp_files, recursive = TRUE) if ("finalize" %in% ls(super)) super$finalize() }, #' @description #' Print method for an `r_session`. #' @param ... Arguments are not used currently. print = function(...) { cat( sep = "", "R SESSION, ", if (self$is_alive()) { paste0("alive, ", self$get_state(), ", ") } else { "finished, " }, "pid ", self$get_pid(), ".\n") invisible(self) } ), private = list( options = NULL, state = NULL, started_at = NULL, fun_started_at = as.POSIXct(NA), pipe = NULL, tmp_output_file = NULL, tmp_error_file = NULL, func_file = NULL, res_file = NULL, buffer = NULL, read_buffer = function() rs__read_buffer(self, private), read_message = function() rs__read_message(self, private), get_result_and_output = function(std = FALSE) rs__get_result_and_output(self, private, std), report_back = function(code, text = "") rs__report_back(self, private, code, text), write_for_sure = function(text) rs__write_for_sure(self, private, text), parse_msg = function(msg) rs__parse_msg(self, private, msg), attach_wait = function() rs__attach_wait(self, private) ) ) rs_init <- function(self, private, super, options, wait, wait_timeout) { options$func <- options$func %||% function() { } options$args <- list() options$load_hook <- session_load_hook(options$load_hook) options <- convert_and_check_my_args(options) options <- setup_context(options) options <- setup_r_binary_and_args(options, script_file = FALSE) private$options <- options prepare_client_files() with_envvar( options$env, do.call(super$initialize, c(list(options$bin, options$real_cmdargs, stdin = "|", stdout = "|", stderr = "|", poll_connection = TRUE), options$extra)) ) ## Make child report back when ready private$report_back(201, "ready to go") private$pipe <- self$get_poll_connection() private$started_at <- Sys.time() private$state <- "starting" if (wait) { timeout <- wait_timeout have_until <- Sys.time() + as.difftime(timeout / 1000, units = "secs") pr <- self$poll_io(timeout) out <- "" err <- "" while (any(pr == "ready")) { if (pr["output"] == "ready") out <- paste0(out, self$read_output()) if (pr["error"] == "ready") err <- paste0(err, self$read_error()) if (pr["process"] == "ready") break timeout <- as.double(have_until - Sys.time(), units = "secs") * 1000 pr <- self$poll_io(as.integer(timeout)) } if (pr["process"] == "ready") { msg <- self$read() out <- paste0(out, msg$stdout) err <- paste0(err, msg$stderr) if (msg$code != 201) { data <- list( status = self$get_exit_status(), stdout = out, stderr = err, timeout = FALSE ) throw(new_callr_error(data, "Failed to start R session")) } } else if (pr["process"] != "ready") { cat("stdout:]\n", out, "\n") cat("stderr:]\n", err, "\n") throw(new_error("Could not start R session, timed out")) } } invisible(self) } rs_read <- function(self, private) { if (!is.null(private$buffer)) { # There is a partial message in the buffer, try to finish it. out <- private$read_buffer() } else { # A new message. out <- private$read_message() } if (!length(out)) { if (processx::processx_conn_is_incomplete(private$pipe)) return() if (self$is_alive()) { # We do this in on.exit(), because parse_msg still reads the streams on.exit(self$kill(), add = TRUE) out <- list(header = list( code = 502, length = 0, rest = "R session closed the process connection, killed" )) } else if (identical(es <- self$get_exit_status(), 0L)) { out <- list(header = list( code = 500, length = 0, rest = "R session finished cleanly" )) } else { out <- list(header = list( code = 501, length = 0, rest = paste0("R session crashed with exit code ", es) )) } } if (length(out)) private$parse_msg(out) } rs__read_buffer <- function(self, private) { # There is a partial message in the buffer already, we need to # read some more need <- private$buffer$header$length - private$buffer$got chunk <- processx::processx_conn_read_chars(private$pipe, need) got <- nchar(chunk) if (got == 0) { # make this special case fast NULL } else if (got == need) { msg <- list( header = private$buffer$header, body = paste(c(private$buffer$chunks, list(chunk)), collapse = "") ) private$buffer <- NULL msg } else { private$buffer$got <- private$buffer$got + got private$buffer$chunks <- c(private$buffer$chunks, list(chunk)) NULL } } rs__read_message <- function(self, private) { # A new message, we can surely read the first line out <- processx::processx_conn_read_lines(private$pipe, 1) if (length(out) == 0) return(NULL) header <- rs__parse_header(out) body <- "" if (header$length > 0) { body <- processx::processx_conn_read_chars( private$pipe, header$length ) } got <- nchar(body) if (got < header$length) { # Partial message private$buffer <- list( header = header, got = got, chunks = list(body) ) NULL } else { list(header = header, body = body) } } rs__parse_header <- function(line) { parts <- strsplit(line, " ", fixed = TRUE)[[1]] parts2 <- suppressWarnings(as.integer(parts[1:2])) rest <- paste(parts[-(1:2)], collapse = " ") header <- list(code = parts2[1], length = parts2[2], rest = rest) if (is.na(header$code) || is.na(header$length)) { stop("Internal callr error, invalid message header") } header } rs_close <- function(self, private, grace) { processx::processx_conn_close(self$get_input_connection()) self$poll_process(grace) self$kill() self$wait(1000) if (self$is_alive()) throw(new_error("Could not kill background R session")) private$state <- "finished" private$fun_started_at <- as.POSIXct(NA) processx::processx_conn_close(private$pipe) processx::processx_conn_close(self$get_output_connection()) processx::processx_conn_close(self$get_error_connection()) } rs_call <- function(self, private, func, args, package) { ## We only allow a new command if the R session is idle. ## This allows keeping a clean state ## TODO: do we need a state at all? if (private$state == "starting") throw(new_error("R session not ready yet")) if (private$state == "finished") throw(new_error("R session finished")) if (private$state == "busy") throw(new_error("R session busy")) ## Save the function in a file private$options$func <- func private$options$args <- args private$options$package <- package private$options$func_file <- save_function_to_temp(private$options) private$options$result_file <- tempfile("callr-rs-result-") private$options$tmp_files <- c(private$options$tmp_files, private$options$func_file, private$options$result_file) ## Maybe we need to redirect stdout / stderr re_stdout <- if (is.null(private$options$stdout)) { private$tmp_output_file <- tempfile("callr-rs-stdout-") } re_stderr <- if (is.null(private$options$stderr)) { private$tmp_error_file <- tempfile("callr-rs-stderr-") } pre <- rs__prehook(re_stdout, re_stderr) post <- rs__posthook(re_stdout, re_stderr) ## Run an expr that loads it, in the child process, with error handlers expr <- make_vanilla_script_expr(private$options$func_file, private$options$result_file, private$options$error, pre_hook = pre, post_hook = post, messages = TRUE) cmd <- paste0(deparse(expr), "\n") ## Write this to stdin private$write_for_sure(cmd) private$fun_started_at <- Sys.time() ## Report back when done report_str <- paste0("done ", basename(private$options$result_file)) private$report_back(200, report_str) private$state <- "busy" } rs_run_with_output <- function(self, private, func, args, package) { self$call(func, args, package) go <- TRUE res <- NULL while (go) { ## TODO: why is this in a tryCatch? res <- tryCatch( { processx::poll(list(private$pipe), -1) msg <- self$read() if (is.null(msg)) next if (msg$code == 200 || (msg$code >= 500 && msg$code < 600)) { return(msg) } if (msg$code == 301) { rs__handle_condition(msg$message) } }, interrupt = function(e) { self$interrupt() ## The R process will catch the interrupt, and then save the ## error object to a file, but this might still take some time, ## so we need to poll here. If the bg process ignores ## interrupts, then we kill it. ps <- processx::poll(list(private$pipe), 1000)[[1]] if (ps == "timeout") { self$kill() } else { res <<- self$read() go <<- FALSE } iconn <- structure( list(message = "Interrupted"), class = c("interrupt", "condition")) signalCondition(iconn) cat("\n") invokeRestart("abort") }) } res } rs_run <- function(self, private, func, args, package) { res <- rs_run_with_output(self, private, func, args, package) if (is.null(res$error)) { res$result } else{ res$stdout <- paste0(res$stdout, self$read_output()) res$stderr <- paste0(res$stderr, self$read_error()) throw(res$error) } } rs_get_state <- function(self, private) { private$state } rs_get_running_time <- function(self, private) { now <- Sys.time() finished <- private$state == "finished" c(total = if (finished) now - private$started_at else as.POSIXct(NA), current = now - private$fun_started_at) } rs_poll_process <- function(self, private, timeout) { processx::poll(list(self$get_poll_connection()), timeout)[[1]] } rs_traceback <- function(self, private) { ## TODO: get rid of magic number 12 traceback(utils::head(self$run(function() { traceback(as.environment("tools:callr")$`__callr_data__`$.Traceback, 10) }), -12)) } rs_debug <- function(self, private) { hasdump <- self$run(function() { ! is.null(as.environment("tools:callr")$`__callr_data__`$.Last.dump) }) if (!hasdump) stop("Can't find dumped frames, nothing to debug") help <- function() { cat("Debugging in process ", self$get_pid(), ", press CTRL+C (ESC) to quit. Commands:\n", sep = "") cat(" .where -- print stack trace\n", " .inspect -- inspect a frame, 0 resets to .GlobalEnv\n", " .help -- print this message\n", " -- run in frame or .GlobalEnv\n\n", sep = "") } translate_cmd <- function(cmd) { if (cmd == ".where") { traceback(tb) if (frame) cat("Inspecting frame", frame, "\n") NULL } else if (cmd == ".help") { help() NULL } else if (grepl("^.inspect ", cmd)) { newframe <- as.integer(strsplit(cmd, " ")[[1]][[2]]) if (is.na(newframe)) { message("Cannot parse frame number") } else { frame <<- newframe } NULL } else { cmd } } help() tb <- self$traceback() frame <- 0L while (TRUE) { cat("\n") prompt <- paste0( "RS ", self$get_pid(), if (frame) paste0(" (frame ", frame, ")"), " > ") cmd <- rs__attach_get_input(prompt) cmd2 <- translate_cmd(cmd) if (is.null(cmd2)) next update_history(cmd) ret <- self$run_with_output(function(cmd, frame) { dump <- as.environment("tools:callr")$`__callr_data__`$.Last.dump envir <- if (!frame) .GlobalEnv else dump[[frame + 12L]] eval(parse(text = cmd), envir = envir) }, list(cmd = cmd, frame = frame)) cat(ret$stdout) cat(ret$stderr) if (!is.null(ret$error)) print(ret$error) print(ret$result) } invisible() } rs_attach <- function(self, private) { out <- self$get_output_connection() err <- self$get_error_connection() while (nchar(x <- processx::processx_conn_read_chars(out))) cat(x) while (nchar(x <- processx::processx_conn_read_chars(err))) cat(bold(x)) tryCatch({ while (TRUE) { cmd <- rs__attach_get_input(paste0("RS ", self$get_pid(), " > ")) update_history(cmd) private$write_for_sure(paste0(cmd, "\n")) private$report_back(202, "done") private$attach_wait() } }, interrupt = function(e) { self$interrupt(); invisible() } ) } ## Internal functions ---------------------------------------------------- rs__attach_get_input <- function(prompt) { cmd <- readline(prompt = prompt) while (! is_complete_expression(cmd)) { cmd <- paste0(cmd, sep = "\n", readline(prompt = "+ ")) } cmd } rs__attach_wait <- function(self, private) { out <- self$get_output_connection() err <- self$get_error_connection() pro <- private$pipe while (TRUE) { pr <- processx::poll(list(out, err, pro), -1) if (pr[[1]] == "ready") { if (nchar(x <- processx::processx_conn_read_chars(out))) cat(x) } if (pr[[2]] == "ready") { if (nchar(x <- processx::processx_conn_read_chars(err))) cat(bold(x)) } if (pr[[3]] == "ready") { msg <- self$read() if (msg$code == 202) break; } } } rs__report_back <- function(self, private, code, text) { cmd <- paste0( deparse(rs__status_expr(code, text, fd = 3)), "\n" ) private$write_for_sure(cmd) } rs__write_for_sure <- function(self, private, text) { while (1) { text <- self$write_input(text) if (!length(text)) break; Sys.sleep(.1) } } rs__parse_msg <- function(self, private, msg) { code <- as.character(msg$header$code) message <- msg$body if (length(message) && substr(message, 1, 8) == "base64::") { message <- substr(message, 9, nchar(message)) message <- unserialize(processx::base64_decode(message)) } else { message <- msg$header$rest } if (! code %in% names(rs__parse_msg_funcs)) { throw(new_error("Unknown message code: `", code, "`")) } structure( rs__parse_msg_funcs[[code]](self, private, msg$header$code, message), class = "callr_session_result") } rs__parse_msg_funcs <- list() rs__parse_msg_funcs[["200"]] <- function(self, private, code, message) { if (private$state != "busy") { throw(new_error("Got `done` message when session is not busy")) } private$state <- "idle" res <- private$get_result_and_output() c(list(code = code, message = message), res) } rs__parse_msg_funcs[["201"]] <- function(self, private, code, message) { if (private$state != "starting") { throw(new_error("Session already started, invalid `starting` message")) } private$state <- "idle" list(code = code, message = message) } rs__parse_msg_funcs[["202"]] <- function(self, private, code, message) { private$state <- "idle" list(code = code, message = message) } rs__parse_msg_funcs[["301"]] <- function(self, private, code, message) { ## TODO: progress bar update, what to do here? list(code = code, message = message) } rs__parse_msg_funcs[["500"]] <- function(self, private, code, message) { private$state <- "finished" res <- private$get_result_and_output(std = TRUE) c(list(code = code, message = message), res) } rs__parse_msg_funcs[["501"]] <- function(self, private, code, message) { private$state <- "finished" err <- structure( list(message = message), class = c("error", "condition")) res <- private$get_result_and_output(std = TRUE) res$error <- err c(list(code = code, message = message), res) } rs__parse_msg_funcs[["502"]] <- rs__parse_msg_funcs[["501"]] rs__status_expr <- function(code, text = "", fd = 3L) { substitute( local({ pxlib <- as.environment("tools:callr")$`__callr_data__`$pxlib code_ <- code; fd_ <- fd; text_ <- text data <- paste0(code_, " 0 ", text_, "\n") pxlib$write_fd(as.integer(fd), data) }), list(code = code, fd = fd, text = text) ) } rs__prehook <- function(stdout, stderr) { oexpr <- if (!is.null(stdout)) substitute({ assign( ".__stdout__", as.environment("tools:callr")$`__callr_data__`$pxlib$ set_stdout_file(`__fn__`), envir = as.environment("tools:callr")$`__callr_data__`) }, list(`__fn__` = stdout)) eexpr <- if (!is.null(stderr)) substitute({ assign( ".__stderr__", as.environment("tools:callr")$`__callr_data__`$pxlib$ set_stderr_file(`__fn__`), envir = as.environment("tools:callr")$`__callr_data__`) }, list(`__fn__` = stderr)) substitute({ o; e }, list(o = oexpr, e = eexpr)) } rs__posthook <- function(stdout, stderr) { oexpr <- if (!is.null(stdout)) substitute({ as.environment("tools:callr")$`__callr_data__`$ pxlib$set_stdout(as.environment("tools:callr")$`__callr_data__`$ .__stdout__) }) eexpr <- if (!is.null(stderr)) substitute({ as.environment("tools:callr")$`__callr_data__`$ pxlib$set_stderr(as.environment("tools:callr")$`__callr_data__`$ .__stderr__) }) substitute({ o; e }, list(o = oexpr, e = eexpr)) } rs__get_result_and_output <- function(self, private, std) { ## Get stdout and stderr stdout <- if (!is.null(private$tmp_output_file) && file.exists(private$tmp_output_file)) { tryCatch(suppressWarnings(read_all(private$tmp_output_file)), error = function(e) "") } else if (std && self$has_output_connection()) { tryCatch(self$read_all_output(), error = function(err) NULL) } stderr <- if (!is.null(private$tmp_error_file) && file.exists(private$tmp_error_file)) { tryCatch(suppressWarnings(read_all(private$tmp_error_file)), error = function(e) "") } else if (std && self$has_error_connection()) { tryCatch(self$read_all_error(), error = function(err) NULL) } unlink(c(private$tmp_output_file, private$tmp_error_file)) private$tmp_output_file <- private$tmp_error_file <- NULL ## Get result or error from RDS outp <- list( status = 0, stdout = stdout %||% "", stderr = stderr %||% "", timeout = FALSE ) res <- err <- NULL tryCatch( res <- get_result(outp, private$options), error = function(e) err <<- e, interrupt = function(e) err <<- e ) unlink(private$options$tmp_files, recursive = TRUE) private$options$tmp_files <- NULL ## Assemble result list(result = res, stdout = stdout, stderr = stderr, error = err) } rs__handle_condition <- function(cond) { default_handler <- function(x) { classes <- class(x) for (cl in classes) { opt <- paste0("callr.condition_handler_", cl) if (!is.null(val <- getOption(opt)) && is.function(val)) { val(x) break } } } if (is.list(cond) && is.null(cond$muffle)) { cond$muffle <- "callr_r_session_muffle" } withRestarts({ signalCondition(cond) default_handler(cond) }, callr_r_session_muffle = function() NULL) invisible() } ## Helper functions ------------------------------------------------------ #' Create options for an [r_session] object #' #' @param ... Options to override, named arguments. #' @return Named list of options. #' #' The current options are: #' * `libpath`: Library path for the subprocess. By default the same as the #' _current_ library path. I.e. _not_ necessarily the library path of #' a fresh R session.) #' * `repos`: `repos` option for the subprocess. By default the current #' value of the main process. #' * `stdout`: Standard output of the sub-process. This can be `NULL` or #' a pipe: `"|"`. If it is a pipe then the output of the subprocess is #' not included in the responses, but you need to poll and read it #' manually. This is for exports. #' * `stderr`: Similar to `stdout`, but for the standard error. #' * `error`: See 'Error handling' in [r()]. #' * `cmdargs`: See the same argument of [r()]. (Its default might be #' different, though.) #' * `system_profile`: See the same argument of [r()]. #' * `user_profile`: See the same argument of [r()]. #' * `env`: See the same argument of [r()]. #' * `load_hook`: `NULL`, or code (quoted) to run in the sub-process #' at start up. (I.e. not for every single `run()` call.) #' * `extra`: List of extra arguments to pass to [processx::process]. #' #' Call `r_session_options()` to see the default values. #' `r_session_options()` might contain undocumented entries, you cannot #' change these. #' #' @export #' @examples #' r_session_options() r_session_options <- function(...) { update_options(r_session_options_default(), ...) } r_session_options_default <- function() { list( func = NULL, args = NULL, libpath = .libPaths(), repos = default_repos(), stdout = NULL, stderr = NULL, error = getOption("callr.error", "error"), cmdargs = c( if (os_platform() != "windows") "--no-readline", "--slave", "--no-save", "--no-restore" ), system_profile = FALSE, user_profile = "project", env = c(TERM = "dumb"), supervise = FALSE, load_hook = NULL, extra = list(), arch = "same" ) } #' Interactive debugging of persistent R sessions #' #' The `r_session$debug()` method is an interactive debugger to inspect #' the stack of the background process after an error. #' #' `$debug()` starts a REPL (Read-Eval-Print-Loop), that evaluates R #' expressions in the subprocess. It is similar to [browser()] and #' [debugger()] and also has some extra commands: #' #' * `.help` prints a short help message. #' * `.where` prints the complete stack trace of the error. (The same as #' the `$traceback()` method. #' * `.inspect ` switches the "focus" to frame ``. Frame 0 is the #' global environment, so `.inspect 0` will switch back to that. #' #' To exit the debugger, press the usual interrupt key, i.e. `CTRL+c` or #' `ESC` in some GUIs. #' #' Here is an example session that uses `$debug()` (some output is omitted #' for brevity): #' #' ``` #' # ---------------------------------------------------------------------- #' > rs <- r_session$new() #' > rs$run(function() knitr::knit("no-such-file")) #' Error in rs_run(self, private, func, args) : #' callr subprocess failed: cannot open the connection #' #' > rs$debug() #' Debugging in process 87361, press CTRL+C (ESC) to quit. Commands: #' .where -- print stack trace #' .inspect -- inspect a frame, 0 resets to .GlobalEnv #' .help -- print this message #' -- run in frame or .GlobalEnv #' #' 3: file(con, "r") #' 2: readLines(input2, encoding = "UTF-8", warn = FALSE) #' 1: knitr::knit("no-such-file") at #1 #' #' RS 87361 > .inspect 1 #' #' RS 87361 (frame 1) > ls() #' [1] "encoding" "envir" "ext" "in.file" "input" "input.dir" #' [7] "input2" "ocode" "oconc" "oenvir" "oopts" "optc" #' [13] "optk" "otangle" "out.purl" "output" "quiet" "tangle" #' [19] "text" #' #' RS 87361 (frame 1) > input #' [1] "no-such-file" #' #' RS 87361 (frame 1) > file.exists(input) #' [1] FALSE #' #' RS 87361 (frame 1) > # #' # ---------------------------------------------------------------------- #' ``` #' #' @name r_session_debug NULL callr/R/result.R0000644000176200001440000000567214037246352013231 0ustar liggesusers #' Read the result object from the output file, or the error #' #' Even if an error happens, the output file might still exist, #' because [saveRDS()] creates the file before evaluating its object #' argument. So we need to check for the error file to decide #' if an error happened. #' #' @param out List of the output object from [run()] and #' the name of the result file to read. For the error file, #' `.error` is appended to this. #' @param options The context, including all parameters. #' @return If no error happened, the result is returned. Otherwise #' we handle the error. #' #' @keywords internal get_result <- function(output, options) { res <- options$result_file ## Timeout? if (output$timeout) throw(new_callr_error(output)) ## No output file and no error file? Some other (system?) error then, ## unless exit status was zero, which is probably just quit(). ## (Newer R versions do not write a corrupt RDS file in this case.) ret <- NULL errorres <- paste0(res, ".error") killmsg <- paste( "could not start R, exited with non-zero status,", "has crashed or was killed") if (! file.exists(res) && ! file.exists(errorres)) { if (is.na(output$status) || output$status != 0) { throw(new_callr_error(output, killmsg)) } else { return(ret) } } ## No error file? Then probably all is well, return the output ## If this is currupt, then the R process has crashed ## This cannot happen from R 3.5.0, because that version only writes ## out the output file if no error or crash has happened. ## (Older R versions write a corrupt RDS file in this case.) if (! file.exists(errorres)) { tryCatch( ret <- readRDS(res), error = function(e) { if (is.na(output$status) || output$status != 0) { throw(new_callr_error(output, killmsg)) } } ) return(ret) } ## The error RDS might be corrupt, too, if we crashed/got killed after ## an error tryCatch( remerr <- readRDS(errorres), error = function(e) throw(new_callr_error(output, killmsg)) ) if (remerr[[1]] == "error") { remerr[[2]]$message <- remerr[[2]]$message %||% "interrupt" msg <- conditionMessage(remerr[[2]]$error) newerr <- new_callr_error(output, msg) throw(newerr, parent = remerr[[2]]) } else if (remerr[[1]] == "stack") { myerr <- structure( list( message = conditionMessage(remerr[[2]]), call = conditionCall(remerr[[2]]), stack = clean_stack(remerr[[3]]) ), class = c("callr_error", "error", "condition") ) throw(myerr) } else if (remerr[[1]] == "debugger") { utils::debugger(clean_stack(remerr[[3]])) } else { throw(new_error("Unknown callr error strategy: ", remerr[[1]])) # nocov } } clean_stack <- function(stack) { att <- attributes(stack) att$names <- utils::head(utils::tail(att$names, -11), -2) res <- utils::head(utils::tail(stack, -11), -2) attributes(res) <- att res } callr/R/x-client.R0000644000176200001440000000054714025755433013434 0ustar liggesusers client_env <- local({ env <- new.env(parent = emptyenv()) env$`__callr_data__` <- new.env(parent = baseenv()) errfile <- file.path("R", "errors.R") sys.source(errfile, envir = env$`__callr_data__`, keep.source = FALSE) loadfile <- file.path("R", "load-client.R") sys.source(loadfile, envir = env$`__callr_data__`, keep.source = FALSE) env }) callr/R/setup.R0000644000176200001440000002105114037524577013050 0ustar liggesusers setup_script_files <- function(options) { within(options, { func_file <- save_function_to_temp(options) result_file <- tempfile("callr-res-") script_file <- make_vanilla_script_file( func_file, result_file, options$error) tmp_files <- c(tmp_files, func_file, script_file, result_file) }) } save_function_to_temp <- function(options) { tmp <- tempfile("callr-fun-") options$func <- transport_fun(options$func, options$package) # Once we start saving the function environments, we might get # "'package:x' may not be available when loading" warnings suppressWarnings(saveRDS(list(options$func, options$args), file = tmp)) tmp } transport_fun <- function(fun, package, source_refs = getOption("callr.keep.source")) { if (!isTRUE(source_refs)) fun <- remove_source(fun) if (isTRUE(package)) { # Do nothing } else if (identical(package, FALSE)) { environment(fun) <- .GlobalEnv } else if (is_string(package)) { environment(fun) <- asNamespace(package) } else { stop("Invalid `package` value for callr function") } fun } setup_context <- function(options) { ## Avoid R CMD check warning... repos <- libpath <- system_profile <- user_profile <- load_hook <- NULL make_path <- function(x) paste(x, collapse = .Platform$path.sep) within(options, { ## profiles profiles <- make_profiles(system_profile, user_profile, repos, libpath, load_hook, env) tmp_files <- c(tmp_files, profiles) ## environment files envs <- make_environ(profiles, libpath, env) tmp_files <- c(tmp_files, envs) ## environment variables ## First, save these, so we can restore them exactly in the subprocess, ## and sub-subprocesses are not affected by our workarounds save_env <- c("R_ENVIRON", "R_ENVIRON_USER", "R_PROFILE", "R_PROFILE_USER", "R_LIBS", "R_LIBS_USER", "R_LIBS_SITE") keep_set <- save_env %in% names(env) save_set <- !keep_set & save_env %in% names(Sys.getenv()) save_nms <- paste0("CALLR_", save_env, "_BAK") env[save_nms[keep_set]] <- env[save_env[keep_set]] env[save_nms[save_set]] <- Sys.getenv(save_env[save_set]) env <- env[setdiff(names(env), save_nms[!keep_set & !save_set])] if (is.na(env["R_ENVIRON"])) env["R_ENVIRON"] <- envs[[1]] if (is.na(env["R_ENVIRON_USER"])) env["R_ENVIRON_USER"] <- envs[[2]] if (is.na(env["R_PROFILE"])) env["R_PROFILE"] <- profiles[[1]] if (is.na(env["R_PROFILE_USER"])) env["R_PROFILE_USER"] <- profiles[[2]] if (is.na(env["R_LIBS"])) env["R_LIBS"] <- make_path(libpath) if (is.na(env["R_LIBS_USER"])) env["R_LIBS_USER"] <- make_path(libpath) if (is.na(env["R_LIBS_SITE"])) env["R_LIBS_SITE"] <- make_path(.Library.site) }) } make_profiles <- function(system, user, repos, libpath, load_hook, env) { profile_system <- tempfile("callr-spr-") profile_user <- tempfile("callr-upr-") ## Create file2 cat("", file = profile_system) cat("", file = profile_user) ## Add profiles if (system) { sys <- env["R_PROFILE"] if (is.na(sys)) { sys <- Sys.getenv( "R_PROFILE", file.path(R.home("etc"), "Rprofile.site") ) } sys <- path.expand(sys) if (file.exists(sys)) file.append(profile_system, sys) } if (identical(user, "project")) { local <- ".Rprofile" if (file.exists(local)) user <- local else user <- NA_character_ } else if (user) { user <- env["R_PROFILE_USER"] if (is.na(user)) user <- Sys.getenv("R_PROFILE_USER", NA_character_) local <- ".Rprofile" home <- path.expand("~/.Rprofile") if (is.na(user) && file.exists(local)) user <- local if (is.na(user) && file.exists(home)) user <- home } else { user <- NA_character_ } if (!is.na(user) && file.exists(user)) { xpr <- substitute( if (file.exists(user)) source(user, local = TRUE), list(user = user) ) cat(deparse(xpr), file = profile_user, append = TRUE, sep = "\n") } ## Override repos, as requested for (p in c(profile_system, profile_user)) { cat("options(repos=", deparse(repos), ")\n", sep = "", file = p, append = TRUE) } ## Set .Library.site cat(".Library.site <- ", deparse(.Library.site), "\n.libPaths(.libPaths())\n", file = profile_system, append = TRUE) ## Set .libPaths() for (p in c(profile_system, profile_user)) { cat(".libPaths(", deparse(libpath), ")\n", sep = "", file = p, append = TRUE) } if (!is.null(load_hook)) { cat(load_hook, sep = "", file = profile_user, append = TRUE) } c(profile_system, profile_user) } make_environ <- function(profiles, libpath, env) { env_sys <- tempfile("callr-sev-") env_user <- tempfile("callr-uev-") for (ef in c(env_sys, env_user)) { cat("CALLR_CHILD_R_LIBS=\"${R_LIBS}\"\n", "CALLR_CHILD_R_LIBS_USER=\"${R_LIBS_USER}\"\n", "CALLR_CHILD_R_LIBS_SITE=\"${R_LIBS_SITE}\"\n", file = ef, append = TRUE) } sys <- env["R_ENVIRON"] if (is.na(sys)) sys <- Sys.getenv("R_ENVIRON", NA_character_) if (is.na(sys)) sys <- file.path(R.home("etc"), "Renviron.site") if (!is.na(sys) && file.exists(sys)) file.append(env_sys, sys) user <- env["R_ENVIRON_USER"] if (is.na(user)) user <- Sys.getenv("R_ENVIRON_USER", NA_character_) local <- ".Renviron" home <- "~/.Renviron" if (is.na(user) && file.exists(local)) user <- local if (is.na(user) && file.exists(home)) user <- home if (!is.na(user) && file.exists(user)) file.append(env_user, user) for (ef in c(env_sys, env_user)) { cat("R_PROFILE=\"", profiles[[1]], "\"\n", file = ef, append = TRUE, sep = "") cat("R_PROFILE_USER=\"", profiles[[2]], "\"\n", file = ef, append = TRUE, sep = "") cat("R_LIBS_SITE=\"${CALLR_CHILD_R_LIBS_SITE:-", paste(.Library.site, collapse = .Platform$path.sep), "}\"\n", file = ef, append = TRUE, sep = "") cat("R_LIBS=\"${CALLR_CHILD_R_LIBS:-", paste(libpath, collapse = .Platform$path.sep), "}\"\n", file = ef, append = TRUE, sep = "") cat("R_LIBS_USER=\"${CALLR_CHILD_R_LIBS_USER:-", paste(libpath, collapse = .Platform$path.sep), "}\"\n", file = ef, append = TRUE, sep = "") } c(env_sys, env_user) } setup_callbacks <- function(options) { ## We cannot easily use `within` here, because the ## functions we create will have the wrong environment cb <- options$callback block_cb <- options$block_callback ## This is cumbersome, because we cannot easily set a named list ## element to NULL options <- append( options, list("real_block_callback" = if (!is.null(block_cb)) function(x, proc) block_cb(x)) ) callback_factory <- function(stream) { ## Need to evaluate it when the callback is created force(stream) ## In case there is no output, we create an empty file here if (!is.null(stream) && stream != "2>&1") cat("", file = stream) if (!is.null(cb)) { function(x, proc) { if (!is.null(stream)) cat(x, file = stream, sep = "\n", append = TRUE) cb(x) } } else { function(x, proc) { if (!is.null(stream)) cat(x, file = stream, sep = "\n", append = TRUE) } } } options <- append(options, list("real_callback" = callback_factory)) options } setup_r_binary_and_args <- function(options, script_file = TRUE) { options$arch <- options$arch %||% "same" if (grepl("[/\\\\]", options$arch)) { path <- options$arch } else if (options$arch != "same") { path <- file.path( R.home(), "bin", options$arch, if (os_platform() == "windows") "Rterm" else "R" ) } else { exec <- if (os_platform() == "windows") "Rterm" else "R" path <- file.path(R.home("bin"), exec) } if (!file.exists(path) && !file.exists(paste0(path, ".exe"))) { stop("Cannot find R executable at `", path, "`") } options$bin <- path options$real_cmdargs <- c(options$cmdargs, if (script_file) c("-f", options$script_file)) options } setup_rcmd_binary_and_args <- function(options) { if (os_platform() == "windows") { options$bin <- file.path(R.home("bin"), "Rcmd.exe") options$real_cmdargs <- c(options$cmd, options$cmdargs) } else { options$bin <- file.path(R.home("bin"), "R") options$real_cmdargs <- c("CMD", options$cmd, options$cmdargs) } options } setup_rscript_binary_and_args <- function(options) { if(os_platform() == "windows") { options$bin <- file.path(R.home("bin"), "Rscript.exe") } else { options$bin <- file.path(R.home("bin"), "Rscript") } options$real_cmdargs <- c(options$script, options$cmdargs) options } callr/R/error.R0000644000176200001440000000172514037277700013040 0ustar liggesusers #' Create an error object #' #' There are two kinds of errors, both have class `callr_error`: #' 1. the first one is thrown after a timeout: `callr_timeout_error`. #' 2. the second one is thrown after an R error (in the other session): #' `callr_status_error`. #' #' @param out The object returned by [run()]. #' @param msg An extra message to add to the error message. #' @keywords internal new_callr_error <- function(out, msg = NULL) { error_msg <- paste0( if (out$timeout) "callr timed out" else "callr subprocess failed", if (!is.null(msg)) paste0(": ", msg) else if (!out$timeout) ":" ) cond <- new_error(paste(error_msg)) class(cond) <- c( if (out$timeout) "callr_timeout_error" else "callr_status_error", "callr_error", class(cond)) cond$status <- out$status cond$stdout <- out$stdout cond$stderr <- out$stderr cond } #' @export print.callr_error <- function(x, ...) { err$.internal$print_rlib_error_2_0(x) invisible(x) } callr/R/eval-bg.R0000644000176200001440000000271714026375646013234 0ustar liggesusers #' Evaluate an expression in another R session, in the background #' #' Starts evaluating an R function call in a background R process, and #' returns immediately. #' #' @inheritSection r Security considerations #' @inheritParams r #' @param supervise Whether to register the process with a supervisor. If \code{TRUE}, #' the supervisor will ensure that the process is killed when the R process #' exits. #' @param ... Extra arguments are passed to the [processx::process] #' constructor. #' @return An `r_process` object, which inherits from [process], #' so all `process` methods can be called on it, and in addition it also #' has a `get_result()` method to collect the result. #' #' @export #' @examplesIf FALSE #' rx <- r_bg(function() 1 + 2) #' #' # wait until it is done #' rx$wait() #' rx$is_alive() #' rx$get_result() r_bg <- function(func, args = list(), libpath = .libPaths(), repos = default_repos(), stdout = "|", stderr = "|", poll_connection = TRUE, error = getOption("callr.error", "error"), cmdargs = c("--slave", "--no-save", "--no-restore"), system_profile = FALSE, user_profile = "project", env = rcmd_safe_env(), supervise = FALSE, package = FALSE, arch = "same", ...) { options <- as.list(environment()) options$extra <- list(...) options$load_hook <- default_load_hook() r_process$new(options = options) } callr/R/options.R0000644000176200001440000000737014026375646013412 0ustar liggesusers #' Create options for an [r_process] object #' #' @param ... Options to override, named arguments. #' @return A list of options. #' #' `r_process_options()` creates a set of options to initialize a new #' object from the `r_process` class. Its arguments must be named, the #' names are used as option names. The options correspond to (some of) #' the arguments of the [r()] function. At least the `func` option must be #' specified, this is the R function to run in the background. #' #' @export #' @examples #' ## List all options and their default values: #' r_process_options() r_process_options <- function(...) { update_options(r_process_options_default(), ...) } #' Create options for an [rcmd_process] object #' #' @param ... Options to override, named arguments. #' @return A list of options. #' #' `rcmd_process_options()` creates a set of options to initialize a new #' object from the `rcmd_process` class. Its arguments must be named, the #' names are used as option names. The options correspond to (some of) #' the arguments of the [rcmd()] function. At least the `cmd` option must #' be specified, to select the `R CMD` subcommand to run. Typically #' `cmdargs` is specified as well, to supply more arguments to `R CMD`. #' #' @export #' @examples #' ## List all options and their default values: #' rcmd_process_options() rcmd_process_options <- function(...) { update_options(rcmd_process_options_default(), ...) } #' Create options for an [rscript_process] object #' #' @param ... Options to override, named arguments. #' @return A list of options. #' #' `rscript_process_options()` creates a set of options to initialize a new #' object from the `rscript_process` class. Its arguments must be named, #' the names are used as option names. The options correspond to (some of) #' the arguments of the [rscript()] function. At least the `script` option #' must be specified, the script file to run. #' #' @export #' @examples #' ## List all options and their default values: #' rscript_process_options() rscript_process_options <- function(...) { update_options(rscript_process_options_default(), ...) } r_process_options_default <- function() { list( func = NULL, args = list(), libpath = .libPaths(), repos = default_repos(), stdout = "|", stderr = "|", poll_connection = TRUE, error = getOption("callr.error", "error"), cmdargs = c("--slave", "--no-save", "--no-restore"), system_profile = FALSE, user_profile = "project", env = character(), supervise = FALSE, load_hook = default_load_hook(), extra = list(), package = FALSE, arch = "same" ) } rcmd_process_options_default <- function() { list( cmd = NULL, cmdargs = character(), libpath = .libPaths(), stdout = "|", stderr = "|", poll_connection = TRUE, repos = default_repos(), system_profile = FALSE, user_profile = "project", env = rcmd_safe_env(), wd = ".", supervise = FALSE, extra = list(), arch = "same" ) } rscript_process_options_default <- function() { list( script = NULL, cmdargs = character(), libpath = .libPaths(), stdout = "|", stderr = "|", poll_connection = TRUE, repos = default_repos(), system_profile = FALSE, user_profile = "project", env = rcmd_safe_env(), wd = ".", color = FALSE, extra = list(), arch = "same" ) } update_options <- function(old_opts, ...) { new_opts <- list(...) stopifnot(is.named(new_opts)) check_for_option_names(old_opts, new_opts) utils::modifyList(old_opts, new_opts) } check_for_option_names <- function(old, new) { if (length(miss <- setdiff(names(new), names(old)))) { throw(new_error("Unknown option", if (length(miss) > 1) "s", ":", enumerate(sQuote(miss)))) } } callr/R/load-client.R0000644000176200001440000000665114037426724014107 0ustar liggesusers load_client_lib <- function(sofile = NULL, pxdir = NULL) { ext <- .Platform$dynlib.ext sofile_in_processx <- function() { arch <- .Platform$r_arch if (!is.null(pxdir)) { sofile <- file.path(pxdir, "libs", arch, paste0("client", ext)) if (file.exists(sofile)) return(sofile) } sofile <- system.file( "libs", arch, paste0("client", ext), package = "processx") if (sofile != "" && file.exists(sofile)) return(sofile) # Try this as well, this is for devtools/pkgload sofile <- system.file( "src", paste0("client", ext), package = "processx") if (sofile != "" && file.exists(sofile)) return(sofile) # stop() here and not throw(), because this function should be standalone stop("Cannot find client file") } if (is.null(sofile)) { sofile <- sofile_in_processx() lib <- dyn.load(sofile) } else { # This is the usual case, first we try loading it from the # temporary directory. If that fails (e.g. noexec), then # from processx. We saved the location of processx when we # loaded callr, just in case the used changes the lib path. lib <- tryCatch(dyn.load(sofile), error = function(err) err) if (inherits(lib, "error")) { sofile <- sofile_in_processx() tryCatch( lib <- dyn.load(sofile), error = function(err2) { err2$message <- err2$message <- paste0(" after ", lib$message) stop(err2) } ) } } # cleanup if setup fails on.exit(dyn.unload(sofile)) sym_encode <- getNativeSymbolInfo("processx_base64_encode", lib) sym_decode <- getNativeSymbolInfo("processx_base64_decode", lib) sym_disinh <- getNativeSymbolInfo("processx_disable_inheritance", lib) sym_write <- getNativeSymbolInfo("processx_write", lib) sym_setout <- getNativeSymbolInfo("processx_set_stdout", lib) sym_seterr <- getNativeSymbolInfo("processx_set_stderr", lib) sym_setoutf <- getNativeSymbolInfo("processx_set_stdout_to_file", lib) sym_seterrf <- getNativeSymbolInfo("processx_set_stderr_to_file", lib) env <- new.env(parent = emptyenv()) env$.path <- sofile mycall <- .Call env$base64_encode <- function(x) rawToChar(mycall(sym_encode, x)) env$base64_decode <- function(x) { if (is.character(x)) { x <- charToRaw(paste(gsub("\\s+", "", x), collapse = "")) } mycall(sym_decode, x) } env$disable_fd_inheritance <- function() mycall(sym_disinh) env$write_fd <- function(fd, data) { if (is.character(data)) data <- charToRaw(paste0(data, collapse = "")) len <- length(data) repeat { written <- mycall(sym_write, fd, data) len <- len - written if (len == 0) break if (written) data <- data[-(1:written)] Sys.sleep(.1) } } env$set_stdout <- function(fd, drop = TRUE) { mycall(sym_setout, as.integer(fd), as.logical(drop)) } env$set_stderr <- function(fd, drop = TRUE) { mycall(sym_seterr, as.integer(fd), as.logical(drop)) } env$set_stdout_file <- function(path) { mycall(sym_setoutf, as.character(path)[1]) } env$set_stderr_file <- function(path) { mycall(sym_seterrf, as.character(path)[1]) } env$.finalize <- function() { dyn.unload(env$.path) rm(list = ls(env, all.names = TRUE), envir = env) } penv <- environment() parent.env(penv) <- baseenv() reg.finalizer( env, function(e) if (".finalize" %in% names(e)) e$.finalize(), onexit = TRUE) ## Clear the cleanup method on.exit(NULL) env } callr/R/presets.R0000644000176200001440000000364314037356544013401 0ustar liggesusers #' Run an R child process, with no configuration #' #' It tries to mimic a fresh R installation. In particular: #' * No library path setting. #' * No CRAN(-like) repository is set. #' * The system and user profiles are not run. #' #' @param ... Additional arguments are passed to [r()]. #' @inheritParams r #' @inheritSection r Security considerations #' #' @family callr functions #' @export #' @examplesIf FALSE #' # Compare to r() #' r(function() .libPaths()) #' r_vanilla(function() .libPaths()) #' #' r(function() getOption("repos")) #' r_vanilla(function() getOption("repos")) r_vanilla <- function(func, args = list(), libpath = character(), repos = c(CRAN = "@CRAN@"), cmdargs = "--slave", system_profile = FALSE, user_profile = FALSE, env = character(), ...) { r(func, args = args, libpath = libpath, repos = repos, cmdargs = cmdargs, system_profile = system_profile, user_profile = user_profile, env = env, ...) } #' @rdname r #' @export r_safe <- r #' Run an R process that mimics the current R process #' #' Differences to [r()]: #' * No extra repositories are set up. #' * The `--no-save`, `--no-restore` #' command line arguments are not used. (But `--slave` still is.) #' * The system profile and the user profile are loaded. #' * No extra environment variables are set up. #' #' @inheritSection r Security considerations #' @inheritParams r #' @param ... Additional arguments are passed to [r()]. #' #' @family callr functions #' @export r_copycat <- function(func, args = list(), libpath = .libPaths(), repos = getOption("repos"), cmdargs = "--slave", system_profile = TRUE, user_profile = TRUE, env = character(), ...) { r(func, args = args, libpath = libpath, repos = repos, cmdargs = cmdargs, system_profile = system_profile, user_profile = user_profile, env = env, ...) } callr/R/errors.R0000644000176200001440000006163614037276761013240 0ustar liggesusers # # Standalone file for better error handling ---------------------------- # # If can allow package dependencies, then you are probably better off # using rlang's functions for errors. # # The canonical location of this file is in the processx package: # https://github.com/r-lib/processx/master/R/errors.R # # ## Features # # - Throw conditions and errors with the same API. # - Automatically captures the right calls and adds them to the conditions. # - Sets `.Last.error`, so you can easily inspect the errors, even if they # were not caught. # - It only sets `.Last.error` for the errors that are not caught. # - Hierarchical errors, to allow higher level error messages, that are # more meaningful for the users, while also keeping the lower level # details in the error object. (So in `.Last.error` as well.) # - `.Last.error` always includes a stack trace. (The stack trace is # common for the whole error hierarchy.) The trace is accessible within # the error, e.g. `.Last.error$trace`. The trace of the last error is # also at `.Last.error.trace`. # - Can merge errors and traces across multiple processes. # - Pretty-print errors and traces, if the cli package is loaded. # - Automatically hides uninformative parts of the stack trace when # printing. # # ## API # # ``` # new_cond(..., call. = TRUE, domain = NULL) # new_error(..., call. = TRUE, domain = NULL) # throw(cond, parent = NULL) # catch_rethrow(expr, ...) # rethrow(expr, cond) # rethrow_call(.NAME, ...) # add_trace_back(cond) # ``` # # ## Roadmap: # - better printing of anonymous function in the trace # # ## NEWS: # # ### 1.0.0 -- 2019-06-18 # # * First release. # # ### 1.0.1 -- 2019-06-20 # # * Add `rlib_error_always_trace` option to always add a trace # # ### 1.0.2 -- 2019-06-27 # # * Internal change: change topenv of the functions to baseenv() # # ### 1.1.0 -- 2019-10-26 # # * Register print methods via onload_hook() function, call from .onLoad() # * Print the error manually, and the trace in non-interactive sessions # # ### 1.1.1 -- 2019-11-10 # # * Only use `trace` in parent errors if they are `rlib_error`s. # Because e.g. `rlang_error`s also have a trace, with a slightly # different format. # # ### 1.2.0 -- 2019-11-13 # # * Fix the trace if a non-thrown error is re-thrown. # * Provide print_this() and print_parents() to make it easier to define # custom print methods. # * Fix annotating our throw() methods with the incorrect `base::`. # # ### 1.2.1 -- 2020-01-30 # # * Update wording of error printout to be less intimidating, avoid jargon # * Use default printing in interactive mode, so RStudio can detect the # error and highlight it. # * Add the rethrow_call_with_cleanup function, to work with embedded # cleancall. # # ### 1.2.2 -- 2020-11-19 # # * Add the `call` argument to `catch_rethrow()` and `rethrow()`, to be # able to omit calls. # # ### 1.2.3 -- 2021-03-06 # # * Use cli instead of crayon # # ### 1.2.4 -- 2021-04-01 # # * Allow omitting the call with call. = FALSE in `new_cond()`, etc. # # ### 1.3.0 -- 2021-04-19 # # * Avoid embedding calls in trace with embed = FALSE. # # ### 2.0.0 -- 2021-04-19 # # * Versioned classes and print methods err <- local({ # -- condition constructors ------------------------------------------- #' Create a new condition #' #' @noRd #' @param ... Parts of the error message, they will be converted to #' character and then concatenated, like in [stop()]. #' @param call. A call object to include in the condition, or `TRUE` #' or `NULL`, meaning that [throw()] should add a call object #' automatically. If `FALSE`, then no call is added. #' @param domain Translation domain, see [stop()]. #' @return Condition object. Currently a list, but you should not rely #' on that. new_cond <- function(..., call. = TRUE, domain = NULL) { message <- .makeMessage(..., domain = domain) structure( list(message = message, call = call.), class = c("condition")) } #' Create a new error condition #' #' It also adds the `rlib_error` class. #' #' @noRd #' @param ... Passed to [new_cond()]. #' @param call. Passed to [new_cond()]. #' @param domain Passed to [new_cond()]. #' @return Error condition object with classes `rlib_error`, `error` #' and `condition`. new_error <- function(..., call. = TRUE, domain = NULL) { cond <- new_cond(..., call. = call., domain = domain) class(cond) <- c("rlib_error_2_0", "rlib_error", "error", "condition") cond } # -- throwing conditions ---------------------------------------------- #' Throw a condition #' #' If the condition is an error, it will also call [stop()], after #' signalling the condition first. This means that if the condition is #' caught by an exiting handler, then [stop()] is not called. #' #' @noRd #' @param cond Condition object to throw. If it is an error condition, #' then it calls [stop()]. #' @param parent Parent condition. Use this within [rethrow()] and #' [catch_rethrow()]. throw <- function(cond, parent = NULL) { if (!inherits(cond, "condition")) { throw(new_error("You can only throw conditions")) } if (!is.null(parent) && !inherits(parent, "condition")) { throw(new_error("Parent condition must be a condition object")) } if (isTRUE(cond$call)) { cond$call <- sys.call(-1) %||% sys.call() } else if (identical(cond$call, FALSE)) { cond$call <- NULL } # Eventually the nframe numbers will help us print a better trace # When a child condition is created, the child will use the parent # error object to make note of its own nframe. Here we copy that back # to the parent. if (is.null(cond$`_nframe`)) cond$`_nframe` <- sys.nframe() if (!is.null(parent)) { cond$parent <- parent cond$call <- cond$parent$`_childcall` cond$`_nframe` <- cond$parent$`_childframe` cond$`_ignore` <- cond$parent$`_childignore` } # We can set an option to always add the trace to the thrown # conditions. This is useful for example in context that always catch # errors, e.g. in testthat tests or knitr. This options is usually not # set and we signal the condition here always_trace <- isTRUE(getOption("rlib_error_always_trace")) if (!always_trace) signalCondition(cond) # If this is not an error, then we'll just return here. This allows # throwing interrupt conditions for example, with the same UI. if (! inherits(cond, "error")) return(invisible()) if (is.null(cond$`_pid`)) cond$`_pid` <- Sys.getpid() if (is.null(cond$`_timestamp`)) cond$`_timestamp` <- Sys.time() # If we get here that means that the condition was not caught by # an exiting handler. That means that we need to create a trace. # If there is a hand-constructed trace already in the error object, # then we'll just leave it there. if (is.null(cond$trace)) cond <- add_trace_back(cond) # Set up environment to store .Last.error, it will be just before # baseenv(), so it is almost as if it was in baseenv() itself, like # .Last.value. We save the print methos here as well, and then they # will be found automatically. if (! "org:r-lib" %in% search()) { do.call("attach", list(new.env(), pos = length(search()), name = "org:r-lib")) } env <- as.environment("org:r-lib") env$.Last.error <- cond env$.Last.error.trace <- cond$trace # If we always wanted a trace, then we signal the condition here if (always_trace) signalCondition(cond) # Top-level handler, this is intended for testing only for now, # and its design might change. if (!is.null(th <- getOption("rlib_error_handler")) && is.function(th)) { th(cond) } else { if (is_interactive()) { # In interactive mode, we print the error message through # conditionMessage() and also add a note about .Last.error.trace. # R will potentially truncate the error message, so we make sure # that the note is shown. Ideally we would print the error # ourselves, but then RStudio would not highlight it. max_msg_len <- as.integer(getOption("warning.length")) if (is.na(max_msg_len)) max_msg_len <- 1000 msg <- conditionMessage(cond) adv <- style_advice( "\nType .Last.error.trace to see where the error occurred" ) dots <- "\033[0m\n[...]" if (bytes(msg) + bytes(adv) + bytes(dots) + 5L> max_msg_len) { msg <- paste0( substr(msg, 1, max_msg_len - bytes(dots) - bytes(adv) - 5L), dots ) } cond$message <- paste0(msg, adv) } else { # In non-interactive mode, we print the error + the traceback # manually, to make sure that it won't be truncated by R's error # message length limit. cat("\n", file = stderr()) cat(style_error(gettext("Error: ")), file = stderr()) out <- capture_output(print(cond)) cat(out, file = stderr(), sep = "\n") out <- capture_output(print(cond$trace)) cat(out, file = stderr(), sep = "\n") # Turn off the regular error printing to avoid printing # the error twice. opts <- options(show.error.messages = FALSE) on.exit(options(opts), add = TRUE) } # Dropping the classes and adding "duplicate_condition" is a workaround # for the case when we have non-exiting handlers on throw()-n # conditions. These would get the condition twice, because stop() # will also signal it. If we drop the classes, then only handlers # on "condition" objects (i.e. all conditions) get duplicate signals. # This is probably quite rare, but for this rare case they can also # recognize the duplicates from the "duplicate_condition" extra class. class(cond) <- c("duplicate_condition", "condition") stop(cond) } } # -- rethrowing conditions -------------------------------------------- #' Catch and re-throw conditions #' #' See [rethrow()] for a simpler interface that handles `error` #' conditions automatically. #' #' @noRd #' @param expr Expression to evaluate. #' @param ... Condition handler specification, the same way as in #' [withCallingHandlers()]. You are supposed to call [throw()] from #' the error handler, with a new error object, setting the original #' error object as parent. See examples below. #' @param call Logical flag, whether to add the call to #' `catch_rethrow()` to the error. #' @examples #' f <- function() { #' ... #' err$catch_rethrow( #' ... code that potentially errors ..., #' error = function(e) { #' throw(new_error("This will be the child error"), parent = e) #' } #' ) #' } catch_rethrow <- function(expr, ..., call = TRUE) { realcall <- if (isTRUE(call)) sys.call(-1) %||% sys.call() realframe <- sys.nframe() parent <- parent.frame() cl <- match.call() cl[[1]] <- quote(withCallingHandlers) handlers <- list(...) for (h in names(handlers)) { cl[[h]] <- function(e) { # This will be NULL if the error is not throw()-n if (is.null(e$`_nframe`)) e$`_nframe` <- length(sys.calls()) e$`_childcall` <- realcall e$`_childframe` <- realframe # We drop after realframe, until the first withCallingHandlers wch <- find_call(sys.calls(), quote(withCallingHandlers)) if (!is.na(wch)) e$`_childignore` <- list(c(realframe + 1L, wch)) handlers[[h]](e) } } eval(cl, envir = parent) } find_call <- function(calls, call) { which(vapply( calls, function(x) length(x) >= 1 && identical(x[[1]], call), logical(1)))[1] } #' Catch and re-throw conditions #' #' `rethrow()` is similar to [catch_rethrow()], but it has a simpler #' interface. It catches conditions with class `error`, and re-throws #' `cond` instead, using the original condition as the parent. #' #' @noRd #' @param expr Expression to evaluate. #' @param ... Condition handler specification, the same way as in #' [withCallingHandlers()]. #' @param call Logical flag, whether to add the call to #' `rethrow()` to the error. rethrow <- function(expr, cond, call = TRUE) { realcall <- if (isTRUE(call)) sys.call(-1) %||% sys.call() realframe <- sys.nframe() withCallingHandlers( expr, error = function(e) { # This will be NULL if the error is not throw()-n if (is.null(e$`_nframe`)) e$`_nframe` <- length(sys.calls()) e$`_childcall` <- realcall e$`_childframe` <- realframe # We just ignore the withCallingHandlers call, and the tail e$`_childignore` <- list( c(realframe + 1L, realframe + 1L), c(e$`_nframe` + 1L, sys.nframe() + 1L)) throw(cond, parent = e) } ) } #' Version of .Call that throw()s errors #' #' It re-throws error from interpreted code. If the error had class #' `simpleError`, like all errors, thrown via `error()` in C do, it also #' adds the `c_error` class. #' #' @noRd #' @param .NAME Compiled function to call, see [.Call()]. #' @param ... Function arguments, see [.Call()]. #' @return Result of the call. rethrow_call <- function(.NAME, ...) { call <- sys.call() nframe <- sys.nframe() withCallingHandlers( # do.call to work around an R CMD check issue do.call(".Call", list(.NAME, ...)), error = function(e) { e$`_nframe` <- nframe e$call <- call if (inherits(e, "simpleError")) { class(e) <- c("c_error", "rlib_error_2_0", "rlib_error", "error", "condition") } e$`_ignore` <- list(c(nframe + 1L, sys.nframe() + 1L)) throw(e) } ) } package_env <- topenv() #' Version of rethrow_call that supports cleancall #' #' This function is the same as [rethrow_call()], except that it #' uses cleancall's [.Call()] wrapper, to enable resource cleanup. #' See https://github.com/r-lib/cleancall#readme for more about #' resource cleanup. #' #' @noRd #' @param .NAME Compiled function to call, see [.Call()]. #' @param ... Function arguments, see [.Call()]. #' @return Result of the call. rethrow_call_with_cleanup <- function(.NAME, ...) { call <- sys.call() nframe <- sys.nframe() withCallingHandlers( package_env$call_with_cleanup(.NAME, ...), error = function(e) { e$`_nframe` <- nframe e$call <- call if (inherits(e, "simpleError")) { class(e) <- c("c_error", "rlib_error_2_0", "rlib_error", "error", "condition") } e$`_ignore` <- list(c(nframe + 1L, sys.nframe() + 1L)) throw(e) } ) } # -- create traceback ------------------------------------------------- #' Create a traceback #' #' [throw()] calls this function automatically if an error is not caught, #' so there is currently not much use to call it directly. #' #' @param cond Condition to add the trace to #' @param embed Whether to embed calls into the condition. #' #' @return A condition object, with the trace added. add_trace_back <- function( cond, embed = getOption("rlib_error_embed_calls", FALSE)) { idx <- seq_len(sys.parent(1L)) frames <- sys.frames()[idx] parents <- sys.parents()[idx] calls <- as.list(sys.calls()[idx]) envs <- lapply(frames, env_label) topenvs <- lapply( seq_along(frames), function(i) env_label(topenvx(environment(sys.function(i))))) nframes <- if (!is.null(cond$`_nframe`)) cond$`_nframe` else sys.parent() messages <- list(conditionMessage(cond)) ignore <- cond$`_ignore` classes <- class(cond) pids <- rep(cond$`_pid` %||% Sys.getpid(), length(calls)) if (!embed) calls <- as.list(format_calls(calls, topenvs, nframes)) if (is.null(cond$parent)) { # Nothing to do, no parent } else if (is.null(cond$parent$trace) || !inherits(cond$parent, "rlib_error_2_0")) { # If the parent does not have a trace, that means that it is using # the same trace as us. We ignore traces from non-r-lib errors. # E.g. rlang errors have a trace, but we do not use that. parent <- cond while (!is.null(parent <- parent$parent)) { nframes <- c(nframes, parent$`_nframe`) messages <- c(messages, list(conditionMessage(parent))) ignore <- c(ignore, parent$`_ignore`) } } else { # If it has a trace, that means that it is coming from another # process or top level evaluation. In this case we'll merge the two # traces. pt <- cond$parent$trace parents <- c(parents, pt$parents + length(calls)) nframes <- c(nframes, pt$nframes + length(calls)) ignore <- c(ignore, lapply(pt$ignore, function(x) x + length(calls))) envs <- c(envs, pt$envs) topenvs <- c(topenvs, pt$topenvs) calls <- c(calls, pt$calls) messages <- c(messages, pt$messages) pids <- c(pids, pt$pids) } cond$trace <- new_trace( calls, parents, envs, topenvs, nframes, messages, ignore, classes, pids) cond } topenvx <- function(x) { topenv(x, matchThisEnv = err_env) } new_trace <- function (calls, parents, envs, topenvs, nframes, messages, ignore, classes, pids) { indices <- seq_along(calls) structure( list(calls = calls, parents = parents, envs = envs, topenvs = topenvs, indices = indices, nframes = nframes, messages = messages, ignore = ignore, classes = classes, pids = pids), class = c("rlib_trace_2_0", "rlib_trace")) } env_label <- function(env) { nm <- env_name(env) if (nzchar(nm)) { nm } else { env_address(env) } } env_address <- function(env) { class(env) <- "environment" sub("^.*(0x[0-9a-f]+)>$", "\\1", format(env), perl = TRUE) } env_name <- function(env) { if (identical(env, err_env)) { return("") } if (identical(env, globalenv())) { return("global") } if (identical(env, baseenv())) { return("namespace:base") } if (identical(env, emptyenv())) { return("empty") } nm <- environmentName(env) if (isNamespace(env)) { return(paste0("namespace:", nm)) } nm } # -- printing --------------------------------------------------------- print_this <- function(x, ...) { msg <- conditionMessage(x) call <- conditionCall(x) cl <- class(x)[1L] if (!is.null(call)) { cat("<", cl, " in ", format_call(call), ":\n ", msg, ">\n", sep = "") } else { cat("<", cl, ": ", msg, ">\n", sep = "") } print_srcref(x$call) if (!identical(x$`_pid`, Sys.getpid())) { cat(" in process", x$`_pid`, "\n") } invisible(x) } print_parents <- function(x, ...) { if (!is.null(x$parent)) { cat("-->\n") print(x$parent) } invisible(x) } print_rlib_error_2_0 <- function(x, ...) { print_this(x, ...) print_parents(x, ...) } format_calls <- function(calls, topenv, nframes, messages = NULL) { calls <- map2(calls, topenv, namespace_calls) callstr <- vapply(calls, format_call_src, character(1)) if (!is.null(messages)) { callstr[nframes] <- paste0(callstr[nframes], "\n", style_error_msg(messages), "\n") } callstr } print_rlib_trace_2_0 <- function(x, ...) { cl <- paste0(" Stack trace:") cat(sep = "", "\n", style_trace_title(cl), "\n\n") callstr <- enumerate( format_calls(x$calls, x$topenv, x$nframes, x$messages) ) # Ignore what we were told to ignore ign <- integer() for (iv in x$ignore) { if (iv[2] == Inf) iv[2] <- length(callstr) ign <- c(ign, iv[1]:iv[2]) } # Plus always ignore the tail. This is not always good for # catch_rethrow(), but should be good otherwise last_err_frame <- x$nframes[length(x$nframes)] if (!is.na(last_err_frame) && last_err_frame < length(callstr)) { ign <- c(ign, (last_err_frame+1):length(callstr)) } ign <- unique(ign) if (length(ign)) callstr <- callstr[-ign] # Add markers for subprocesses if (length(unique(x$pids)) >= 2) { pids <- x$pids[-ign] pid_add <- which(!duplicated(pids)) pid_str <- style_process(paste0("Process ", pids[pid_add], ":")) callstr[pid_add] <- paste0(" ", pid_str, "\n", callstr[pid_add]) } cat(callstr, sep = "\n") invisible(x) } capture_output <- function(expr) { if (has_cli()) { opts <- options(cli.num_colors = cli::num_ansi_colors()) on.exit(options(opts), add = TRUE) } out <- NULL file <- textConnection("out", "w", local = TRUE) sink(file) on.exit(sink(NULL), add = TRUE) expr if (is.null(out)) invisible(NULL) else out } is_interactive <- function() { opt <- getOption("rlib_interactive") if (isTRUE(opt)) { TRUE } else if (identical(opt, FALSE)) { FALSE } else if (tolower(getOption("knitr.in.progress", "false")) == "true") { FALSE } else if (tolower(getOption("rstudio.notebook.executing", "false")) == "true") { FALSE } else if (identical(Sys.getenv("TESTTHAT"), "true")) { FALSE } else { interactive() } } onload_hook <- function() { reg_env <- Sys.getenv("R_LIB_ERROR_REGISTER_PRINT_METHODS", "TRUE") if (tolower(reg_env) != "false") { registerS3method("print", "rlib_error_2_0", print_rlib_error_2_0, baseenv()) registerS3method("print", "rlib_trace_2_0", print_rlib_trace_2_0, baseenv()) } } namespace_calls <- function(call, env) { if (length(call) < 1) return(call) if (typeof(call[[1]]) != "symbol") return(call) pkg <- strsplit(env, "^namespace:")[[1]][2] if (is.na(pkg)) return(call) call[[1]] <- substitute(p:::f, list(p = as.symbol(pkg), f = call[[1]])) call } print_srcref <- function(call) { src <- format_srcref(call) if (length(src)) cat(sep = "", " ", src, "\n") } `%||%` <- function(l, r) if (is.null(l)) r else l format_srcref <- function(call) { if (is.null(call)) return(NULL) file <- utils::getSrcFilename(call) if (!length(file)) return(NULL) dir <- utils::getSrcDirectory(call) if (length(dir) && nzchar(dir) && nzchar(file)) { srcfile <- attr(utils::getSrcref(call), "srcfile") if (isTRUE(srcfile$isFile)) { file <- file.path(dir, file) } else { file <- file.path("R", file) } } else { file <- "??" } line <- utils::getSrcLocation(call) %||% "??" col <- utils::getSrcLocation(call, which = "column") %||% "??" style_srcref(paste0(file, ":", line, ":", col)) } format_call <- function(call) { width <- getOption("width") str <- format(call) callstr <- if (length(str) > 1 || nchar(str[1]) > width) { paste0(substr(str[1], 1, width - 5), " ...") } else { str[1] } style_call(callstr) } format_call_src <- function(call) { callstr <- format_call(call) src <- format_srcref(call) if (length(src)) callstr <- paste0(callstr, "\n ", src) callstr } enumerate <- function(x) { paste0(style_numbers(paste0(" ", seq_along(x), ". ")), x) } map2 <- function (.x, .y, .f, ...) { mapply(.f, .x, .y, MoreArgs = list(...), SIMPLIFY = FALSE, USE.NAMES = FALSE) } bytes <- function(x) { nchar(x, type = "bytes") } # -- printing, styles ------------------------------------------------- has_cli <- function() "cli" %in% loadedNamespaces() style_numbers <- function(x) { if (has_cli()) cli::col_silver(x) else x } style_advice <- function(x) { if (has_cli()) cli::col_silver(x) else x } style_srcref <- function(x) { if (has_cli()) cli::style_italic(cli::col_cyan(x)) } style_error <- function(x) { if (has_cli()) cli::style_bold(cli::col_red(x)) else x } style_error_msg <- function(x) { sx <- paste0("\n x ", x, " ") style_error(sx) } style_trace_title <- function(x) { x } style_process <- function(x) { if (has_cli()) cli::style_bold(x) else x } style_call <- function(x) { if (!has_cli()) return(x) call <- sub("^([^(]+)[(].*$", "\\1", x) rest <- sub("^[^(]+([(].*)$", "\\1", x) if (call == x || rest == x) return(x) paste0(cli::col_yellow(call), rest) } err_env <- environment() parent.env(err_env) <- baseenv() structure( list( .internal = err_env, new_cond = new_cond, new_error = new_error, throw = throw, rethrow = rethrow, catch_rethrow = catch_rethrow, rethrow_call = rethrow_call, add_trace_back = add_trace_back, onload_hook = onload_hook, print_this = print_this, print_parents = print_parents ), class = c("standalone_errors", "standalone")) }) # These are optional, and feel free to remove them if you prefer to # call them through the `err` object. new_cond <- err$new_cond new_error <- err$new_error throw <- err$throw rethrow <- err$rethrow rethrow_call <- err$rethrow_call rethrow_call_with_cleanup <- err$.internal$rethrow_call_with_cleanup callr/R/processx-forward.R0000644000176200001440000000025413612550263015207 0ustar liggesusers #' @importFrom processx run #' @export processx::run #' @importFrom processx process #' @export processx::process #' @importFrom processx poll #' @export processx::poll callr/R/run.R0000644000176200001440000000176714023425430012507 0ustar liggesusers run_r <- function(options) { oldwd <- getwd() setwd(options$wd) on.exit(setwd(oldwd), add = TRUE) ## We redirect stderr to stdout if either of these are true: ## - stderr is the string "2>&1" ## - both stdout and stderr are non-null, and they are the same stderr_to_stdout <- with( options, (!is.null(stderr) && stderr == "2>&1") || (!is.null(stdout) && !is.null(stderr) && stdout == stderr) ) res <- with( options, with_envvar( env, do.call(processx::run, c(list( bin, args = real_cmdargs, stdout_line_callback = real_callback(stdout), stderr_line_callback = real_callback(stderr), stdout_callback = real_block_callback, stderr_callback = real_block_callback, stderr_to_stdout = stderr_to_stdout, echo_cmd = echo, echo = show, spinner = spinner, error_on_status = fail_on_status, timeout = timeout), extra) ) ) ) res$command <- c(options$bin, options$real_cmdargs) res } callr/R/r-process.R0000644000176200001440000000516213620746743013630 0ustar liggesusers #' External R Process #' #' @description #' An R process that runs in the background. This is an R6 class that #' extends the [processx::process] class. The process starts in the #' background, evaluates an R function call, and then quits. #' #' @examplesIf FALSE #' ## List all options and their default values: #' r_process_options() #' #' ## Start an R process in the background, wait for it, get result #' opts <- r_process_options(func = function() 1 + 1) #' rp <- r_process$new(opts) #' rp$wait() #' rp$get_result() #' @export r_process <- R6::R6Class( "r_process", inherit = processx::process, public = list( #' @description #' Start a new R process in the background. #' @param options A list of options created via [r_process_options()]. #' @return A new `r_process` object. initialize = function(options) rp_init(self, private, super, options), #' @description #' Return the result, an R object, from a finished #' background R process. If the process has not finished yet, it throws #' an error. (You can use `wait()` method (see [processx::process]) to #' wait for the process to finish, optionally with a timeout.) You can #' also use [processx::poll()] to wait for the end of the process, #' together with other processes or events. #' #' @return The return value of the R expression evaluated in the R #' process. get_result = function() rp_get_result(self, private), #' @description #' Clean up temporary files once an R process has finished and its #' handle is garbage collected. finalize = function() { unlink(private$options$tmp_files, recursive = TRUE) if ("finalize" %in% ls(super)) super$finalize() } ), private = list( options = NULL ) ) rp_init <- function(self, private, super, options) { ## This contains the context that we set up in steps options <- convert_and_check_my_args(options) options <- setup_script_files(options) options <- setup_context(options) options <- setup_r_binary_and_args(options) private$options <- options with_envvar( options$env, do.call(super$initialize, c(list( options$bin, options$real_cmdargs, stdout = options$stdout, stderr = options$stderr, poll_connection = options$poll_connection, supervise = options$supervise), options$extra)) ) invisible(self) } rp_get_result <- function(self, private) { if (self$is_alive()) throw(new_error("Still alive")) ## This is artificial... out <- list( status = self$get_exit_status(), stdout = "", stderr = "", timeout = FALSE ) get_result(out, private$options) } callr/R/rcmd.R0000644000176200001440000001160614023345544012630 0ustar liggesusers #' Run an `R CMD` command #' #' Run an `R CMD` command form within R. This will usually start #' another R process, from a shell script. #' #' Starting from `callr` 2.0.0, `rcmd()` has safer defaults, the same as #' the `rcmd_safe()` default values. Use [rcmd_copycat()] for the old #' defaults. #' #' @param cmd Command to run. See `R --help` from the command #' line for the various commands. In the current version of R (3.2.4) #' these are: `BATCH`, `COMPILE`, `SHLIB`, `INSTALL`, `REMOVE`, `build`, #' `check`, `LINK`, `Rprof`, `Rdconv`, `Rd2pdf`, `Rd2txt`, `Stangle`, #' `Sweave`, `Rdiff`, `config`, `javareconf`, `rtags`. #' @param cmdargs Command line arguments. #' @param stdout Optionally a file name to send the standard output to. #' @param stderr Optionally a file name to send the standard error to. #' It may be the same as `stdout`, in which case standard error is #' redirected to standard output. It can also be the special string #' `"2>&1"`, in which case standard error will be redirected to standard #' output. #' @param poll_connection Whether to have a control connection to #' the process. This is used to transmit messages from the subprocess #' to the parent. #' @param echo Whether to echo the complete command run by `rcmd`. #' @param wd Working directory to use for running the command. Defaults #' to the current working directory. #' @param fail_on_status Whether to throw an R error if the command returns #' with a non-zero status code. By default no error is thrown. #' @inheritParams r #' @inheritSection r Security considerations #' @return A list with the command line `$command`), #' standard output (`$stdout`), standard error (`stderr`), #' exit status (`$status`) of the external `R CMD` command, and #' whether a timeout was reached (`$timeout`). #' #' @family R CMD commands #' @export #' #' @examplesIf FALSE #' rcmd("config", "CC") rcmd <- function(cmd, cmdargs = character(), libpath = .libPaths(), repos = default_repos(), stdout = NULL, stderr = NULL, poll_connection = TRUE, echo = FALSE, show = FALSE, callback = NULL, block_callback = NULL, spinner = show && interactive(), system_profile = FALSE, user_profile = "project", env = rcmd_safe_env(), timeout = Inf, wd = ".", fail_on_status = FALSE, ...) { ## This contains the context that we set up in steps options <- convert_and_check_my_args(as.list(environment())) options$extra <- list(...) options <- setup_context(options) options <- setup_callbacks(options) options <- setup_rcmd_binary_and_args(options) ## This cleans up everything... on.exit(unlink(options$tmp_files, recursive = TRUE), add = TRUE) run_r(options) } #' @rdname rcmd #' @export rcmd_safe <- rcmd #' `rcmd_safe_env` returns a set of environment variables that are #' more appropriate for [rcmd_safe()]. It is exported to allow manipulating #' these variables (e.g. add an extra one), before passing them to the #' [rcmd()] functions. #' #' It currently has the following variables: #' * `CYGWIN="nodosfilewarning"`: On Windows, do not warn about MS-DOS #' style file names. #' * `R_TESTS=""` This variable is set by `R CMD check`, and makes the #' child R process load a startup file at startup, from the current #' working directory, that is assumed to be the `/test` directory #' of the package being checked. If the current working directory is #' changed to something else (as it typically is by `testthat`, then R #' cannot start. Setting it to the empty string ensures that `callr` can #' be used from unit tests. #' * `R_BROWSER="false"`: typically we don't want to start up a browser #' from the child R process. #' * `R_PDFVIEWER="false"`: similarly for the PDF viewer. #' #' Note that `callr` also sets the `R_ENVIRON`, `R_ENVIRON_USER`, #' `R_PROFILE` and `R_PROFILE_USER` environment variables #' appropriately, unless these are set by the user in the `env` argument #' of the `r`, etc. calls. #' #' @return A named character vector of environment variables. #' #' @export rcmd_safe_env <- function() { vars <- c( CYGWIN = "nodosfilewarning", R_TESTS = "", R_BROWSER = "false", R_PDFVIEWER = "false" ) vars } #' Call and `R CMD` command, while mimicking the current R session #' #' This function is similar to [rcmd()], but it has slightly different #' defaults: #' * The `repos` options is unchanged. #' * No extra environment variables are defined. #' #' @inheritSection r Security considerations #' @inheritParams rcmd #' @param ... Additional arguments are passed to [rcmd()]. #' #' @family R CMD commands #' @export rcmd_copycat <- function(cmd, cmdargs = character(), libpath = .libPaths(), repos = getOption("repos"), env = character(), ...) { rcmd(cmd, cmdargs = cmdargs, libpath = libpath, repos = repos, env = env, ...) } callr/R/hook.R0000644000176200001440000000376114037426724012653 0ustar liggesusers common_hook <- function() { substitute({ # This should not happen in a new R session, but just to be safe while ("tools:callr" %in% search()) detach("tools:callr") env <- readRDS(`__envfile__`) do.call("attach", list(env, pos = length(search()), name = "tools:callr")) data <- env$`__callr_data__` data$pxlib <- data$load_client_lib( data$sofile[[paste0("arch-", .Platform$r_arch)]], data$pxdir ) options(error = function() invokeRestart("abort")) rm(list = c("data", "env")) lapply( c("R_ENVIRON", "R_ENVIRON_USER", "R_PROFILE", "R_PROFILE_USER", "R_LIBS", "R_LIBS_USER", "R_LIBS_SITE"), function(var) { bakvar <- paste0("CALLR_", var, "_BAK") val <- Sys.getenv(bakvar, NA_character_) if (!is.na(val)) { do.call("Sys.setenv", structure(list(val), names = var)) } else { Sys.unsetenv(var) } Sys.unsetenv(bakvar) } ) Sys.unsetenv("CALLR_CHILD_R_LIBS") Sys.unsetenv("CALLR_CHILD_R_LIBS_SITE") Sys.unsetenv("CALLR_CHILD_R_LIBS_USER") }, list("__envfile__" = env_file)) } default_load_hook <- function(user_hook = NULL) { prepare_client_files() hook <- common_hook() if (!is.null(user_hook)) { hook <- substitute({ d; u }, list(d = hook, u = user_hook)) } paste0(deparse(hook), "\n") } session_load_hook <- function(user_hook = NULL) { chook <- common_hook() ehook <- substitute({ data <- as.environment("tools:callr")$`__callr_data__` data$pxlib$disable_fd_inheritance() rm(data) }) hook <- substitute({ c; e }, list(c = chook, e = ehook)) if (!is.null(user_hook)) { hook <- substitute({ d; u }, list(d = hook, u = user_hook)) } hook <- substitute({ err_ <- TRUE callr_startup_hook <- function() { on.exit(if (err_) quit("no", 1, TRUE)) { `_hook_` } err_ <<- FALSE } callr_startup_hook() rm(err_, callr_startup_hook) }, list("_hook_" = hook)) paste0(deparse(hook), "\n") } callr/R/rscript.R0000644000176200001440000000630214037511674013372 0ustar liggesusers #' Run an R script #' #' It uses the `Rscript` program corresponding to the current R version, #' to run the script. It streams `stdout` and `stderr` of the process. #' #' @inheritSection r Security considerations #' @inheritParams rcmd #' @param script Path of the script to run. #' @param color Whether to use terminal colors in the child process, #' assuming they are active in the parent process. #' #' @export rscript <- function(script, cmdargs = character(), libpath = .libPaths(), repos = default_repos(), stdout = NULL, stderr = NULL, poll_connection = TRUE, echo = FALSE, show = TRUE, callback = NULL, block_callback = NULL, spinner = FALSE, system_profile = FALSE, user_profile = "project", env = rcmd_safe_env(), timeout = Inf, wd = ".", fail_on_status = TRUE, color = TRUE, ...) { load_hook <- rscript_load_hook_color(color) options <- convert_and_check_my_args(as.list(environment())) options$extra <- list(...) options <- setup_context(options) options <- setup_callbacks(options) options <- setup_rscript_binary_and_args(options) ## This cleans up everything... on.exit(unlink(options$tmp_files, recursive = TRUE), add = TRUE) invisible(run_r(options)) } rscript_load_hook_color <- function(color) { if (!color) return("") nc <- tryCatch( cli::num_ansi_colors(), error = function(e) 1L ) if (nc == 1) return("") expr <- substitute( options(crayon.enabled = TRUE, crayon.colors = `_nc_`), list("_nc_" = nc) ) paste0(deparse(expr), "\n") } #' External `Rscript` process #' #' @description #' An `Rscript script.R` command that runs in the background. This is an #' R6 class that extends the [processx::process] class. #' #' @name rscript_process #' @examplesIf FALSE #' options <- rscript_process_options(script = "script.R") #' rp <- rscript_process$new(options) #' rp$wait() #' rp$read_output_lines() #' @export rscript_process <- R6::R6Class( "rscript_process", inherit = processx::process, public = list( #' @description Create a new `Rscript` process. #' @param options A list of options created via #' [rscript_process_options()]. initialize = function(options) rscript_init(self, private, super, options), #' @description Clean up after an `Rsctipt` process, remove #' temporary files. finalize = function() { unlink(private$options$tmp_files, recursive = TRUE) if ("finalize" %in% ls(super)) super$finalize() } ), private = list( options = NULL ) ) rscript_init <- function(self, private, super, options) { options$load_hook <- rscript_load_hook_color(options$color) options <- convert_and_check_my_args(options) options <- setup_context(options) options <- setup_rscript_binary_and_args(options) private$options <- options oldwd <- getwd() setwd(options$wd) on.exit(setwd(oldwd), add = TRUE) with_envvar( options$env, do.call(super$initialize, c(list(options$bin, options$real_cmdargs, stdout = options$stdout, stderr = options$stderr, poll_connection = options$poll_connection), options$extra)) ) invisible(self) } callr/R/eval.R0000644000176200001440000002065214026375646012644 0ustar liggesusers #' Evaluate an expression in another R session #' #' From `callr` version 2.0.0, `r()` is equivalent to `r_safe()`, and #' tries to set up a less error prone execution environment. In particular: #' * Ensures that at least one reasonable CRAN mirror is set up. #' * Adds some command line arguments to avoid saving `.RData` files, etc. #' * Ignores the system and user profiles (by default). #' * Sets various environment variables: `CYGWIN` to avoid #' warnings about DOS-style paths, `R_TESTS` to avoid issues #' when `callr` is invoked from unit tests, `R_BROWSER` #' and `R_PDFVIEWER` to avoid starting a browser or a PDF viewer. #' See [rcmd_safe_env()]. #' #' The `r()` function from before 2.0.0 is called [r_copycat()] now. #' #' @param func Function object to call in the new R process. #' The function should be self-contained and only refer to #' other functions and use variables explicitly from other packages #' using the `::` notation. By default the environment of the function #' is set to `.GlobalEnv` before passing it to the child process. #' (See the `package` option if you want to keep the environment.) #' Because of this, it is good practice to create an anonymous #' function and pass that to `callr`, instead of passing #' a function object from a (base or other) package. In particular #' ``` #' r(.libPaths) #' ``` #' does not work, because `.libPaths` is defined in a special #' environment, but #' ``` #' r(function() .libPaths()) #' ``` #' works just fine. #' @param args Arguments to pass to the function. Must be a list. #' @param libpath The library path. #' @param repos The `repos` option. If `NULL`, then no #' `repos` option is set. This options is only used if #' `user_profile` or `system_profile` is set `FALSE`, #' as it is set using the system or the user profile. #' @param stdout The name of the file the standard output of #' the child R process will be written to. #' If the child process runs with the `--slave` option (the default), #' then the commands are not echoed and will not be shown #' in the standard output. Also note that you need to call `print()` #' explicitly to show the output of the command(s). #' @param stderr The name of the file the standard error of #' the child R process will be written to. #' In particular `message()` sends output to the standard #' error. If nothing was sent to the standard error, then this file #' will be empty. This argument can be the same file as `stdout`, #' in which case they will be correctly interleaved. If this is the #' string `"2>&1"`, then standard error is redirected to standard output. #' @param error What to do if the remote process throws an error. #' See details below. #' @param poll_connection Whether to have a control connection to #' the process. This is used to transmit messages from the subprocess #' to the main process. #' @param cmdargs Command line arguments to pass to the R process. #' Note that `c("-f", rscript)` is appended to this, `rscript` #' is the name of the script file to run. This contains a call to the #' supplied function and some error handling code. #' @param show Logical, whether to show the standard output on the screen #' while the child process is running. Note that this is independent #' of the `stdout` and `stderr` arguments. The standard #' error is not shown currently. #' @param callback A function to call for each line of the standard #' output and standard error from the child process. It works together #' with the `show` option; i.e. if `show = TRUE`, and a #' callback is provided, then the output is shown of the screen, and the #' callback is also called. #' @param block_callback A function to call for each block of the standard #' output and standard error. This callback is not line oriented, i.e. #' multiple lines or half a line can be passed to the callback. #' @param spinner Whether to show a calming spinner on the screen while #' the child R session is running. By default it is shown if #' `show = TRUE` and the R session is interactive. #' @param system_profile Whether to use the system profile file. #' @param user_profile Whether to use the user's profile file. #' If this is `"project"`, then only the profile from the working #' directory is used, but the `R_PROFILE_USER` environment variable #' and the user level profile are not. See also "Security considerations" #' below. #' @param env Environment variables to set for the child process. #' @param timeout Timeout for the function call to finish. It can be a #' [base::difftime] object, or a real number, meaning seconds. #' If the process does not finish before the timeout period expires, #' then a `system_command_timeout_error` error is thrown. `Inf` #' means no timeout. #' @param package Whether to keep the environment of `func` when passing #' it to the other package. Possible values are: #' * `FALSE`: reset the environment to `.GlobalEnv`. This is the default. #' * `TRUE`: keep the environment as is. #' * `pkg`: set the environment to the `pkg` package namespace. #' @param arch Architecture to use in the child process, for multi-arch #' builds of R. By default the same as the main process. See #' [supported_archs()]. If it contains a forward or backward slash #' character, then it is taken as the path to the R executable. #' Note that on Windows you need the path to `Rterm.exe`. #' @param ... Extra arguments are passed to [processx::run()]. #' @return Value of the evaluated expression. #' #' @section Error handling: #' #' `callr` handles errors properly. If the child process throws an #' error, then `callr` throws an error with the same error message #' in the main process. #' #' The `error` expert argument may be used to specify a different #' behavior on error. The following values are possible: #' * `error` is the default behavior: throw an error in the main process, #' with a prefix and the same error message as in the subprocess. #' * `stack` also throws an error in the main process, but the error #' is of a special kind, class `callr_error`, and it contains #' both the original error object, and the call stack of the child, #' as written out by [utils::dump.frames()]. This is now deprecated, #' because the error thrown for `"error"` has the same information. #' * `debugger` is similar to `stack`, but in addition #' to returning the complete call stack, it also start up a debugger #' in the child call stack, via [utils::debugger()]. #' #' The default error behavior can be also set using the `callr.error` #' option. This is useful to debug code that uses `callr`. #' #' callr uses parent errors, to keep the stacks of the main process and the #' subprocess(es) in the same error object. #' #' @section Security considerations: #' #' `callr` makes a copy of the user's `.Renviron` file and potentially of #' the local or user `.Rprofile`, in the session temporary #' directory. Avoid storing sensitive information such as passwords, in #' your environment file or your profile, otherwise this information will #' get scattered in various files, at least temporarily, until the #' subprocess finishes. You can use the keyring package to avoid passwords #' in plain files. #' #' @family callr functions #' @examplesIf FALSE #' # Workspace is empty #' r(function() ls()) #' #' # library path is the same by default #' r(function() .libPaths()) #' .libPaths() #' #' @export r <- function(func, args = list(), libpath = .libPaths(), repos = default_repos(), stdout = NULL, stderr = NULL, poll_connection = TRUE, error = getOption("callr.error", "error"), cmdargs = c("--slave", "--no-save", "--no-restore"), show = FALSE, callback = NULL, block_callback = NULL, spinner = show && interactive(), system_profile = FALSE, user_profile = "project", env = rcmd_safe_env(), timeout = Inf, package = FALSE, arch = "same", ...) { ## This contains the context that we set up in steps options <- convert_and_check_my_args(as.list(environment())) options$extra <- list(...) options$load_hook <- default_load_hook() ## This cleans up everything... on.exit(unlink(options$tmp_files, recursive = TRUE), add = TRUE) options <- setup_script_files(options) options <- setup_context(options) options <- setup_callbacks(options) options <- setup_r_binary_and_args(options) out <- run_r(options) get_result(output = out, options) } callr/R/package.R0000644000176200001440000000267714037426724013313 0ustar liggesusers #' Call R from R #' #' It is sometimes useful to perform a computation in a separate R #' process, without affecting the current R process at all. This packages #' does exactly that. #' #' @name callr "_PACKAGE" ## R CMD check workaround dummy_r6 <- function() R6::R6Class clients <- NULL sofiles <- NULL env_file <- NULL ## We save this as an RDS, so it can be loaded quickly .onLoad <- function(libname, pkgname) { err$onload_hook() env_file <<- tempfile("callr-env-") clients <<- asNamespace("processx")$client sofiles <<- get_client_files() client_env$`__callr_data__`$sofile <- sofiles client_env$`__callr_data__`$pxdir <- system.file(package = "processx") } prepare_client_files <- function() { for (aa in names(client_env$`__callr_data__`$sofile)) { fn <- client_env$`__callr_data__`$sofile[[aa]] if (!file.exists(fn)) writeBin(clients[[aa]]$bytes, fn) } if (!file.exists(env_file)) { saveRDS(client_env, file = env_file, version = 2, compress = FALSE) } invisible() } get_client_files <- function() { archs <- ls(clients) vapply(archs, function(aa) { file.path( tempdir(), paste0( "callr-client-", sub("arch-", "", aa), "-", substr(clients[[aa]]$md5, 1, 7), .Platform$dynlib.ext ) ) }, character(1)) } .onUnload <- function(libpath) { unlink( normalizePath(c(sofiles, env_file), mustWork = FALSE), recursive = TRUE, force = TRUE ) } callr/R/rcmd-bg.R0000644000176200001440000000201713612550263013211 0ustar liggesusers #' Run an `R CMD` command in the background #' #' The child process is started in the background, and the function #' return immediately. #' #' @inheritSection r Security considerations #' @inheritParams rcmd #' @param supervise Whether to register the process with a supervisor. If \code{TRUE}, #' the supervisor will ensure that the process is killed when the R process #' exits. #' @param ... Extra arguments are passed to the [processx::process] #' constructor. #' @return It returns a [process] object. #' #' @family R CMD commands #' @export rcmd_bg <- function(cmd, cmdargs = character(), libpath = .libPaths(), stdout = "|", stderr = "|", poll_connection = TRUE, repos = default_repos(), system_profile = FALSE, user_profile = "project", env = rcmd_safe_env(), wd = ".", supervise = FALSE, ...) { options <- as.list(environment()) options$extra <- list(...) rcmd_process$new(options = options) } callr/NEWS.md0000644000176200001440000001667414037545504012472 0ustar liggesusers # callr 3.7.0 * Reporting errors is much faster now (#185). * The `user_profile` option of `r_vanilla()` defaults to `FALSE` now (#194). * It is now possible to set R environment variables (`R_ENVIRON_USER`, `R_PROFILE_USER`, etc.) via the `env` argument (#193). # callr 3.6.0 * callr now supports starting an R process with a different architecture, so on Windows 64-bit R can start a 32-bit R background process, and vice-versa (#95). * callr now handles symbolic arguments properly, and does not evaluate them. E.g. `callr::r(function(x) x, list(quote(foobar)))` works now (#175). * `callr::r_session` does not leak file descriptors now in the sub-process (#184). # callr 3.5.1 * `callr::r_session` now handles large messages from the subprocess well (#168). # callr 3.5.0 * callr can now pass the environment of the function to the subprocess, optionally. This makes it easier to call an internal function of a package in a subprocess. See the `package` argument of `r()`, `r_bg()`, `r_session$run()`, etc. (#147). # callr 3.4.4 * An `r_session` now exits if the load hook errors. This generates an error if the session is started with `wait = TRUE`. For `wait = FALSE` the first `$read()` operation will return with an error (#162). # callr 3.4.3 * `default_repos()` now returns a list if `getOption("repos")` is a list, and a vector otherwise, on R 4.x.y as well. # callr 3.4.2 * Improved error messages. Error messages are now fully printed after an error. In non-interactive sessions, the stack trace is printed as well. # callr 3.4.1 * callr is now more careful when loading the local `.Rprofile` in the subprocess. This fixes issues with packrat and renv that use `.Rprofile` for setup (#139). * callr functions fail early if environment file is missing (#123, @jdblischak) # callr 3.4.0 * All callr functions and background processes properly clean up temporary files now (#104). * callr now uses a more principled setup for the library path, and restores the related environment variables in the child process. This is a **breaking change** if you relied on having the library set in a `system()` subprocess of the callr subprocess (#114). * Better printing of `rlang_error`s that happened in the subprocess. * The stacking of error objects is slightly different now, as we keep the unmodified error from the subprocess in `$parent$error`. * callr now loads `.Rprofile` files from the current working directory by default. This works better with packrat, renv, and other software that relies on a local profile for initialization (#131). # callr 3.3.2 No user visible changes in this version. # callr 3.3.1 * `r_session` now avoids creating `data` and `env` objects in the global environment of the subprocess. * New `$debug()` method for `r_session` to inspect the dumped frames in the subprocess, after an error. # callr 3.3.0 * callr now sets the `.Last.error` variable for every uncaught callr error to the error condition, and also sets `.Last.error.trace` to its stack trace. If the error originates in the subprocess, then `.Last.error` is a hierarchical error object, and `.Last.error.trace` merges the traces from the two processes. See the `README.md` for an example. * New `$traceback()` method for `r_session`, to run `traceback()` in the subprocess, after an error. * A callr subprocess now does not load any R packages by default. * New vignette, that showcases `r_session`. # callr 3.2.0 * `r()`, `rcmd()` and `rscript()` can now redirect the standard error of the subprocess its standard output. This allows to keep them correctly interleaved. For this, you need to either set the `stderr` argument to the special string `"2>&1"`, or to the same output file as specified for `stdout`. * `r()`, `rcmd()` and `rscript()` now pass `...` arguments to `processx::run()`. `r_bg()` and `rcmd_bg()` pass `...` arguments to the `processx::process` constructor. For `r_process`, `rcmd_process` and `rscript_process` extra arguments can be specified as `options$extra`, these are also passed to the `processx::process` constructor (#100). # callr 3.1.1 * `r()`, `r_bg()`, etc. now handle messages from the cliapp package properly. They used to make the R session exit. * Better default for the `repos` option in callr subprocesses. callr no longer creates duplicate "CRAN" entries. By default the new `default_repos()` function is used to set `repos` in the subprocess. # callr 3.1.0 * New `rscript()` function and `rscript_process` class to execute R scripts via `Rscript` (#40, #81). * Library paths are now correctly set up for `system()` (and similar) calls from the callr subprocesses (#83, #84). * Pass `options("repos")` to the child process as is, without checking. Closes #82. * `r_session$run_with_output()` now returns an S3 object with class `callr_session_result`. * `r_session$run*()` handle interrupts properly. It tries to interrupt the background process fist, kills it if it is not interruptible, and then re-throws the interrupt condition, going back to the top level prompt if the re-thrown condition is uncaught. # callr 3.0.0 * New `r_session` class: a background R session you can send commands to (#56). * Rewrote passing the library path to the subprocess (#73, #75) * Retain names of the `repos` option (#67, @jennybc) # callr 2.0.4 * pkgdown web site at https://callr.r-lib.org (#52, #53). * callr users `.Renviron` files now (and `R_ENVIRON_USER` as well), but overrides the library path, as requested in `r()`, etc. (#30). * callr now handles the case when the subprocess calls `quit()`. * callr now uses the processx package, instead of embedded code, to create and control processes. # callr 2.0.3 * The default behavior on error can be set now with the `callr.error` option. * Better error message if the child R process crashes or gets killed. (#41) * `r_bg` and `rcmd_bg` now have the `supervise` option (#45). # callr 2.0.2 * Fix a bug with R-devel, caused by the change on 2018-02-08: https://github.com/wch/r-source/commit/924582943706100e88a11d6bb0585d25779c91f5 #37, #38 * Fix a race condition on Windows, when creating named pipes for `stdout` or `stderr`. The client sometimes didn't wait for the server, and callr failed with ERROR_PIPE_BUSY (231, All pipe instances are busy). # callr 2.0.1 * Fix compilation issues on Solaris * Fix a test failure on macOS # callr 2.0.0 * Run R or `R CMD` in the background, see `r_bg()`, `rcmd_bg()`, and also `r_process` and `rcmd_process` * The defaults for `r()` are safer now, the match the defaults of `r_safe()`. `r_safe()` is kept for compatibility. `r_copycat()` has the old `r()` defaults. * The defaults for `rcmd()` are safer now, the match the defaults of `rcmd_safe()`. `rcmd_safe()` is kept for compatibility. `rcmd_copycat()` has the old `rcmd()` defaults. * Support block callbacks, in addition to line callbacks. Block callbacks are called for arbitrary chunks of output, even without a newline * Add `spinner` argument to show a spinner in `r()` or `rcmd()` * Support timeouts, via the `timeout` argument * Fix bug when `stdout` and `stderr` are redirected to the same file * `rcmd_safe_env()` to allow extending the environment variables set in safe mode * `rcmd()` gets a `fail_on_status` argument * `rcmd()` gets an `echo` argument to potentially show the command to be run on the screen (#15) * `rcmd()` gets a `wd` argument to set the working directory # callr 1.0.0 First public release. callr/MD50000644000176200001440000001132114037561432011662 0ustar liggesusers5b2e6eee2ff5214fcc0a44bed21f11f6 *DESCRIPTION 09079911e7389192b5cd3b99be725742 *LICENSE 3d3c9bbc9109621090e9fbb9216e2403 *NAMESPACE 11564b515e3849b1b361188ad30dc997 *NEWS.md 7b4246baa698319c22353ad73253651e *R/check.R 32b3740b9b169e16bfd8623362c79602 *R/error.R 8b14d08c68b8d583e974894eb1a7fbb5 *R/errors.R e6e64e183da517946cef70d258d087a7 *R/eval-bg.R b35fbc667a967f898160fa4dd7a407a1 *R/eval.R 1a23f35f7bbc27d66cef3b3d8adb5c21 *R/hook.R 39351d3840577617b7a5837e4772662d *R/load-client.R 88dea3f0167bb3245c8893be49656c3b *R/options.R d15dede1da798a31be6ff415f415e5bb *R/package.R f69a6206127612a37bee50e104b02f23 *R/presets.R 57e46eb271bbc3020eb4c7bf45fa5625 *R/processx-forward.R b25c8314bed7881c36b13b18d6e64daf *R/r-process.R a5c17fba5423b5c59da7050c4cdf9728 *R/r-session.R 41320594a9a458d32e823ab1fac2bb0f *R/rcmd-bg.R ae4070b19c1a01b9bbf4e77e00b76f77 *R/rcmd-process.R e4c3f71ca6e59932c0dcef14825aa776 *R/rcmd.R 704c3106ef21e8daa2074f13f5e60c92 *R/result.R 06405a0b145bf0b6bc6a0fb956253f4f *R/rscript.R fbd5775d259e9d8aa802aa9f873c19fa *R/run.R 5cb5d77f2b85c3dd7c185251fcc3a26a *R/script.R 38c8f97307eb82a1b652abcee0c3ac19 *R/setup.R 9c1278cef1060b35f91e0b412f795e77 *R/utils.R 7f6ddbbbd5c5a5bec45aa5ed04d4e33b *R/x-client.R 658538660f2c8eeb94c5fb6529620758 *README.md 881fd9c80031ef2266af383832d074b6 *inst/WORDLIST f016ae44474fd10d44cef6ccaf294913 *inst/developer-notes.md bec8e97d93386d51eedc323fadef627c *man/callr.Rd 66b8df108030a8f7d6857033105fc6ed *man/convert_and_check_my_args.Rd 88d3e9765ac8330eefe6b15b68b3ee75 *man/default_repos.Rd 3a107dfbffecc4e75864c19216800a4c *man/get_result.Rd 8777ad804f78dfd014aee5f735708276 *man/new_callr_error.Rd 94653bc057c06c739c88908cca651ef6 *man/r.Rd 2d7eaab79266824391f4d7fd8b10f3e1 *man/r_bg.Rd fea94fa263f2d17786520e1cad78032c *man/r_copycat.Rd 4890d1db6f4078820c4ad86e912df532 *man/r_process.Rd 0ba68b24099710b8a7257b172cfb8d26 *man/r_process_options.Rd 0eeeb2e7c6487d3bfd099a30ed219e37 *man/r_session.Rd ff5646f8d83aa8e034623cfd4f6239b7 *man/r_session_debug.Rd e4e03a5ce2da6ec48432b241e4049421 *man/r_session_options.Rd 638bfa08e8f22cef26a104664a8530ac *man/r_vanilla.Rd 4c667f799f67a597039e34b16a50b7bc *man/rcmd.Rd 0c374e56db0d30c0db408dd65c475528 *man/rcmd_bg.Rd 752091f0ab22fa29ad5c93dcda3118bd *man/rcmd_copycat.Rd 08971fcefccc8fae135ee4685924b02d *man/rcmd_process.Rd cbbf751d3ff24c9e94b4b022650a5d26 *man/rcmd_process_options.Rd 35170cbc634370608fe653844ea34bbe *man/rcmd_safe_env.Rd 0759420e2f99421950fd3f48603f4847 *man/reexports.Rd c275e1f4d0b4106139458695ba631501 *man/rscript.Rd f0876767a3c9161bb6ad725e84788069 *man/rscript_process.Rd 6725f7b56f6004b3085ff7c8fa7445a9 *man/rscript_process_options.Rd 343cd45d6ef35b84073cfb5fb4863723 *man/supported_archs.Rd 7a2272f8f60cc24c366462371942b9cc *tests/testthat.R 109be60c7f5501f2f255c3a7dd0d01cc *tests/testthat/fixtures/D1 12a5442aa928a92aa446356fb4a209a8 *tests/testthat/fixtures/csomag/DESCRIPTION d41d8cd98f00b204e9800998ecf8427e *tests/testthat/fixtures/csomag/NAMESPACE 38fac57769ccc547f9acbffc0b0147bf *tests/testthat/fixtures/csomag/R/libpath.R 19aaa7d4386a4ad5f957f13ac71eedf6 *tests/testthat/fixtures/script.R 18c609108fadd5808ee7df8ac918c714 *tests/testthat/fixtures/script2.R b4e448e8600fa63f41cc30e5e784f75c *tests/testthat/fixtures/simple.txt 18aae34da8888d68af2fb12c7c2066d1 *tests/testthat/helper.R 59a89f2db8a99102c0c0cc08c867440c *tests/testthat/test-archs.R 19133deedd59da5bc2f1a834239b41a1 *tests/testthat/test-bugs.R 197da4227a5456cd02cb7da8a4e40001 *tests/testthat/test-callback.R 5d6487c43a179d58877d62b9880fbeb5 *tests/testthat/test-clean-subprocess.R 1a387a305233be0a08c0ca40401fc0b6 *tests/testthat/test-error.R 9bfbf9bb465496ac660913040225bfb1 *tests/testthat/test-eval.R e5726cb1a08ae6e1f602b3002168f41c *tests/testthat/test-function-env.R 441d35f480f3d387ba961a151ba013db *tests/testthat/test-libpath.R c6352f50cbdd7242a9524e759ea02003 *tests/testthat/test-messages.R 29b792e646d8d54d5d041e4d4ca23b1f *tests/testthat/test-options.R 800ce9ab678d824030049d71611baa07 *tests/testthat/test-presets.R 6965462e84301519ac3c87bc266df793 *tests/testthat/test-quit.R 392f06124ab74a400735a6c33a7ab7ce *tests/testthat/test-r-bg.R 9523280f938b491366dae48ffa63455c *tests/testthat/test-r-process.R c5b42ea0959baff45b0bc243c789cb0e *tests/testthat/test-r-session-messages.R 4ddc00c4e618e7cec3c4944f7907f79e *tests/testthat/test-r-session.R 6ee62f3bad58f00199abbe84d5bb7b96 *tests/testthat/test-rcmd-bg.R 93d8c399ae5dde1e886c2a9dd83166a2 *tests/testthat/test-rcmd-process.R acd77f5b077d60f94ca1d222d1120ebc *tests/testthat/test-rcmd.R aed26b0afa851bc63e2b49d519808c31 *tests/testthat/test-rscript.R a0277136ac317355a2bd61d3a1e86056 *tests/testthat/test-spelling.R ea6ca0aafbc98da55e00772add24ce55 *tests/testthat/test-timeout.R 35e5520c782d5a119d0b70f462417f31 *tests/testthat/test-utils.R callr/inst/0000755000176200001440000000000014037545573012341 5ustar liggesuserscallr/inst/developer-notes.md0000644000176200001440000000372213612550263015770 0ustar liggesusers ## Design of the internals There are a lot of possible ways to implement this package, to minimize duplication. This is the API we want: ```r r(fun, args, ...) r_safe(fun, args, ...) r_bg(fun, args, ...) r_bg_safe(fun, args, ...) rcmd(cmd, cmdargs, ...) rcmd_safe(cmd, cmdargs, ...) rcmd_bg(cmd, cmdargs, ...) rcmd_bg_safe(cmd, cmdargs ...) ``` The `_safe` versions are easy to deal with, they just call the non-`_safe` versions with different arguments. For the other versions, this is what they need to do: ### `r` 1. convert / check arguments 2. save function to a file 3. create script file 4. set up profiles 5. set up library path 6. set up LIB and PROFILE env vars 7. set up callbacks (combine show and callbacks) 8. run the R process 9. write stdout & stderr to file, if needed 10. fail on status, if requested 11. get the result ### `r_bg` 1. convert / check arguments 2. save function to a file 3. create script file 4. set up profiles 5. set up library path 7. set up LIB and PROFILE env vars 8. start the R process in the bg ### `rcmd` 1. convert / check arguments 2. get the R binary and its arguments 3. set specified wd 4. set up profiles 5. set up library path 6. set up callbacks (combine show and callbacks) 7. set up LIB and PROFILE env vars 8. run the R process 9. write stdout & stderr to file, if needed 10. fail on status, if requested ### `rcmd_bg` 1. convert/check arguments 2. get the R binary and its arguments 3. set specified wd 4. set up profiles 5. set up library path 7. set up LIB and PROFILE env vars 8. run the R process in the bg ### Building blocks #### Always run, `check_my_args`: 1. convert / check arguments #### Run by `r` and `r_bg`, `setup_script_files`: 1. save function to a file 2. create script file #### Always run, `setup_context`: This is run with `.` as the working directory for `r` and `r_bg`. 1. set specified wd 2. set up profiles 3. set up library path 4. set up LIB and PROFILE env vars callr/inst/WORDLIST0000644000176200001440000000025014026377530013522 0ustar liggesusersCMD Eval Finalizer ORCID REPL cli cliapp cloneable funder igraph interruptible keyring lifecycle macOS packrat pkgdown processx renv subcommand subprocess subprocesses