callr/0000755000176200001440000000000014564733763011371 5ustar liggesuserscallr/NAMESPACE0000644000176200001440000000124314520207124012563 0ustar liggesusers# Generated by roxygen2: do not edit by hand S3method(format,callr_status_error) S3method(print,callr_status_error) export(add_hook) 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/LICENSE0000644000176200001440000000006114564626131012361 0ustar liggesusersYEAR: 2024 COPYRIGHT HOLDER: see COPYRIGHTS file callr/README.md0000644000176200001440000002533414521175632012643 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-CMD-check](https://github.com/r-lib/callr/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/r-lib/callr/actions/workflows/R-CMD-check.yaml) [![](https://www.r-pkg.org/badges/version/callr)](https://www.r-pkg.org/pkg/callr) [![CRAN Posit mirror downloads](https://cranlogs.r-pkg.org/badges/callr)](https://www.r-pkg.org/pkg/callr) [![Codecov test coverage](https://codecov.io/gh/r-lib/callr/branch/main/graph/badge.svg)](https://app.codecov.io/gh/r-lib/callr?branch=main) 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) - [Code of Conduct](#code-of-conduct) ## 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") ``` Install the development version from GitHub: ``` r pak::pak("r-lib/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 callr::r(function() var(iris[, 1:4])) ``` ### 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 callr::r(function() summary(mycars)) ``` But this does: ``` r mycars <- cars callr::r(function(x) summary(x), args = list(mycars)) ``` 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 callr::r(function() { g <- igraph::sample_gnp(1000, 4/1000); igraph::diameter(g) }) ``` ### Error handling callr copies errors from the child process back to the main R session: ``` r callr::r(function() 1 + "A") ``` 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 ``` 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: 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 <- callr::r(function() { print("hello world!"); message("hello again!") }, stdout = "/tmp/out", stderr = "/tmp/err" ) readLines("/tmp/out") ``` ``` r readLines("/tmp/err") ``` 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 <- callr::r_bg(function() Sys.sleep(.2)) rp ``` This is a list of all `r_process` methods: ``` r ls(rp) ``` 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 <- callr::r_bg(function() { Sys.sleep(1/2); "1 done" }) rp2 <- callr::r_bg(function() { Sys.sleep(1/1000); "2 done" }) processx::poll(list(rp1, rp2), 1000) ``` ``` r rp2$get_result() ``` ``` r processx::poll(list(rp1), 1000) ``` ``` r rp1$get_result() ``` ## Persistent R sessions `r_session` is another `processx::process` subclass that represents a persistent background R session: ``` r rs <- callr::r_session$new() rs ``` `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 <- callr::r_session$new() rs$run(function() runif(10)) ``` ``` r rs$call(function() rnorm(10)) rs ``` ``` r rs$poll_process(2000) ``` ``` r rs$read() ``` ## 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 callr::rcmd("config", "CC") ``` This returns a list with three components: the standard output, the standard error, and the exit (status) code of the `R CMD` command. ## Code of Conduct Please note that the callr project is released with a [Contributor Code of Conduct](https://callr.r-lib.org/CODE_OF_CONDUCT.html). By contributing to this project, you agree to abide by its terms. callr/man/0000755000176200001440000000000014564626344012140 5ustar liggesuserscallr/man/r_vanilla.Rd0000644000176200001440000000651614326533425014377 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 \if{html}{\out{
}}\preformatted{r(.libPaths) }\if{html}{\out{
}} does not work, because \code{.libPaths} is defined in a special environment, but \if{html}{\out{
}}\preformatted{r(function() .libPaths()) }\if{html}{\out{
}} 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.Rd0000644000176200001440000000476014326534733015604 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{ Note that on callr version 3.8.0 and above, you need to set the \code{callr.traceback} option to \code{TRUE} (in the main process) to make the subprocess dump the frames on error. This is because saving the frames can be costly for large objects passed as arguments. \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): \if{html}{\out{
}}\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) > # # ---------------------------------------------------------------------- }\if{html}{\out{
}} } callr/man/rcmd_process.Rd0000644000176200001440000003210714326534733015111 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-rcmd_process-new}{\code{rcmd_process$new()}} \item \href{#method-rcmd_process-finalize}{\code{rcmd_process$finalize()}} \item \href{#method-rcmd_process-clone}{\code{rcmd_process$clone()}} } } \if{html}{\out{
Inherited methods
}} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-rcmd_process-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-rcmd_process-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-rcmd_process-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.Rd0000644000176200001440000000401614326534733016203 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 experts. Note that this option is not used for the startup phase that currently always runs with \code{stdout = "|"}. \item \code{stderr}: Similar to \code{stdout}, but for the standard error. Like \code{stdout}, it is not used for the startup phase, which runs with \code{stderr = "|"}. \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/callr-package.Rd0000644000176200001440000006127014564553726015126 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/callr-package.R \docType{package} \name{callr-package} \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. } \section{callr}{ \subsection{Features}{ \itemize{ \item Calls an R function, with arguments, in a subprocess. \item Copies function arguments to the subprocess and copies the return value of the function back, seamlessly. \item Copies error objects back from the subprocess, including a stack trace. \item Shows and/or collects the standard output and standard error of the subprocess. \item Supports both one-off and persistent R subprocesses. \item Calls the function synchronously or asynchronously (in the background). \item Can call \verb{R CMD} commands, synchronously or asynchronously. \item Can call R scripts, synchronously or asynchronously. \item Provides extensible \code{r_process}, \code{rcmd_process} and \code{rscript_process} R6 classes, based on \code{processx::process}. } } \subsection{Installation}{ Install the stable version from CRAN: \if{html}{\out{
}}\preformatted{install.packages("callr") }\if{html}{\out{
}} Install the development version from GitHub: \if{html}{\out{
}}\preformatted{pak::pak("r-lib/callr") }\if{html}{\out{
}} } \subsection{Synchronous, one-off R processes}{ Use \code{r()} to run an R function in a new R process. The results are passed back seamlessly: \if{html}{\out{
}}\preformatted{callr::r(function() var(iris[, 1:4])) }\if{html}{\out{
}}\if{html}{\out{
#>              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                  
}} \subsection{Passing arguments}{ You can pass arguments to the function by setting \code{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: \if{html}{\out{
}}\preformatted{mycars <- cars callr::r(function() summary(mycars)) }\if{html}{\out{
}}\if{html}{\out{
#> Error:                                                                          
#> ! in callr subprocess.                                                          
#> Caused by error in `summary(mycars)`:                                           
#> ! object 'mycars' not found                                                     
#> Type .Last.error to see the more details.                                       
}} But this does: \if{html}{\out{
}}\preformatted{mycars <- cars callr::r(function(x) summary(x), args = list(mycars)) }\if{html}{\out{
}}\if{html}{\out{
#>      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. } \subsection{Using packages}{ You can use any R package in the child process, just make sure to refer to it explicitly with the \code{::} operator. For example, the following code creates an \href{https://github.com/igraph/rigraph}{igraph} graph in the child, and calculates some metrics of it. \if{html}{\out{
}}\preformatted{callr::r(function() \{ g <- igraph::sample_gnp(1000, 4/1000); igraph::diameter(g) \}) }\if{html}{\out{
}}\if{html}{\out{
#> [1] 11                                                                          
}} } \subsection{Error handling}{ callr copies errors from the child process back to the main R session: \if{html}{\out{
}}\preformatted{callr::r(function() 1 + "A") }\if{html}{\out{
}}\if{html}{\out{
#> Error:                                                                          
#> ! in callr subprocess.                                                          
#> Caused by error in `1 + "A"`:                                                   
#> ! non-numeric argument to binary operator                                       
#> Type .Last.error to see the more details.                                       
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. }} \if{html}{\out{
}}\preformatted{.Last.error }\if{html}{\out{
}}\if{html}{\out{
#> Error:                                                                          
#> ! in callr subprocess.                                                          
#> Caused by error in `1 + "A"`:                                                   
#> ! non-numeric argument to binary operator                                       
#> ---                                                                             
#> Backtrace:                                                                      
#> 1. callr::r(function() 1 + "A")                                                 
#> 2. callr:::get_result(output = out, options)                                    
#> 3. callr:::throw(callr_remote_error(remerr, output), parent = fix_msg(remerr[[3]
#> ]))                                                                             
#> ---                                                                             
#> Subprocess backtrace:                                                           
#> 1. base::.handleSimpleError(function (e) …                                      
#> 2. global h(simpleError(msg, call))                                             
}} The error objects has two parts. The first belongs to the main process, and the second belongs to the subprocess. \code{.Last.error} also includes a stack trace, that includes both the main R process and the subprocess: 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. } \subsection{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: \if{html}{\out{
}}\preformatted{x <- callr::r(function() \{ print("hello world!"); message("hello again!") \}, stdout = "/tmp/out", stderr = "/tmp/err" ) readLines("/tmp/out") }\if{html}{\out{
}}\if{html}{\out{
#> [1] "[1] \\"hello world!\\""                                                      
}} \if{html}{\out{
}}\preformatted{readLines("/tmp/err") }\if{html}{\out{
}}\if{html}{\out{
#> [1] "hello again!"                                                              
}} With the \code{stdout} option, the standard output is collected and can be examined once the child process finished. The \code{show = TRUE} options will also show the output of the child, as it is printed, on the console of the parent. } } \subsection{Background R processes}{ \code{r_bg()} is similar to \code{r()} but it starts the R process in the background. It returns an \code{r_process} R6 object, that provides a rich API: \if{html}{\out{
}}\preformatted{rp <- callr::r_bg(function() Sys.sleep(.2)) rp }\if{html}{\out{
}}\if{html}{\out{
#> PROCESS 'R', running, pid 58242.                                                
}} This is a list of all \code{r_process} methods: \if{html}{\out{
}}\preformatted{ls(rp) }\if{html}{\out{
}}\if{html}{\out{
#>  [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 \code{processx::process} superclass and the new \code{get_result()} method, to retrieve the R object returned by the function call. Some of the handiest methods are: \itemize{ \item \code{get_exit_status()} to query the exit status of a finished process. \item \code{get_result()} to collect the return value of the R function call. \item \code{interrupt()} to send an interrupt to the process. This is equivalent to a \code{CTRL+C} key press, and the R process might ignore it. \item \code{is_alive()} to check if the process is alive. \item \code{kill()} to terminate the process. \item \code{poll_io()} to wait for any standard output, standard error, or the completion of the process, with a timeout. \item \verb{read_*()} to read the standard output or error. \item \code{suspend()} and \code{resume()} to stop and continue a process. \item \code{wait()} to wait for the completion of the process, with a timeout. } } \subsection{Multiple background R processes and \code{poll()}}{ Multiple background R processes are best managed with the \code{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. \if{html}{\out{
}}\preformatted{rp1 <- callr::r_bg(function() \{ Sys.sleep(1/2); "1 done" \}) rp2 <- callr::r_bg(function() \{ Sys.sleep(1/1000); "2 done" \}) processx::poll(list(rp1, rp2), 1000) }\if{html}{\out{
}}\if{html}{\out{
#> [[1]]                                                                           
#>   output    error  process                                                      
#> "silent" "silent" "silent"                                                      
#>                                                                                 
#> [[2]]                                                                           
#>  output   error process                                                         
#> "ready" "ready" "ready"                                                         
#>                                                                                 
}} \if{html}{\out{
}}\preformatted{rp2$get_result() }\if{html}{\out{
}}\if{html}{\out{
#> [1] "2 done"                                                                    
}} \if{html}{\out{
}}\preformatted{processx::poll(list(rp1), 1000) }\if{html}{\out{
}}\if{html}{\out{
#> [[1]]                                                                           
#>   output    error  process                                                      
#> "silent" "silent"  "ready"                                                      
#>                                                                                 
}} \if{html}{\out{
}}\preformatted{rp1$get_result() }\if{html}{\out{
}}\if{html}{\out{
#> [1] "1 done"                                                                    
}} } \subsection{Persistent R sessions}{ \code{r_session} is another \code{processx::process} subclass that represents a persistent background R session: \if{html}{\out{
}}\preformatted{rs <- callr::r_session$new() rs }\if{html}{\out{
}}\if{html}{\out{
#> R SESSION, alive, idle, pid 58288.                                              
}} \code{r_session$run()} is a synchronous call, that works similarly to \code{r()}, but uses the persistent session. \code{r_session$call()} starts the function call and returns immediately. The \code{r_session$poll_process()} method or \code{processx::poll()} can then be used to wait for the completion or other events from one or more R sessions, R processes or other \code{processx::process} objects. Once an R session is done with an asynchronous computation, its \code{poll_process()} method returns \code{"ready"} and the \code{r_session$read()} method can read out the result. \if{html}{\out{
}}\preformatted{rs <- callr::r_session$new() rs$run(function() runif(10)) }\if{html}{\out{
}}\if{html}{\out{
#>  [1] 0.8047354 0.8938617 0.7142338 0.8505395 0.3118376 0.7083882 0.9514367      
#>  [8] 0.2536755 0.6727270 0.3359578                                              
}} \if{html}{\out{
}}\preformatted{rs$call(function() rnorm(10)) rs }\if{html}{\out{
}}\if{html}{\out{
#> R SESSION, alive, busy, pid 58294.                                              
}} \if{html}{\out{
}}\preformatted{rs$poll_process(2000) }\if{html}{\out{
}}\if{html}{\out{
#> [1] "ready"                                                                     
}} \if{html}{\out{
}}\preformatted{rs$read() }\if{html}{\out{
}}\if{html}{\out{
#> $code                                                                           
#> [1] 200                                                                         
#>                                                                                 
#> $message                                                                        
#> [1] "done callr-rs-result-e3324ebebc8b"                                         
#>                                                                                 
#> $result                                                                         
#>  [1] -0.60962697 -0.41063130  0.22121432  1.44621900  0.26890394  0.11432756    
#>  [7] -0.53206118  0.47493933  0.02069551  1.37348004                            
#>                                                                                 
#> $stdout                                                                         
#> [1] ""                                                                          
#>                                                                                 
#> $stderr                                                                         
#> [1] ""                                                                          
#>                                                                                 
#> $error                                                                          
#> NULL                                                                            
#>                                                                                 
#> attr(,"class")                                                                  
#> [1] "callr_session_result"                                                      
}} } \subsection{Running \verb{R CMD} commands}{ The \code{rcmd()} function calls an \verb{R CMD} command. For example, you can call \verb{R CMD INSTALL}, \verb{R CMD check} or \verb{R CMD config} this way: \if{html}{\out{
}}\preformatted{callr::rcmd("config", "CC") }\if{html}{\out{
}}\if{html}{\out{
#> $status                                                                         
#> [1] 0                                                                           
#>                                                                                 
#> $stdout                                                                         
#> [1] "clang -arch arm64\\n"                                                       
#>                                                                                 
#> $stderr                                                                         
#> [1] ""                                                                          
#>                                                                                 
#> $timeout                                                                        
#> [1] FALSE                                                                       
#>                                                                                 
#> $command                                                                        
#> [1] "/Library/Frameworks/R.framework/Versions/4.2-arm64/Resources/bin/R"        
#> [2] "CMD"                                                                       
#> [3] "config"                                                                    
#> [4] "CC"                                                                        
#>                                                                                 
}} This returns a list with three components: the standard output, the standard error, and the exit (status) code of the \verb{R CMD} command. } \subsection{Code of Conduct}{ Please note that the callr project is released with a \href{https://callr.r-lib.org/CODE_OF_CONDUCT.html}{Contributor Code of Conduct}. By contributing to this project, you agree to abide by its terms. } } \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 Posit Software, PBC [copyright holder, funder] \item Mango Solutions [copyright holder, funder] } } \keyword{internal} callr/man/add_hook.Rd0000644000176200001440000000133614326534733014176 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/hook.R \name{add_hook} \alias{add_hook} \title{Add a user hook to be executed before launching an R subprocess} \usage{ add_hook(...) } \arguments{ \item{...}{Named argument specifying a hook function to add, or \code{NULL} to delete the named hook.} } \value{ \code{add_hook} is called for its side-effects. } \description{ This function allows users of \code{callr} to specify functions that get invoked whenever an R session is launched. The function can modify the environment variables and command line arguments. } \details{ The prototype of the hook function is \verb{function (options)}, and it is expected to return the modified \code{options}. } callr/man/r_process_options.Rd0000644000176200001440000000147314143453135016173 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.Rd0000644000176200001440000000107214143453135015250 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.Rd0000644000176200001440000002342614330454562012667 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 \if{html}{\out{
}}\preformatted{r(.libPaths) }\if{html}{\out{
}} does not work, because \code{.libPaths} is defined in a special environment, but \if{html}{\out{
}}\preformatted{r(function() .libPaths()) }\if{html}{\out{
}} 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). IF \code{NULL} (the default), then standard output is not returned, but it is recorded and included in the error object if an error happens.} \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. IF \code{NULL} (the default), then standard output is not returned, but it is recorded and included in the error object if an error happens.} \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. } \section{Transporting objects}{ \code{func} and \code{args} are copied to the child process by first serializing them into a temporary file using \code{\link[=saveRDS]{saveRDS()}} and then loading them back into the child session using \code{\link[=readRDS]{readRDS()}}. The same strategy is used to copy the result of calling \code{func(args)} to the main session. Note that some objects, notably those with \code{externalptr} type, won't work as expected after being saved to a file and loaded back. For performance reasons \code{compress=FALSE} is used when serializing with \code{\link[=saveRDS]{saveRDS()}}, this can be disabled by setting \code{options(callr.compress_transport = TRUE)}. } \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.Rd0000644000176200001440000003212014564553726015654 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-rscript_process-new}{\code{rscript_process$new()}} \item \href{#method-rscript_process-finalize}{\code{rscript_process$finalize()}} \item \href{#method-rscript_process-clone}{\code{rscript_process$clone()}} } } \if{html}{\out{
Inherited methods
}} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-rscript_process-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-rscript_process-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-rscript_process-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.Rd0000644000176200001440000001331414564551363013340 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 \if{html}{\out{
}}\preformatted{r(.libPaths) }\if{html}{\out{
}} does not work, because \code{.libPaths} is defined in a special environment, but \if{html}{\out{
}}\preformatted{r(function() .libPaths()) }\if{html}{\out{
}} 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). IF \code{NULL} (the default), then standard output is not returned, but it is recorded and included in the error object if an error happens.} \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. IF \code{NULL} (the default), then standard output is not returned, but it is recorded and included in the error object if an error happens.} \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. Use \code{p$get_result()} to collect the result or to throw an error if the background computation failed. } \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.Rd0000644000176200001440000000375014143453135015070 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.Rd0000644000176200001440000000621314143453135014013 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_crash_error.Rd0000644000176200001440000000121214330431133016737 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/error.R \name{new_callr_crash_error} \alias{new_callr_crash_error} \title{Create an error object} \usage{ new_callr_crash_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.Rd0000644000176200001440000000165614143453135016662 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/roxygen/0000755000176200001440000000000014326534733013627 5ustar liggesuserscallr/man/roxygen/meta.R0000644000176200001440000000111514326534733014676 0ustar liggesusersif (exists(".knitr_asciicast_process", envir = .GlobalEnv)) { rm(list = ".knitr_asciicast_process", envir = .GlobalEnv) } asciicast::init_knitr_engine( echo = TRUE, echo_input = FALSE, timeout = as.integer(Sys.getenv("ASCIICAST_TIMEOUT", 10)), startup = quote(options(cli.num_colors = 256)) ) knitr::opts_chunk$set( asciicast_knitr_output = "html", asciicast_include_style = FALSE, cache = TRUE, cache.path = file.path(getwd(), "man/_cache/"), fig.path = file.path(getwd(), "man/figures"), error = TRUE ) list( markdown = TRUE, restrict_image_formats = TRUE ) callr/man/r_copycat.Rd0000644000176200001440000000626014326533425014407 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 \if{html}{\out{
}}\preformatted{r(.libPaths) }\if{html}{\out{
}} does not work, because \code{.libPaths} is defined in a special environment, but \if{html}{\out{
}}\preformatted{r(function() .libPaths()) }\if{html}{\out{
}} 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.Rd0000644000176200001440000000152614143453135017600 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.Rd0000644000176200001440000000154014143453135017413 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.Rd0000644000176200001440000000146214564551567014613 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{output}{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.} \item{options}{The context, including all parameters.} } \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/figures/0000755000176200001440000000000014521175632013574 5ustar liggesuserscallr/man/figures/io-dark.svg0000644000176200001440000000234614521175632015650 0ustar liggesusers[1]"[1]\"helloworld!\""callr/man/figures/bg-methods.svg0000644000176200001440000001535314521175632016355 0ustar liggesusers[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"callr/man/figures/error2-2.svg0000644000176200001440000001645114521175632015676 0ustar liggesusers---Error:!incallrsubprocess.Causedbyerrorin`1+"A"`:!non-numericargumenttobinaryoperatorBacktrace:1.callr::r(function()1+"A")2.callr:::get_result(output=out,options)ateval.R:193:33.callr:::throw(callr_remote_error(remerr,output),parent=fix_msg(remerr[[3]]))atresult.R:87:5Subprocessbacktrace:1.base::.handleSimpleError(function(e)2.globalh(simpleError(msg,call))callr/man/figures/error1.svg0000644000176200001440000000564514521175632015541 0ustar liggesusersError:!incallrsubprocess.Causedbyerrorin`1+"A"`:!non-numericargumenttobinaryoperatorType.Last.errortoseethemoredetails.callr/man/figures/simple-dark.svg0000644000176200001440000000531614521175632016532 0ustar liggesusersSepal.LengthSepal.WidthPetal.LengthPetal.WidthSepal.Length0.6856935-0.04243401.27431540.5162707Sepal.Width-0.04243400.1899794-0.3296564-0.1216394Petal.Length1.2743154-0.32965643.11627791.2956094Petal.Width0.5162707-0.12163941.29560940.5810063callr/man/figures/bg-dark.svg0000644000176200001440000000243614521175632015631 0ustar liggesusersPROCESS'R',running,pid98048.callr/man/figures/passargsfail-dark.svg0000644000176200001440000000533614521175632017722 0ustar liggesusersError:!incallrsubprocess.Causedbyerrorin`summary(mycars)`:!object'mycars'notfoundType.Last.errortoseethemoredetails.callr/man/figures/rsession2-2.svg0000644000176200001440000000255414521175632016411 0ustar liggesusersRSESSION,alive,busy,pid98090.callr/man/figures/bg-methods-dark.svg0000644000176200001440000001535014521175632017271 0ustar liggesusers[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"callr/man/figures/rsession-5.svg0000644000176200001440000000634314521175632016332 0ustar liggesusers[1]""$code[1]200$message[1]"donecallr-rs-result-17eb55fb39cd4"$result[1]-0.043703461.18261173-0.329021950.144230131.935970941.19500910[7]0.238762750.85456492-1.740516321.42462027$stdout$stderr$errorNULLattr(,"class")[1]"callr_session_result"callr/man/figures/io-2.svg0000644000176200001440000000223514521175632015065 0ustar liggesusers[1]"helloagain!"callr/man/figures/rcmd-dark.svg0000644000176200001440000000513014521175632016160 0ustar liggesusers$status[1]0$stdout[1]"clang-archarm64\n"$stderr[1]""$timeout[1]FALSE$command[1]"/Library/Frameworks/R.framework/Versions/4.2-arm64/Resources/bin/R"[2]"CMD"[3]"config"[4]"CC"callr/man/figures/poll.svg0000644000176200001440000000376014521175632015271 0ustar liggesusers[[1]]outputerrorprocess"silent""silent""silent"[[2]]outputerrorprocess"ready""ready""ready"callr/man/figures/error1-dark.svg0000644000176200001440000000564514521175632016460 0ustar liggesusersError:!incallrsubprocess.Causedbyerrorin`1+"A"`:!non-numericargumenttobinaryoperatorType.Last.errortoseethemoredetails.callr/man/figures/passargsfail.svg0000644000176200001440000000533614521175632017003 0ustar liggesusersError:!incallrsubprocess.Causedbyerrorin`summary(mycars)`:!object'mycars'notfoundType.Last.errortoseethemoredetails.callr/man/figures/rsession-5-dark.svg0000644000176200001440000000634014521175632017246 0ustar liggesusers[1]""$code[1]200$message[1]"donecallr-rs-result-17eb55fb39cd4"$result[1]-0.043703461.18261173-0.329021950.144230131.935970941.19500910[7]0.238762750.85456492-1.740516321.42462027$stdout$stderr$errorNULLattr(,"class")[1]"callr_session_result"callr/man/figures/packages.svg0000644000176200001440000000210014521175632016064 0ustar liggesusers[1]12callr/man/figures/rsession-dark.svg0000644000176200001440000000255114521175632017104 0ustar liggesusersRSESSION,alive,idle,pid98086.callr/man/figures/poll-2-dark.svg0000644000176200001440000000222314521175632016340 0ustar liggesusers[1]"2done"callr/man/figures/bg.svg0000644000176200001440000000244114521175632014706 0ustar liggesusersPROCESS'R',running,pid98048.callr/man/figures/packages-dark.svg0000644000176200001440000000207514521175632017016 0ustar liggesusers[1]12callr/man/figures/rsession-4-dark.svg0000644000176200001440000000211414521175632017240 0ustar liggesusers[1]"ready"callr/man/figures/rsession-4.svg0000644000176200001440000000211714521175632016324 0ustar liggesusers[1]"ready"callr/man/figures/poll-3-dark.svg0000644000176200001440000000272314521175632016346 0ustar liggesusers[[1]]outputerrorprocess"ready""ready""ready"callr/man/figures/rcmd.svg0000644000176200001440000000513314521175632015244 0ustar liggesusers$status[1]0$stdout[1]"clang-archarm64\n"$stderr[1]""$timeout[1]FALSE$command[1]"/Library/Frameworks/R.framework/Versions/4.2-arm64/Resources/bin/R"[2]"CMD"[3]"config"[4]"CC"callr/man/figures/poll-3.svg0000644000176200001440000000272614521175632015432 0ustar liggesusers[[1]]outputerrorprocess"ready""ready""ready"callr/man/figures/io.svg0000644000176200001440000000235114521175632014725 0ustar liggesusers[1]"[1]\"helloworld!\""callr/man/figures/simple.svg0000644000176200001440000000532114521175632015607 0ustar liggesusersSepal.LengthSepal.WidthPetal.LengthPetal.WidthSepal.Length0.6856935-0.04243401.27431540.5162707Sepal.Width-0.04243400.1899794-0.3296564-0.1216394Petal.Length1.2743154-0.32965643.11627791.2956094Petal.Width0.5162707-0.12163941.29560940.5810063callr/man/figures/poll-4.svg0000644000176200001440000000222614521175632015426 0ustar liggesusers[1]"1done"callr/man/figures/passargsok.svg0000644000176200001440000000611414521175632016474 0ustar liggesusersspeeddistMin.:4.0Min.:2.001stQu.:12.01stQu.:26.00Median:15.0Median:36.00Mean:15.4Mean:42.983rdQu.:19.03rdQu.:56.00Max.:25.0Max.:120.00callr/man/figures/io-2-dark.svg0000644000176200001440000000223214521175632016001 0ustar liggesusers[1]"helloagain!"callr/man/figures/error2-2-dark.svg0000644000176200001440000001645114521175632016615 0ustar liggesusers---Error:!incallrsubprocess.Causedbyerrorin`1+"A"`:!non-numericargumenttobinaryoperatorBacktrace:1.callr::r(function()1+"A")2.callr:::get_result(output=out,options)ateval.R:193:33.callr:::throw(callr_remote_error(remerr,output),parent=fix_msg(remerr[[3]]))atresult.R:87:5Subprocessbacktrace:1.base::.handleSimpleError(function(e)2.globalh(simpleError(msg,call))callr/man/figures/rsession2-2-dark.svg0000644000176200001440000000255114521175632017325 0ustar liggesusersRSESSION,alive,busy,pid98090.callr/man/figures/rsession2-dark.svg0000644000176200001440000000353514521175632017171 0ustar liggesusers[1]0.93875370.43054120.12512460.98262260.40328680.81394150.9970173[8]0.87883710.29427240.9011616callr/man/figures/poll-2.svg0000644000176200001440000000222614521175632015424 0ustar liggesusers[1]"2done"callr/man/figures/poll-dark.svg0000644000176200001440000000375514521175632016214 0ustar liggesusers[[1]]outputerrorprocess"silent""silent""silent"[[2]]outputerrorprocess"ready""ready""ready"callr/man/figures/rsession.svg0000644000176200001440000000255414521175632016170 0ustar liggesusersRSESSION,alive,idle,pid98086.callr/man/figures/poll-4-dark.svg0000644000176200001440000000222314521175632016342 0ustar liggesusers[1]"1done"callr/man/figures/passargsok-dark.svg0000644000176200001440000000611114521175632017410 0ustar liggesusersspeeddistMin.:4.0Min.:2.001stQu.:12.01stQu.:26.00Median:15.0Median:36.00Mean:15.4Mean:42.983rdQu.:19.03rdQu.:56.00Max.:25.0Max.:120.00callr/man/figures/rsession2.svg0000644000176200001440000000354014521175632016246 0ustar liggesusers[1]0.93875370.43054120.12512460.98262260.40328680.81394150.9970173[8]0.87883710.29427240.9011616callr/man/rscript.Rd0000644000176200001440000001016714143453135014107 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.Rd0000644000176200001440000000075514143453135014456 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.Rd0000644000176200001440000006170314564553726014445 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-r_session-new}{\code{r_session$new()}} \item \href{#method-r_session-run}{\code{r_session$run()}} \item \href{#method-r_session-run_with_output}{\code{r_session$run_with_output()}} \item \href{#method-r_session-call}{\code{r_session$call()}} \item \href{#method-r_session-poll_process}{\code{r_session$poll_process()}} \item \href{#method-r_session-get_state}{\code{r_session$get_state()}} \item \href{#method-r_session-get_running_time}{\code{r_session$get_running_time()}} \item \href{#method-r_session-read}{\code{r_session$read()}} \item \href{#method-r_session-close}{\code{r_session$close()}} \item \href{#method-r_session-traceback}{\code{r_session$traceback()}} \item \href{#method-r_session-debug}{\code{r_session$debug()}} \item \href{#method-r_session-attach}{\code{r_session$attach()}} \item \href{#method-r_session-finalize}{\code{r_session$finalize()}} \item \href{#method-r_session-print}{\code{r_session$print()}} \item \href{#method-r_session-clone}{\code{r_session$clone()}} } } \if{html}{\out{
Inherited methods
}} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-r_session-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-r_session-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-r_session-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-r_session-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-r_session-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-r_session-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-r_session-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-r_session-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-r_session-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-r_session-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. On callr version 3.8.0 and above, you need to set the \code{callr.traceback} option to \code{TRUE} (in the main process) to make the subprocess save the trace on error. This is because saving the trace can be costly for large objects passed as arguments. \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-r_session-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}. On callr version 3.8.0 and above, you need to set the \code{callr.traceback} option to \code{TRUE} (in the main process) to make the subprocess dump frames on error. This is because saving the frames can be costly for large objects passed as arguments. \subsection{Usage}{ \if{html}{\out{
}}\preformatted{r_session$debug()}\if{html}{\out{
}} } } \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-r_session-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-r_session-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-r_session-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-r_session-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.Rd0000644000176200001440000000300614326530060015202 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.Rd0000644000176200001440000000102314326530060015611 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.Rd0000644000176200001440000003354214326534733014431 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-r_process-new}{\code{r_process$new()}} \item \href{#method-r_process-get_result}{\code{r_process$get_result()}} \item \href{#method-r_process-finalize}{\code{r_process$finalize()}} \item \href{#method-r_process-clone}{\code{r_process$clone()}} } } \if{html}{\out{
Inherited methods
}} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-r_process-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-r_process-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-r_process-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-r_process-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.Rd0000644000176200001440000001267114326527471013360 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/DESCRIPTION0000644000176200001440000000262614564733763013105 0ustar liggesusersPackage: callr Title: Call R from R Version: 3.7.5 Authors@R: c( person("Gábor", "Csárdi", , "csardi.gabor@gmail.com", role = c("aut", "cre", "cph"), comment = c(ORCID = "0000-0001-7098-9676")), person("Winston", "Chang", role = "aut"), person("Posit Software, PBC", role = c("cph", "fnd")), person("Ascent Digital Services", 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 BugReports: https://github.com/r-lib/callr/issues Depends: R (>= 3.4) Imports: processx (>= 3.6.1), R6, utils Suggests: asciicast (>= 2.3.1), cli (>= 1.1.0), covr, mockery, ps, rprojroot, spelling, testthat (>= 3.2.0), withr (>= 2.3.0) Config/Needs/website: r-lib/asciicast, glue, htmlwidgets, igraph, tibble, tidyverse/tidytemplate Config/testthat/edition: 3 Encoding: UTF-8 Language: en-US RoxygenNote: 7.2.3 NeedsCompilation: no Packaged: 2024-02-19 10:26:44 UTC; gaborcsardi Author: Gábor Csárdi [aut, cre, cph] (), Winston Chang [aut], Posit Software, PBC [cph, fnd], Ascent Digital Services [cph, fnd] Maintainer: Gábor Csárdi Repository: CRAN Date/Publication: 2024-02-19 20:20:03 UTC callr/tests/0000755000176200001440000000000014326532177012523 5ustar liggesuserscallr/tests/testthat/0000755000176200001440000000000014564733762014372 5ustar liggesuserscallr/tests/testthat/test-rcmd-bg.R0000644000176200001440000000116614326532177017002 0ustar liggesusers 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.R0000644000176200001440000000504214521175632016405 0ustar liggesusers 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", { withr::local_options(width = 500) 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_null(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.R0000644000176200001440000000151214326532177017147 0ustar liggesusers 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/0000755000176200001440000000000014143453135016225 5ustar liggesuserscallr/tests/testthat/fixtures/script.R0000644000176200001440000000004314143453135017651 0ustar liggesusers cat("stdout\n") message("stderr") callr/tests/testthat/fixtures/D10000644000176200001440000000102214143453135016407 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/0000755000176200001440000000000014143453135017476 5ustar liggesuserscallr/tests/testthat/fixtures/csomag/NAMESPACE0000644000176200001440000000000014143453135020703 0ustar liggesuserscallr/tests/testthat/fixtures/csomag/DESCRIPTION0000644000176200001440000000054114143453135021204 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/0000755000176200001440000000000014143453135017677 5ustar liggesuserscallr/tests/testthat/fixtures/csomag/R/libpath.R0000644000176200001440000000075514143453135021454 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.R0000644000176200001440000000011214143453135017730 0ustar liggesuserscat("out1") message("err1", appendLF = FALSE) cat("out2") message("err2") callr/tests/testthat/fixtures/simple.txt0000644000176200001440000000002114143453135020250 0ustar liggesuserssimple text file callr/tests/testthat/test-callback.R0000644000176200001440000000100714326532177017215 0ustar liggesusers 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.R0000644000176200001440000001401014326532177017102 0ustar liggesusers 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-hook.R0000644000176200001440000000125314326534733016424 0ustar liggesusers test_that("set options with hook", { add_hook( test_hook = function (options) { within(options, { env["TEST_HOOK"] <- "hello" cmdargs <- c(cmdargs, "extra_arg") }) } ) options <- r_process_options( func = function() { list(env = Sys.getenv("TEST_HOOK"), args = commandArgs()) } ) x <- r_process$new(options) x$wait() res <- x$get_result() expect_equal(res$env, "hello") expect_length(grep("^extra_arg$", res$args), 1L) # Remove hook add_hook(test_hook = NULL) x <- r_process$new(options) x$wait() res <- x$get_result() expect_equal(res$env, "") expect_length(grep("^extra_arg$", res$args), 0L) }) callr/tests/testthat/test-eval.R0000644000176200001440000001477614326534733016431 0ustar liggesusers 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("local .Rprofile is not loaded recursively", { tmp <- tempfile() on.exit(unlink(tmp, recursive = TRUE), add = TRUE) dir.create(tmp) wd <- getwd() on.exit(setwd(wd), add = TRUE) setwd(tmp) expr <- quote({ rprofile <- Sys.getenv("R_PROFILE_USER", "~/.Rprofile") if (file.exists(rprofile)) source(rprofile) rm(rprofile) aa <- 123 }) cat(deparse(expr), file = ".Rprofile", sep = "\n") out <- callr::r(function() aa) expect_equal(out, 123) }) 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.R0000644000176200001440000000311314326532177020731 0ustar liggesusers 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-package.R0000644000176200001440000000064614326534733017064 0ustar liggesusers test_that("all figures in man/figures are needed", { skip_on_cran() skip_on_covr() pkg_dir <- test_package_root() figs <- dir(file.path(pkg_dir, "man", "figures")) readme <- file.path(pkg_dir, "README.md") readme_figs <- grep("man/figures/", readLines(readme), value = TRUE) readme_figs <- sub("^.*man/figures/(.*[.]svg).*$", "\\1", readme_figs) expect_equal( sort(figs), sort(readme_figs) ) }) callr/tests/testthat/test-r-bg.R0000644000176200001440000000336614326532177016322 0ustar liggesusers 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.R0000644000176200001440000000031414326532177016421 0ustar liggesusers 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.R0000644000176200001440000000053714326532177017305 0ustar liggesusers 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.R0000644000176200001440000001315114330474701016606 0ustar liggesusers test_that("error is propagated, .Last.error is set", { expect_r_process_snapshot( callr::r(function() 1 + "A", error = "error"), .Last.error, transform = redact_srcref ) }) test_that("error is propagated, printed if non-interactive mode", { expect_r_process_snapshot( callr::r(function() 1 + "A", error = "error"), interactive = FALSE, transform = redact_srcref ) }) test_that("error stack is passed, .Last.error is set", { expect_r_process_snapshot( callr::r( function() { f <- function() g() g <- function() 1 + "A" f() }, error = "stack" ), .Last.error, transform = redact_srcref ) }) test_that("error behavior can be set using option", { withr::local_options(callr.error = "error") expect_snapshot( error = TRUE, r(function() 1 + "A") ) withr::local_options(callr.error = "stack") expect_snapshot( error = TRUE, r( function() { f <- function() g() g <- function() 1 + "A" f() } ) ) }) test_that("parent errors", { withr::local_options(list("callr.error" = "error")) expect_snapshot({ err <- tryCatch( r(function() 1 + "A"), error = function(e) e ) err$parent }) }) test_that("parent errors, another level", { withr::local_options(list("callr.error" = "error")) expect_snapshot({ err <- tryCatch( callr::r(function() { withr::local_options(list("callr.error" = "error")) callr::r(function() 1 + "A") }), error = function(e) e ) err$parent err$parent$parent }) }) test_that("error traces are printed recursively", { expect_r_process_snapshot( callr::r(function() callr::r(function() 1 + "a")), interactive = FALSE, transform = redact_srcref ) }) test_that("errors in r_bg() are merged", { withr::local_options(list("callr.error" = "error")) p <- r_bg(function() 1 + "A") on.exit(p$kill(), add = TRUE) p$wait(2000) expect_snapshot( error = TRUE, p$get_result() ) }) test_that("errors in r_process are merged", { 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) expect_snapshot( error = TRUE, p$get_result() ) }) test_that("errors in r_session$run() are merged", { rs <- r_session$new() on.exit(rs$kill(), add = TRUE) expect_snapshot( error = TRUE, rs$run(function() 1 + "A") ) expect_snapshot( error = TRUE, rs$run(function() 1 + "A") ) }) 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) expect_snapshot(rs$read()$error) rs$call(function() 1 + "A") rs$poll_process(2000) expect_snapshot(rs$read()$error) }) test_that("child error is not modified", { expect_snapshot({ err <- tryCatch(callr::r(function() stop("foobar")), error = function(e) e) err class(err) class(err$parent) }) }) test_that("new_callr_error, timeout", { expect_r_process_snapshot( callr::r(function() Sys.sleep(3), timeout = 1/5), transform = redact_srcref ) expect_snapshot( error = TRUE, callr::r(function() Sys.sleep(3), timeout = 1/5) ) }) test_that("interrupting an R session", { # Not a great test, because it is timing dependent, especially bad # on Windows, where it takes a bit longer to start running the command. skip_on_cran() rs <- r_session$new() on.exit(rs$close(), add = TRUE) rs$call(function() Sys.sleep(3)) # wait a bit so it starts running Sys.sleep(0.2) rs$interrupt() rs$poll_io(3000) expect_snapshot( rs$read(), transform = redact_callr_rs_result ) }) test_that("format.call_status_error", { err <- tryCatch( callr::r(function() 1 + ""), error = function(e) e ) expect_snapshot(format(err)) expect_snapshot(print(err)) err <- tryCatch( callr::r(function() 1 + "", error = "stack"), error = function(e) e ) expect_snapshot(format(err)) expect_snapshot(print(err)) }) test_that("format.call_status_error 2", { expect_r_process_snapshot( withr::local_options(rlib_error_always_trace = TRUE), err <- tryCatch( callr::r(function() 1 + ""), error = function(e) e ), writeLines(format(err, trace = TRUE)), interactive = FALSE, transform = redact_srcref ) }) test_that("stdout/stderr is printed on error", { expect_r_process_snapshot( callr::r(function() { warning("I have a bad feeling about this") stop("told ya") }), .Last.error, .Last.error$stderr, interactive = TRUE, transform = function(x) fix_eol(redact_srcref(x)) ) }) test_that("stdout/stderr is printed on error 2", { expect_r_process_snapshot( callr::r(function() { writeLines("Just some output") stop("told ya") }), .Last.error, .Last.error$stdout, interactive = TRUE, transform = function(x) fix_eol(redact_srcref(x)) ) }) test_that("stdout/stderr is printed on error 3", { expect_r_process_snapshot( callr::r(function() { writeLines("Just some output") warning("I have a bad feeling about this") stop("told ya") }), interactive = FALSE, transform = redact_srcref ) }) test_that("error is printed to file", { tmp <- tempfile("callr-test") on.exit(unlink(tmp), add = TRUE) err <- tryCatch( callr::r(function() stop("ouch"), stderr = tmp), error = function(e) e ) expect_snapshot( err$stderr, transform = function(x) fix_eol(redact_srcref(x)) ) expect_snapshot(readLines(tmp)) }) callr/tests/testthat/test-r-session.R0000644000176200001440000001765214521175632017414 0ustar liggesusers 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") ## While session is idle the current call running time is NA rs$call(function() 42) expect_equal( is.na(rs$get_running_time()), c(total = FALSE, current = FALSE) ) res <- read_next(rs) expect_equal( is.na(rs$get_running_time()), c(total = FALSE, current = TRUE) ) expect_s3_class(rs$get_running_time(), "difftime") ## 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, "callr_timeout_error") 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", { withr::local_options(callr.traceback = TRUE) 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.R0000644000176200001440000000426314326532177021217 0ustar liggesusers 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.R0000644000176200001440000000043314326532177017151 0ustar liggesusers 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.R0000644000176200001440000001254414521175632015767 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 } expect_r_process_snapshot <- function(..., interactive = TRUE, echo = TRUE, transform = NULL, variant = NULL) { # Skip these tests on platforms where V8 is not available if (! R.Version()$arch %in% c("i386", "x86_64", "aarch64") && ! requireNamespace("asciicast", quietly = TRUE)) { skip("No asciicast package") } # errors.R assumes non-interactive in testthat, but we don't want that withr::local_envvar(TESTTHAT = NA_character_) dots <- eval(substitute(alist(...))) nms <- names(dots) if (all(nms == "")) { code_pos <- rep(TRUE, length(dots)) } else { code_pos <- nms == "" } code <- unlist(lapply(dots[code_pos], deparse)) args <- dots[!code_pos] record_output <- asciicast::record_output output <- do.call( "record_output", c(list(code), args, interactive = interactive, echo = echo) ) r_process <- function() writeLines(output) expect_snapshot(r_process(), transform = transform, variant = variant) } redact_srcref <- function(x) { sub("[ ]*at [-a-zA-Z0-9]+[.]R:[0-9]+:[0-9]+?", "", x) } redact_callr_rs_result <- function(x) { sub("done callr-rs-result-[a-f0-9]+", "done callr-rs-result-", x) } fix_eol <- function(x) { gsub("\\r\\n", "\\n", x, fixed = TRUE) } callr/tests/testthat/test-load-client.R0000644000176200001440000000547714521175632017667 0ustar liggesusers test_that("load_client_lib", { lib <- load_client_lib() funcs <- c("base64_decode", "set_stderr", "write_fd") expect_true( all(funcs %in% names(lib)) ) lib <- load_client_lib(pxdir = find.package("processx")) expect_true( all(funcs %in% names(lib)) ) }) test_that("errors", { mockery::stub(load_client_lib, "system.file", "") expect_error( load_client_lib(), "Cannot find client file" ) }) test_that("errors 2", { sofile <- system.file( "libs", .Platform$r_arch, paste0("client", .Platform$dynlib.ext), package = "processx" ) mockery::stub(load_client_lib, "dyn.load", function(...) stop("ooops")) expect_error(load_client_lib(sofile)) }) test_that("base64", { lib <- load_client_lib() enc <- lib$base64_encode(charToRaw("hello world!")) expect_equal(lib$base64_decode(enc), charToRaw("hello world!")) }) test_that("write_fd", { do <- function() { lib <- asNamespace("callr")$load_client_lib() f1 <- tempfile() c1 <- processx::conn_create_file(f1, write = TRUE) lib$write_fd(processx::conn_get_fileno(c1), "hello world!\n") close(c1) readLines(f1) } ret <- callr::r(do) expect_equal(ret, "hello world!") }) test_that("set_stdout, set_stderr", { # TODO why does this not work on windows, if `set_std{out,err}_file` work? # Also, `set_std{out,err}` work in `r_session`. # But this test crashes at the `set_stdout()` call. skip_on_os("windows") do <- function() { lib <- asNamespace("callr")$load_client_lib() f1 <- tempfile() f2 <- tempfile() c1 <- processx::conn_create_file(f1, write = TRUE) c2 <- processx::conn_create_file(f2, write = TRUE) lib$set_stdout(processx::conn_get_fileno(c1)) lib$set_stderr(processx::conn_get_fileno(c2)) cat("this is output") message("this is error") close(c1) close(c2) c(readLines(f1), readLines(f2)) } ret <- callr::r(do) expect_equal(ret, c("this is output", "this is error")) }) test_that("set_stdout_file, set_setderr_file", { do <- function() { lib <- asNamespace("callr")$load_client_lib() f1 <- tempfile() f2 <- tempfile() lib$set_stdout_file(f1) lib$set_stderr_file(f2) cat("this is output") message("this is error") c(readLines(f1), readLines(f2)) } ret <- callr::r(do) expect_equal(ret, c("this is output", "this is error")) }) test_that("init function of client lib is run", { pxlib <- r(function() { as.environment("tools:callr")$`__callr_data__`$pxlib }) # File name should be `client.SOEXT` so that R can match the init # function from the name expect_true(grepl("^client\\.", basename(pxlib$.path))) # In case R removes the `dynamicLookup` field skip_on_cran() # Should be `FALSE` since processx disables dynamic lookup in the # init function expect_false(unclass(pxlib$.lib)$dynamicLookup) }) callr/tests/testthat/_snaps/0000755000176200001440000000000014521175632015642 5ustar liggesuserscallr/tests/testthat/_snaps/error.md0000644000176200001440000003231714521175632017323 0ustar liggesusers# error is propagated, .Last.error is set Code r_process() Output > callr::r(function() 1 + "A", error = "error") Error: ! in callr subprocess. Caused by error in `1 + "A"`: ! non-numeric argument to binary operator Type .Last.error to see the more details. > .Last.error Error: ! in callr subprocess. Caused by error in `1 + "A"`: ! non-numeric argument to binary operator --- Backtrace: 1. callr::r(function() 1 + "A", error = "error") 2. callr:::get_result(output = out, options) 3. callr:::throw(callr_remote_error(remerr, output), parent = fix_msg(remerr[[3]])) --- Subprocess backtrace: 1. base::.handleSimpleError(function (e) 2. global h(simpleError(msg, call)) # error is propagated, printed if non-interactive mode Code r_process() Output > callr::r(function() 1 + "A", error = "error") Error: ! in callr subprocess. Caused by error in `1 + "A"`: ! non-numeric argument to binary operator --- Backtrace: 1. callr::r(function() 1 + "A", error = "error") 2. callr:::get_result(output = out, options) 3. callr:::throw(callr_remote_error(remerr, output), parent = fix_msg(remerr[[3]])) --- Subprocess backtrace: 1. base::.handleSimpleError(function (e) 2. global h(simpleError(msg, call)) Execution halted # error stack is passed, .Last.error is set Code r_process() Output > callr::r(function() { + f <- function() g() + g <- function() 1 + "A" + f() + }, error = "stack") Error: ! in callr subprocess. Caused by error in `1 + "A"`: ! non-numeric argument to binary operator i With remote `$stack`, use `utils::debugger()` to debug it. Type .Last.error to see the more details. > .Last.error Error: ! in callr subprocess. Caused by error in `1 + "A"`: ! non-numeric argument to binary operator i With remote `$stack`, use `utils::debugger()` to debug it. --- Backtrace: 1. callr::r(function() { 2. callr:::get_result(output = out, options) 3. callr:::throw(callr_remote_error_with_stack(remerr, output), parent = fix_msg(remer # error behavior can be set using option Code r(function() 1 + "A") Condition Error: ! in callr subprocess. Caused by error: ! non-numeric argument to binary operator --- Code r(function() { f <- (function() g()) g <- (function() 1 + "A") f() }) Condition Error: ! in callr subprocess. Caused by error: ! non-numeric argument to binary operator # parent errors Code err <- tryCatch(r(function() 1 + "A"), error = function(e) e) err$parent Output # parent errors, another level Code err <- tryCatch(callr::r(function() { withr::local_options(list(callr.error = "error")) callr::r(function() 1 + "A") }), error = function(e) e) err$parent Output Error: ! in callr subprocess. Caused by error in `1 + "A"`: ! non-numeric argument to binary operator --- Subprocess backtrace: 1. base::.handleSimpleError(function (e) ... 2. global h(simpleError(msg, call)) Code err$parent$parent Output # error traces are printed recursively Code r_process() Output > callr::r(function() callr::r(function() 1 + "a")) Error: ! in callr subprocess. Caused by error: ! in callr subprocess. Caused by error in `1 + "a"`: ! non-numeric argument to binary operator --- Backtrace: 1. callr::r(function() callr::r(function() 1 + "a")) 2. callr:::get_result(output = out, options) 3. callr:::throw(callr_remote_error(remerr, output), parent = fix_msg(remerr[[3]])) --- Subprocess backtrace: 1. callr::r(function() 1 + "a") 2. callr:::get_result(output = out, options) 3. base::throw(callr_remote_error(remerr, output), parent = fix_msg(remerr[[3]])) 4. | base::signalCondition(cond) 5. global (function (e) --- Subprocess backtrace: 1. base::.handleSimpleError(function (e) 2. global h(simpleError(msg, call)) Execution halted # errors in r_bg() are merged Code p$get_result() Condition Error: ! in callr subprocess. Caused by error: ! non-numeric argument to binary operator # errors in r_process are merged Code p$get_result() Condition Error: ! in callr subprocess. Caused by error: ! non-numeric argument to binary operator # errors in r_session$run() are merged Code rs$run(function() 1 + "A") Condition Error: ! in callr subprocess. Caused by error: ! non-numeric argument to binary operator --- Code rs$run(function() 1 + "A") Condition Error: ! in callr subprocess. Caused by error: ! non-numeric argument to binary operator # errors in r_session$call() are merged Code rs$read()$error Output Error: ! in callr subprocess. Caused by error in `1 + "A"`: ! non-numeric argument to binary operator --- Subprocess backtrace: 1. base::.handleSimpleError(function (e) ... 2. global h(simpleError(msg, call)) --- Code rs$read()$error Output Error: ! in callr subprocess. Caused by error in `1 + "A"`: ! non-numeric argument to binary operator --- Subprocess backtrace: 1. base::.handleSimpleError(function (e) ... 2. global h(simpleError(msg, call)) # child error is not modified Code err <- tryCatch(callr::r(function() stop("foobar")), error = function(e) e) err Output Error: ! in callr subprocess. Caused by error in `(function () ...`: ! foobar --- Subprocess backtrace: 1. base::stop("foobar") 2. | base::.handleSimpleError(function (e) ... 3. global h(simpleError(msg, call)) Code class(err) Output [1] "callr_status_error" "callr_error" "rlib_error_3_0" [4] "rlib_error" "error" "condition" Code class(err$parent) Output [1] "simpleError" "error" "condition" # new_callr_error, timeout Code r_process() Output > callr::r(function() Sys.sleep(3), timeout = 1/5) Error in `get_result(output = out, options)`: ! callr timed out Type .Last.error to see the more details. --- Code callr::r(function() Sys.sleep(3), timeout = 1 / 5) Condition Error: ! callr timed out # interrupting an R session Code rs$read() Output $code [1] 200 $message [1] "done callr-rs-result-" $result NULL $stdout [1] "" $stderr [1] "" $error Error: ! callr subprocess interrupted Caused by error: ! interrupt attr(,"class") [1] "callr_session_result" # format.call_status_error Code format(err) Output [1] "Error: " [2] "! in callr subprocess." [3] "Caused by error in `1 + \"\"`:" [4] "! non-numeric argument to binary operator" [5] "Type .Last.error to see the more details." --- Code print(err) Output Error: ! in callr subprocess. Caused by error in `1 + ""`: ! non-numeric argument to binary operator --- Subprocess backtrace: 1. base::.handleSimpleError(function (e) ... 2. global h(simpleError(msg, call)) --- Code format(err) Output [1] "Error: " [2] "! in callr subprocess." [3] "Caused by error in `1 + \"\"`:" [4] "! non-numeric argument to binary operator" [5] "i With remote `$stack`, use `utils::debugger()` to debug it." [6] "Type .Last.error to see the more details." --- Code print(err) Output Error: ! in callr subprocess. Caused by error in `1 + ""`: ! non-numeric argument to binary operator i With remote `$stack`, use `utils::debugger()` to debug it. # format.call_status_error 2 Code r_process() Output > withr::local_options(rlib_error_always_trace = TRUE) > err <- tryCatch(callr::r(function() 1 + ""), error = function(e) e) > writeLines(format(err, trace = TRUE)) Error: ! in callr subprocess. Caused by error in `1 + ""`: ! non-numeric argument to binary operator --- Backtrace: 1. base::tryCatch(callr::r(function() 1 + ""), error = function(e) e) 2. base::tryCatchList(expr, classes, parentenv, handlers) 3. base::tryCatchOne(expr, names, parentenv, handlers[[1L]]) 4. base::doTryCatch(return(expr), name, parentenv, handler) 5. callr::r(function() 1 + "") 6. callr:::get_result(output = out, options) 7. callr:::throw(callr_remote_error(remerr, output), parent = fix_msg(remerr[[3]])) --- Subprocess backtrace: 1. base::.handleSimpleError(function (e) 2. global h(simpleError(msg, call)) # stdout/stderr is printed on error Code r_process() Output > callr::r(function() { + warning("I have a bad feeling about this") + stop("told ya") + }) Error: ! in callr subprocess. Caused by error in `(function () ...`: ! told ya i See `$stderr` for standard error. Type .Last.error to see the more details. > .Last.error Error: ! in callr subprocess. Caused by error in `(function () ...`: ! told ya i See `$stderr` for standard error. --- Backtrace: 1. callr::r(function() { 2. callr:::get_result(output = out, options) 3. callr:::throw(callr_remote_error(remerr, output), parent = fix_msg(remerr[[3]])) --- Subprocess backtrace: 1. base::stop("told ya") 2. | base::.handleSimpleError(function (e) 3. global h(simpleError(msg, call)) > .Last.error$stderr [1] "Warning message:\nIn (function () : I have a bad feeling about this\n" # stdout/stderr is printed on error 2 Code r_process() Output > callr::r(function() { + writeLines("Just some output") + stop("told ya") + }) Error: ! in callr subprocess. Caused by error in `(function () ...`: ! told ya i See `$stdout` for standard output. Type .Last.error to see the more details. > .Last.error Error: ! in callr subprocess. Caused by error in `(function () ...`: ! told ya i See `$stdout` for standard output. --- Backtrace: 1. callr::r(function() { 2. callr:::get_result(output = out, options) 3. callr:::throw(callr_remote_error(remerr, output), parent = fix_msg(remerr[[3]])) --- Subprocess backtrace: 1. base::stop("told ya") 2. | base::.handleSimpleError(function (e) 3. global h(simpleError(msg, call)) > .Last.error$stdout [1] "Just some output\n" # stdout/stderr is printed on error 3 Code r_process() Output > callr::r(function() { + writeLines("Just some output") + warning("I have a bad feeling about this") + stop("told ya") + }) Error: ! in callr subprocess. Caused by error in `(function () ...`: ! told ya --- Standard output: Just some output --- Standard error: Warning message: In (function () : I have a bad feeling about this --- Backtrace: 1. callr::r(function() { 2. callr:::get_result(output = out, options) 3. callr:::throw(callr_remote_error(remerr, output), parent = fix_msg(remerr[[3]])) --- Subprocess backtrace: 1. base::stop("told ya") 2. | base::.handleSimpleError(function (e) 3. global h(simpleError(msg, call)) Execution halted # error is printed to file Code err$stderr Output [1] "Error in (function () : ouch\n" --- Code readLines(tmp) Output [1] "Error in (function () : ouch" callr/tests/testthat/test-rcmd-process.R0000644000176200001440000000150514326532177020065 0ustar liggesusers 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.R0000644000176200001440000000041314326532177017154 0ustar liggesusers 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.R0000644000176200001440000000276414326532177016634 0ustar liggesusers 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.R0000644000176200001440000000111214326532177017265 0ustar liggesusers 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.R0000644000176200001440000000311214326530060016545 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.R0000644000176200001440000000027614326532177017405 0ustar liggesusers 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.R0000644000176200001440000000121614326532177016445 0ustar liggesusers 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.R0000644000176200001440000000436714326532260017154 0ustar liggesusers 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_null(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.R0000644000176200001440000000365114143453135020074 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.R0000644000176200001440000000006614326532177014510 0ustar liggesuserslibrary(testthat) library(callr) test_check("callr") callr/R/0000755000176200001440000000000014564551560011563 5ustar liggesuserscallr/R/script.R0000644000176200001440000001315614521175632013213 0ustar liggesusers make_vanilla_script_expr <- function(expr_file, res, error, pre_hook = NULL, post_hook = NULL, messages = FALSE, print_error = TRUE) { ## Code to handle errors in the child ## This will inserted into the main script if (! error %in% c("error", "stack", "debugger")) { throw(new_error("Unknown `error` argument: `", error, "`")) } err <- if (error == "error") { substitute({ callr_data <- base::as.environment("tools:callr")$`__callr_data__` err <- callr_data$err if (`__traceback__`) { # This might be queried for R sessions with $traceback() base::assign(".Traceback", base::.traceback(4), envir = callr_data) # Also dump frames, this might be queried as well with $debug() utils::dump.frames("__callr_dump__") base::assign(".Last.dump", .GlobalEnv$`__callr_dump__`, envir = callr_data) base::rm("__callr_dump__", envir = .GlobalEnv) } e <- err$process_call(e) e2 <- err$new_error("error in callr subprocess") class <- base::class class(e2) <- base::c("callr_remote_error", class(e2)) e2 <- err$add_trace_back(e2) cut <- base::which(e2$trace$scope == "global")[1] if (!base::is.na(cut)) { e2$trace <- e2$trace[-(1:cut), ] } base::saveRDS(base::list("error", e2, e), file = base::paste0(`__res__`, ".error")) }, list( `__res__` = res, `__traceback__` = getOption("callr.traceback", FALSE) ) ) } else if (error %in% c("stack", "debugger")) { substitute( { callr_data <- base::as.environment("tools:callr")$`__callr_data__` base::assign(".Traceback", base::.traceback(4), envir = callr_data) utils::dump.frames("__dump__") # nocov start base::saveRDS( base::list(`__type__`, e, .GlobalEnv$`__dump__`), file = base::paste0(`__res__`, ".error") ) # nocov end }, list( "__type__" = error, "__res__" = res ) ) } if (messages) { message <- function() { substitute({ pxlib <- base::as.environment("tools:callr")$`__callr_data__`$pxlib if (base::is.null(e$code)) e$code <- "301" msg <- base::paste0("base64::", pxlib$base64_encode(base::serialize(e, NULL))) data <- base::paste0(e$code, " ", base::nchar(msg), "\n", msg) pxlib$write_fd(3L, data) if (base::inherits(e, "cli_message") && !base::is.null(base::findRestart("cli_message_handled"))) { base::invokeRestart("cli_message_handled") } else if (base::inherits(e, "message") && !base::is.null(base::findRestart("muffleMessage"))) { base::invokeRestart("muffleMessage") } }) } } else { message <- function() substitute(base::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( { base::tryCatch( # nocov start base::withCallingHandlers( { `__pre_hook__` base::saveRDS( base::do.call( base::do.call, base::c( base::readRDS(`__expr_file__`), base::list(envir = .GlobalEnv, quote = TRUE) ), envir = .GlobalEnv, quote = TRUE ), file = `__res__`, compress = `__compress__` ) base::flush(base::stdout()) base::flush(base::stderr()) `__post_hook__` base::invisible() }, error = function(e) { `__error__` }, interrupt = function(e) { `__error__` }, callr_message = function(e) { base::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__`; if (`__print_error__`) { base::try(base::stop(e)) } else { base::invisible() } }, interrupt = function(e) { `__post_hook__` if (`__print_error__`) { e } else { base::invisible() } } ) # nocov end }, list(`__error__` = err, `__expr_file__` = expr_file, `__res__` = res, `__pre_hook__` = pre_hook, `__post_hook__` = post_hook, `__message__` = message(), `__compress__` = getOption("callr.compress_transport", FALSE), `__print_error__` = print_error) ) } make_vanilla_script_file <- function(expr_file, res, error, print_error) { expr <- make_vanilla_script_expr( expr_file, res, error, print_error = print_error ) script <- deparse(expr) tmp <- tempfile("callr-scr-") cat(script, file = tmp, sep = "\n") tmp } callr/R/rcmd-process.R0000644000176200001440000000310014143453135014271 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.R0000644000176200001440000000467514326530060012763 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/callr-package.R0000644000176200001440000000055414564617033014376 0ustar liggesusers #' 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. #' #' # callr #' #' ```{r man-readme, child = "README.Rmd"} #' ``` #' #' @keywords internal "_PACKAGE" ## usethis namespace: start ## usethis namespace: end NULL callr/R/utils.R0000644000176200001440000000744114371777711013060 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.R0000644000176200001440000010043414521175632013625 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_session$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. #' #' On callr version 3.8.0 and above, you need to set the #' `callr.traceback` option to `TRUE` (in the main process) to make #' the subprocess save the trace on error. This is because saving #' the trace can be costly for large objects passed as arguments. #' @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]. #' #' On callr version 3.8.0 and above, you need to set the #' `callr.traceback` option to `TRUE` (in the main process) to make #' the subprocess dump frames on error. This is because saving #' the frames can be costly for large objects passed as arguments. 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_crash_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()) invisible() } 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, print_error = FALSE) 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" idle <- private$state == "idle" missing <- as.difftime(NA_real_, units = "secs") c(total = now - private$started_at, current = if (finished | idle) missing else 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) { tb <- self$run(function() { traceback(as.environment("tools:callr")$`__callr_data__`$.Traceback, 10) }) if (is.null(tb)) { throw(new_error("No traceback was recorded in the subprocess (yet?)")) } else { traceback(utils::head(tb, -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) or type .q 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", " .q -- quit debugger\n", " -- run in frame or .GlobalEnv\n\n", sep = "") } should_quit <- FALSE 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 if (cmd == ".q") { should_quit <<- TRUE 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 (should_quit) break if (is.null(cmd2)) next try(update_history(cmd), silent = TRUE) 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) } else { 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(), " > ")) if (cmd == ".q") break try(update_history(cmd), silent = TRUE) 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, muffleMessage = 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 experts. Note that this option is not used #' for the startup phase that currently always runs with `stdout = "|"`. #' * `stderr`: Similar to `stdout`, but for the standard error. Like #' `stdout`, it is not used for the startup phase, which runs with #' `stderr = "|"`. #' * `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. #' #' Note that on callr version 3.8.0 and above, you need to set the #' `callr.traceback` option to `TRUE` (in the main process) to make #' the subprocess dump the frames on error. This is because saving #' the frames can be costly for large objects passed as arguments. #' #' `$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.R0000644000176200001440000000637114564551560013233 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 output 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_crash_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_crash_error(output, killmsg)) } else { return(ret) } } ## No error file? Then probably all is well, return the output ## If this is corrupt, 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_crash_error(output, killmsg)) } else { throw(new_callr_crash_error( output, "could not read result from callr" )) } } ) return(ret) } # To work around an errors.R bug, if the format method is registered # from another package. errors.R expects that the parent error has a # message, but if it is an interrupt (and perhaps some other rare cases), # it does not. fix_msg <- function(cnd) { if (is.null(cnd$message)) { if (inherits(cnd, "interrupt")) { cnd$message <- "interrupt" } else { cnd$message <- "" } } cnd } ## 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_crash_error(output, killmsg)) ) if (remerr[[1]] == "error") { throw(callr_remote_error(remerr, output), parent = fix_msg(remerr[[3]])) } else if (remerr[[1]] == "stack") { throw(callr_remote_error_with_stack(remerr, output), parent = fix_msg(remerr[[2]])) } 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.R0000644000176200001440000000074214326533425013430 0ustar liggesusers client_env <- local({ env <- new.env(parent = emptyenv()) env$`__callr_data__` <- new.env(parent = baseenv()) rsfile <- file.path("R", "aaa-rstudio-detect.R") sys.source(rsfile, envir = env$`__callr_data__`, keep.source = FALSE) 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.R0000644000176200001440000002227014330450337013040 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, !is.null(options$stderr) ) 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, compress = getOption("callr.compress_transport", FALSE))) 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) options <- 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) env["CALLR_IS_RUNNING"] <- "true" }) call_user_hooks(options) } 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) cat("\n", file = profile_system, append = TRUE) } } 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_ } # Prevent circular inclusion of .Rprofile. cat( "if (is.null(getOption(\"callr.rprofile_loaded\"))) {", " options(callr.rprofile_loaded = TRUE)", sep = "\n", file = profile_user, append = TRUE ) 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) } # End of include guard. cat("\n}\n", 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) cat("\n", file = env_sys, append = TRUE) } 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) cat("\n", file = env_user, append = TRUE) } 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.R0000644000176200001440000000737614330453006013037 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_crash_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 } callr_remote_error <- function(remerr, out) { if (inherits(remerr[[3]], "interrupt")) { err <- new_error("interrupt sugnal in callr subprocess", call. = FALSE) class(err) <- c("callr_timeout_error", "callr_error", class(err)) err$message <- "callr subprocess interrupted" } else { err <- new_error("in callr subprocess.", call. = FALSE) class(err) <- c("callr_status_error", "callr_error", class(err)) } err$status <- out$status err$stdout <- out$stdout err$stderr <- out$stderr err$parent_trace <- remerr[[2]]$trace err } callr_remote_error_with_stack <- function(remerr, out) { err <- new_error("in callr subprocess.", call. = FALSE) class(err) <- c("callr_status_error", "callr_error", class(err)) err$status <- out$status err$stdout <- out$stdout err$stderr <- out$stderr err$stack <- clean_stack(remerr[[3]]) err } #' @export format.callr_status_error <- function(x, trace = FALSE, class = FALSE, advice = !trace, ...) { class(x) <- setdiff(class(x), "callr_status_error") lines <- NextMethod( object = x, trace = FALSE, class = class, advice = FALSE, ... ) info <- if (err$.internal$has_cli()) { cli::col_cyan(cli::symbol$info) } else { "i" # nocov } if (!is.null(x$stack)) { lines <- c( lines, paste0(info, " With remote `$stack`, use `utils::debugger()` to debug it.") ) } notempty <- function(x) !is.null(x) && sum(nchar(x)) > 0 hasout <- notempty(x$stdout) haserr <- notempty(x$stderr) if (hasout || haserr) { if (err$.internal$is_interactive()) { lines <- c( lines, if (hasout && haserr) { paste0(info, " See `$stdout` and `$stderr` for standard output and error.") } else if (hasout) { paste0(info, " See `$stdout` for standard output.") } else { paste0(info, " See `$stderr` for standard error.") } ) } else { lines <- c( lines, if (hasout) { c( "---", "Standard output:", trimws(x$stdout) ) }, if (haserr) { c("---", "Standard error:", trimws(x$stderr) ) } ) } } lines <- c( lines, if (advice) err$format$advice(), if (trace && !is.null(x$trace)) { c( "---", "Backtrace:", err$format$trace(x$trace) ) } ) cond <- x while (trace && !is.null(cond$parent_trace)) { lines <- c(lines, c("---", "Subprocess backtrace:", format(cond$parent_trace))) cond <- cond$parent } lines } #' @export print.callr_status_error <- function(x, trace = TRUE, class = TRUE, advice = !trace, ...) { writeLines(format(x, trace = trace, class = class, advice = advice, ...)) } callr/R/eval-bg.R0000644000176200001440000000307314564551363013227 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. #' Use `p$get_result()` to collect the result or to throw an error #' if the background computation failed. #' #' @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.R0000644000176200001440000000737014326530060013374 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.R0000644000176200001440000000743614521175632014106 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) # nocov # stop() here and not throw(), because this function should be standalone stop("Cannot find client file") } # We set this to `FALSE` if we load the library from the processx # install path since that library might be shared (e.g. in tests) need_cleanup <- TRUE if (is.null(sofile)) { sofile <- sofile_in_processx() lib <- dyn.load(sofile) need_cleanup <- FALSE } 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( expr = { lib <- dyn.load(sofile) need_cleanup <- FALSE }, error = function(err2) { err2$message <- err2$message <- paste0(" after ", lib$message) stop(err2) } ) } } # cleanup if setup fails if (need_cleanup) { on.exit(try(dyn.unload(sofile), silent = TRUE), add = TRUE) } 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 env$.lib <- lib 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() { if (need_cleanup) { 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.R0000644000176200001440000000364314326530060013365 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.R0000644000176200001440000010601514521175632013220 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/blob/main/R/errors.R # # ## Dependencies # - rstudio-detect.R for better printing in RStudio # # ## 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, srcref = NULL, domain = NA) # new_error(..., call. = TRUE, srcref = NULL, domain = NA) # throw(cond, parent = NULL, frame = environment()) # throw_error(cond, parent = NULL, frame = environment()) # chain_error(expr, err, call = sys.call(-1)) # chain_call(.NAME, ...) # chain_clean_call(.NAME, ...) # onload_hook() # add_trace_back(cond, frame = NULL) # format$advice(x) # format$call(call) # format$class(x) # format$error(x, trace = FALSE, class = FALSE, advice = !trace, ...) # format$error_heading(x, prefix = NULL) # format$header_line(x, prefix = NULL) # format$srcref(call, srcref = NULL) # format$trace(x, ...) # ``` # # ## 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 # # ### 2.0.1 -- 2021-06-29 # # * Do not convert error messages to native encoding before printing, # to be able to print UTF-8 error messages on Windows. # # ### 2.0.2 -- 2021-09-07 # # * Do not translate error messages, as this converts them to the native # encoding. We keep messages in UTF-8 now. # # ### 3.0.0 -- 2022-04-19 # # * Major rewrite, use rlang compatible error objects. New API. # # ### 3.0.1 -- 2022-06-17 # # * Remove the `rlang_error` and `rlang_trace` classes, because our new # deparsed `call` column in the trace is not compatible with rlang. # # ### 3.0.2 -- 2022-08-01 # # * Use a `procsrcref` column for processed source references. # Otherwise testthat (and probably other rlang based packages), will # pick up the `srcref` column, and they expect an `srcref` object there. # # ### 3.1.0 -- 2022-10-04 # # * Add ANSI hyperlinks to stack traces, if we have a recent enough # cli package that supports this. # # ### 3.1.1 -- 2022-11-17 # # * Use `[[` instead of `$` to fix some partial matches. # * Use fully qualified `base::stop()` to enable overriding `stop()` # in a package. (Makes sense if compat files use `stop()`. # * The `is_interactive()` function is now exported. # # ### 3.1.2 -- 2022-11-18 # # * The `parent` condition can now be an interrupt. err <- local({ # -- dependencies ----------------------------------------------------- rstudio_detect <- rstudio$detect # -- 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 srcref Alternative source reference object to use instead of #' the one of `call.`. #' @param domain Translation domain, see [stop()]. We set this to #' `NA` by default, which means that no translation occurs. This #' has the benefit that the error message is not re-encoded into #' the native locale. #' @return Condition object. Currently a list, but you should not rely #' on that. new_cond <- function(..., call. = TRUE, srcref = NULL, domain = NA) { message <- .makeMessage(..., domain = domain) structure( list(message = message, call = call., srcref = srcref), 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 srcref Passed tp [new_cond()]. #' @param domain Passed to [new_cond()]. #' @return Error condition object with classes `rlib_error`, `error` #' and `condition`. new_error <- function(..., call. = TRUE, srcref = NULL, domain = NA) { cond <- new_cond(..., call. = call., domain = domain, srcref = srcref) class(cond) <- c("rlib_error_3_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. #' @param frame The throwing context. Can be used to hide frames from #' the backtrace. throw <- throw_error <- function(cond, parent = NULL, frame = environment()) { if (!inherits(cond, "condition")) { cond <- new_error(cond) } 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 } cond <- process_call(cond) if (!is.null(parent)) { cond$parent <- process_call(parent) } # 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")) .hide_from_trace <- 1L # .error_frame <- cond if (!always_trace) signalCondition(cond) 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, frame = frame) # 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 methods 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) # 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()) .hide_from_trace <- NULL # 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)) { return(th(cond)) } # 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. out <- format( cond, trace = !is_interactive(), class = FALSE, full = !is_interactive() ) writeLines(out, con = default_output()) # 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") # Turn off the regular error printing to avoid printing # the error twice. opts <- options(show.error.messages = FALSE) on.exit(options(opts), add = TRUE) base::stop(cond) } # -- rethrow with parent ----------------------------------------------- #' Re-throw an error with a better error message #' #' Evaluate `expr` and if it errors, then throw a new error `err`, #' with the original error set as its parent. #' #' @noRd #' @param expr Expression to evaluate. #' @param err Error object or message to use for the child error. #' @param call Call to use in the re-thrown error. See [throw()]. chain_error <- function(expr, err, call = sys.call(-1), srcref = NULL) { .hide_from_trace <- 1 force(call) srcref <- srcref %||% utils::getSrcref(sys.call()) withCallingHandlers({ expr }, error = function(e) { .hide_from_trace <- 0:1 e$srcref <- srcref e$procsrcref <- NULL if (!inherits(err, "condition")) { err <- new_error(err, call. = call) } throw_error(err, parent = e) }) } # -- rethrowing conditions from C code --------------------------------- #' Version of .Call that throw()s errors #' #' It re-throws error from compiled 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. chain_call <- function(.NAME, ...) { .hide_from_trace <- 1:3 # withCallingHandlers + do.call + .handleSimpleError (?) call <- sys.call() call1 <- sys.call(-1) srcref <- utils::getSrcref(call) withCallingHandlers( do.call(".Call", list(.NAME, ...)), error = function(e) { .hide_from_trace <- 0:1 e$srcref <- srcref e$procsrcref <- NULL e[["call"]] <- call name <- native_name(.NAME) err <- new_error("Native call to `", name, "` failed", call. = call1) cerror <- if (inherits(e, "simpleError")) "c_error" class(err) <- c(cerror, "rlib_error_3_0", "rlib_error", "error", "condition") throw_error(err, parent = e) } ) } package_env <- topenv() #' Version of entrace_call that supports cleancall #' #' This function is the same as [entrace_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. chain_clean_call <- function(.NAME, ...) { .hide_from_trace <- 1:3 call <- sys.call() call1 <- sys.call(-1) srcref <- utils::getSrcref(call) withCallingHandlers( package_env$call_with_cleanup(.NAME, ...), error = function(e) { .hide_from_trace <- 0:1 e$srcref <- srcref e$procsrcref <- NULL e[["call"]] <- call name <- native_name(.NAME) err <- new_error("Native call to `", name, "` failed", call. = call1) cerror <- if (inherits(e, "simpleError")) "c_error" class(err) <- c(cerror, "rlib_error_3_0", "rlib_error", "error", "condition") throw_error(err, parent = 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 frame Use this context to hide some frames from the traceback. #' #' @return A condition object, with the trace added. add_trace_back <- function(cond, frame = NULL) { idx <- seq_len(sys.parent(1L)) frames <- sys.frames()[idx] # TODO: remove embedded objects from calls calls <- as.list(sys.calls()[idx]) parents <- sys.parents()[idx] namespaces <- unlist(lapply( seq_along(frames), function(i) { if (is_operator(calls[[i]])) { "o" } else { env_label(topenvx(environment(sys.function(i)))) } } )) pids <- rep(cond$`_pid` %||% Sys.getpid(), length(calls)) mch <- match(format(frame), sapply(frames, format)) if (is.na(mch)) { visibles <- TRUE } else { visibles <- c(rep(TRUE, mch), rep(FALSE, length(frames) - mch)) } scopes <- vapply(idx, FUN.VALUE = character(1), function(i) { tryCatch( get_call_scope(calls[[i]], namespaces[[i]]), error = function(e) "" ) }) namespaces <- ifelse(scopes %in% c("::", ":::"), namespaces, NA_character_) funs <- ifelse( is.na(namespaces), ifelse(scopes != "", paste0(scopes, " "), ""), paste0(namespaces, scopes) ) funs <- paste0( funs, vapply(calls, function(x) format_name(x[[1]])[1], character(1)) ) visibles <- visibles & mark_invisible_frames(funs, frames) pcs <- lapply(calls, function(c) process_call(list(call = c))) calls <- lapply(pcs, "[[", "call") srcrefs <- I(lapply(pcs, "[[", "srcref")) procsrcrefs <- I(lapply(pcs, "[[", "procsrcref")) cond$trace <- new_trace( calls, parents, visibles = visibles, namespaces = namespaces, scopes = scopes, srcrefs = srcrefs, procsrcrefs = procsrcrefs, pids ) cond } is_operator <- function(cl) { is.call(cl) && length(cl) >= 1 && is.symbol(cl[[1]]) && grepl("^[^.a-zA-Z]", as.character(cl[[1]])) } mark_invisible_frames <- function(funs, frames) { visibles <- rep(TRUE, length(frames)) hide <- lapply(frames, "[[", ".hide_from_trace") w_hide <- unlist(mapply(seq_along(hide), hide, FUN = function(i, w) { i + w }, SIMPLIFY = FALSE)) w_hide <- w_hide[w_hide <= length(frames)] visibles[w_hide] <- FALSE hide_from <- which(funs %in% names(invisible_frames)) for (start in hide_from) { hide_this <- invisible_frames[[ funs[start] ]] for (i in seq_along(hide_this)) { if (start + i > length(funs)) break if (funs[start + i] != hide_this[i]) break visibles[start + i] <- FALSE } } visibles } invisible_frames <- list( "base::source" = c("base::withVisible", "base::eval", "base::eval"), "base::stop" = "base::.handleSimpleError", "cli::cli_abort" = c( "rlang::abort", "rlang:::signal_abort", "base::signalCondition"), "rlang::abort" = c("rlang:::signal_abort", "base::signalCondition") ) call_name <- function(x) { if (is.call(x)) { if (is.symbol(x[[1]])) { as.character(x[[1]]) } else if (x[[1]][[1]] == quote(`::`) || x[[1]][[1]] == quote(`:::`)) { as.character(x[[1]][[2]]) } else { NULL } } else { NULL } } get_call_scope <- function(call, ns) { if (is.na(ns)) return("global") if (!is.call(call)) return("") if (is.call(call[[1]]) && (call[[1]][[1]] == quote(`::`) || call[[1]][[1]] == quote(`:::`))) return("") if (ns == "base") return("::") if (! ns %in% loadedNamespaces()) return("") name <- call_name(call) nsenv <- asNamespace(ns)$.__NAMESPACE__. if (is.null(nsenv)) return("::") if (is.null(nsenv$exports)) return(":::") if (exists(name, envir = nsenv$exports, inherits = FALSE)) { "::" } else if (exists(name, envir = asNamespace(ns), inherits = FALSE)) { ":::" } else { "local" } } topenvx <- function(x) { topenv(x, matchThisEnv = err_env) } new_trace <- function (calls, parents, visibles, namespaces, scopes, srcrefs, procsrcrefs, pids) { trace <- data.frame( stringsAsFactors = FALSE, parent = parents, visible = visibles, namespace = namespaces, scope = scopes, srcref = srcrefs, procsrcref = procsrcrefs, pid = pids ) trace[["call"]] <- calls class(trace) <- c("rlib_trace_3_0", "rlib_trace", "tbl", "data.frame") 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(env_name(package_env)) } if (identical(env, globalenv())) { return(NA_character_) } if (identical(env, baseenv())) { return("base") } if (identical(env, emptyenv())) { return("empty") } nm <- environmentName(env) if (isNamespace(env)) { return(nm) } nm } # -- S3 methods ------------------------------------------------------- format_error <- function(x, trace = FALSE, class = FALSE, advice = !trace, full = trace, header = TRUE, ...) { if (has_cli()) { format_error_cli(x, trace, class, advice, full, header, ...) } else { format_error_plain(x, trace, class, advice, full, header, ...) } } print_error <- function(x, trace = TRUE, class = TRUE, advice = !trace, ...) { writeLines(format_error(x, trace, class, advice, ...)) } format_trace <- function(x, ...) { if (has_cli()) { format_trace_cli(x, ...) } else { format_trace_plain(x, ...) } } print_trace <- function(x, ...) { writeLines(format_trace(x, ...)) } cnd_message <- function(cond) { paste(cnd_message_(cond, full = FALSE), collapse = "\n") } cnd_message_ <- function(cond, full = FALSE) { if (has_cli()) { cnd_message_cli(cond, full) } else { cnd_message_plain(cond, full) } } # -- format API ------------------------------------------------------- format_advice <- function(x) { if (has_cli()) { format_advice_cli(x) } else { format_advice_plain(x) } } format_call <- function(call) { if (has_cli()) { format_call_cli(call) } else { format_call_plain(call) } } format_class <- function(x) { if (has_cli()) { format_class_cli(x) } else { format_class_plain(x) } } format_error_heading <- function(x, prefix = NULL) { if (has_cli()) { format_error_heading_cli(x, prefix) } else { format_error_heading_plain(x, prefix) } } format_header_line <- function(x, prefix = NULL) { if (has_cli()) { format_header_line_cli(x, prefix) } else { format_header_line_plain(x, prefix) } } format_srcref <- function(call, srcref = NULL) { if (has_cli()) { format_srcref_cli(call, srcref) } else { format_srcref_plain(call, srcref) } } # -- condition message with cli --------------------------------------- cnd_message_robust <- function(cond) { class(cond) <- setdiff(class(cond), "rlib_error_3_0") conditionMessage(cond) %||% (if (inherits(cond, "interrupt")) "interrupt") %||% "" } cnd_message_cli <- function(cond, full = FALSE) { exp <- paste0(cli::col_yellow("!"), " ") add_exp <- is.null(names(cond$message)) msg <- cnd_message_robust(cond) c( paste0(if (add_exp) exp, msg), if (inherits(cond$parent, "condition")) { msg <- if (full && inherits(cond$parent, "rlib_error_3_0")) { format(cond$parent, trace = FALSE, full = TRUE, class = FALSE, header = FALSE, advice = FALSE ) } else if (inherits(cond$parent, "interrupt")) { "interrupt" } else { conditionMessage(cond$parent) } add_exp <- substr(cli::ansi_strip(msg[1]), 1, 1) != "!" if (add_exp) msg[1] <- paste0(exp, msg[1]) c(format_header_line_cli(cond$parent, prefix = "Caused by error"), msg ) } ) } # -- condition message w/o cli ---------------------------------------- cnd_message_plain <- function(cond, full = FALSE) { exp <- "! " add_exp <- is.null(names(cond$message)) c( paste0(if (add_exp) exp, cnd_message_robust(cond)), if (inherits(cond$parent, "condition")) { msg <- if (full && inherits(cond$parent, "rlib_error_3_0")) { format(cond$parent, trace = FALSE, full = TRUE, class = FALSE, header = FALSE, advice = FALSE ) } else if (inherits(cond$parent, "interrupt")) { "interrupt" } else { conditionMessage(cond$parent) } add_exp <- substr(msg[1], 1, 1) != "!" if (add_exp) { msg[1] <- paste0(exp, msg[1]) } c(format_header_line_plain(cond$parent, prefix = "Caused by error"), msg ) } ) } # -- printing error with cli ------------------------------------------ # Error parts: # - "Error:" or "Error in " prefix, the latter if the error has a call # - the call, possibly syntax highlightedm possibly trimmed (?) # - source ref, with link to the file, potentially in a new line in cli # - error message, just `conditionMessage()` # - advice about .Last.error and/or .Last.error.trace format_error_cli <- function(x, trace = TRUE, class = TRUE, advice = !trace, full = trace, header = TRUE, ...) { p_class <- if (class) format_class_cli(x) p_header <- if (header) format_header_line_cli(x) p_msg <- cnd_message_cli(x, full) p_advice <- if (advice) format_advice_cli(x) else NULL p_trace <- if (trace && !is.null(x$trace)) { c("---", "Backtrace:", format_trace_cli(x$trace)) } c(p_class, p_header, p_msg, p_advice, p_trace) } format_header_line_cli <- function(x, prefix = NULL) { p_error <- format_error_heading_cli(x, prefix) p_call <- format_call_cli(x[["call"]]) p_srcref <- format_srcref_cli(conditionCall(x), x$procsrcref %||% x$srcref) paste0(p_error, p_call, p_srcref, if (!is.null(conditionCall(x))) ":") } format_class_cli <- function(x) { cls <- unique(setdiff(class(x), "condition")) cls # silence codetools cli::format_inline("{.cls {cls}}") } format_error_heading_cli <- function(x, prefix = NULL) { str_error <- if (is.null(prefix)) { cli::style_bold(cli::col_yellow("Error")) } else { cli::style_bold(paste0(prefix)) } if (is.null(conditionCall(x))) { paste0(str_error, ": ") } else { paste0(str_error, " in ") } } format_call_cli <- function(call) { if (is.null(call)) { NULL } else { cl <- trimws(format(call)) if (length(cl) > 1) cl <- paste0(cl[1], " ", cli::symbol$ellipsis) cli::format_inline("{.code {cl}}") } } format_srcref_cli <- function(call, srcref = NULL) { ref <- get_srcref(call, srcref) if (is.null(ref)) return("") link <- if (ref$file != "") { if (Sys.getenv("R_CLI_HYPERLINK_STYLE") == "iterm") { cli::style_hyperlink( cli::format_inline("{basename(ref$file)}:{ref$line}:{ref$col}"), paste0("file://", ref$file, "#", ref$line, ":", ref$col) ) } else { cli::style_hyperlink( cli::format_inline("{basename(ref$file)}:{ref$line}:{ref$col}"), paste0("file://", ref$file), params = c(line = ref$line, col = ref$col) ) } } else { paste0("line ", ref$line) } cli::col_silver(paste0(" at ", link)) } str_advice <- "Type .Last.error to see the more details." format_advice_cli <- function(x) { cli::col_silver(str_advice) } format_trace_cli <- function(x, ...) { x$num <- seq_len(nrow(x)) scope <- ifelse( is.na(x$namespace), ifelse(x$scope != "", paste0(x$scope, " "), ""), paste0(x$namespace, x$scope) ) visible <- if ("visible" %in% names(x)) { x$visible } else { rep(TRUE, nrow(x)) } srcref <- if ("srcref" %in% names(x) || "procsrcref" %in% names(x)) { vapply( seq_len(nrow(x)), function(i) format_srcref_cli(x[["call"]][[i]], x$procsrcref[[i]] %||% x$srcref[[i]]), character(1) ) } else { unname(vapply(x[["call"]], format_srcref_cli, character(1))) } lines <- paste0( cli::col_silver(format(x$num), ". "), ifelse (visible, "", "| "), scope, vapply(seq_along(x$call), function(i) { format_trace_call_cli(x$call[[i]], x$namespace[[i]]) }, character(1)), srcref ) lines[!visible] <- cli::col_silver(cli::ansi_strip( lines[!visible], link = FALSE )) lines } format_trace_call_cli <- function(call, ns = "") { envir <- tryCatch(asNamespace(ns), error = function(e) .GlobalEnv) cl <- trimws(format(call)) if (length(cl) > 1) { cl <- paste0(cl[1], " ", cli::symbol$ellipsis) } # Older cli does not have 'envir'. if ("envir" %in% names(formals(cli::code_highlight))) { fmc <- cli::code_highlight(cl, envir = envir)[1] } else { fmc <- cli::code_highlight(cl)[1] } cli::ansi_strtrim(fmc, cli::console_width() - 5) } # ---------------------------------------------------------------------- format_error_plain <- function(x, trace = TRUE, class = TRUE, advice = !trace, full = trace, header = TRUE, ...) { p_class <- if (class) format_class_plain(x) p_header <- if (header) format_header_line_plain(x) p_msg <- cnd_message_plain(x, full) p_advice <- if (advice) format_advice_plain(x) else NULL p_trace <- if (trace && !is.null(x$trace)) { c("---", "Backtrace:", format_trace_plain(x$trace)) } c(p_class, p_header, p_msg, p_advice, p_trace) } format_trace_plain <- function(x, ...) { x$num <- seq_len(nrow(x)) scope <- ifelse( is.na(x$namespace), ifelse(x$scope != "", paste0(x$scope, " "), ""), paste0(x$namespace, x$scope) ) visible <- if ("visible" %in% names(x)) { x$visible } else { rep(TRUE, nrow(x)) } srcref <- if ("srcref" %in% names(x) || "procsrfref" %in% names(x)) { vapply( seq_len(nrow(x)), function(i) format_srcref_plain(x[["call"]][[i]], x$procsrcref[[i]] %||% x$srcref[[i]]), character(1) ) } else { unname(vapply(x[["call"]], format_srcref_plain, character(1))) } lines <- paste0( paste0(format(x$num), ". "), ifelse (visible, "", "| "), scope, vapply(x[["call"]], format_trace_call_plain, character(1)), srcref ) lines } format_advice_plain <- function(x, ...) { str_advice } format_header_line_plain <- function(x, prefix = NULL) { p_error <- format_error_heading_plain(x, prefix) p_call <- format_call_plain(x[["call"]]) p_srcref <- format_srcref_plain(conditionCall(x), x$procsrcref %||% x$srcref) paste0(p_error, p_call, p_srcref, if (!is.null(conditionCall(x))) ":") } format_error_heading_plain <- function(x, prefix = NULL) { str_error <- if (is.null(prefix)) "Error" else prefix if (is.null(conditionCall(x))) { paste0(str_error, ": ") } else { paste0(str_error, " in ") } } format_class_plain <- function(x) { cls <- unique(setdiff(class(x), "condition")) paste0("<", paste(cls, collapse = "/"), ">") } format_call_plain <- function(call) { if (is.null(call)) { NULL } else { cl <- trimws(format(call)) if (length(cl) > 1) cl <- paste0(cl[1], " ...") paste0("`", cl, "`") } } format_srcref_plain <- function(call, srcref = NULL) { ref <- get_srcref(call, srcref) if (is.null(ref)) return("") link <- if (ref$file != "") { paste0(basename(ref$file), ":", ref$line, ":", ref$col) } else { paste0("line ", ref$line) } paste0(" at ", link) } format_trace_call_plain <- function(call) { fmc <- trimws(format(call)[1]) if (length(fmc) > 1) { fmc <- paste0(fmc[1], " ...") } strtrim(fmc, getOption("width") - 5) } # -- utilities --------------------------------------------------------- cli_version <- function() { # this loads cli! package_version(asNamespace("cli")[[".__NAMESPACE__."]]$spec[["version"]]) } has_cli <- function() { "cli" %in% loadedNamespaces() && cli_version() >= "3.3.0" } `%||%` <- function(l, r) if (is.null(l)) r else l bytes <- function(x) { nchar(x, type = "bytes") } process_call <- function(cond) { cond[c("call", "srcref", "procsrcref")] <- list( call = if (is.null(cond[["call"]])) { NULL } else if (is.character(cond[["call"]])) { cond[["call"]] } else { deparse(cond[["call"]], nlines = 2) }, srcref = NULL, procsrcref = get_srcref(cond[["call"]], cond$procsrcref %||% cond$srcref) ) cond } get_srcref <- function(call, srcref = NULL) { ref <- srcref %||% utils::getSrcref(call) if (is.null(ref)) return(NULL) if (inherits(ref, "processed_srcref")) return(ref) file <- utils::getSrcFilename(ref, full.names = TRUE)[1] if (is.na(file)) file <- "" line <- utils::getSrcLocation(ref) %||% "" col <- utils::getSrcLocation(ref, which = "column") %||% "" structure( list(file = file, line = line, col = col), class = "processed_srcref" ) } 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() } } no_sink <- function() { sink.number() == 0 && sink.number("message") == 2 } rstudio_stdout <- function() { rstudio <- rstudio_detect() rstudio$type %in% c( "rstudio_console", "rstudio_console_starting", "rstudio_build_pane", "rstudio_job", "rstudio_render_pane" ) } default_output <- function() { if ((is_interactive() || rstudio_stdout()) && no_sink()) { stdout() } else { stderr() } } onload_hook <- function() { reg_env <- Sys.getenv("R_LIB_ERROR_REGISTER_PRINT_METHODS", "TRUE") if (tolower(reg_env) != "false") { registerS3method("format", "rlib_error_3_0", format_error, baseenv()) registerS3method("format", "rlib_trace_3_0", format_trace, baseenv()) registerS3method("print", "rlib_error_3_0", print_error, baseenv()) registerS3method("print", "rlib_trace_3_0", print_trace, baseenv()) registerS3method("conditionMessage", "rlib_error_3_0", cnd_message, baseenv()) } } native_name <- function(x) { if (inherits(x, "NativeSymbolInfo")) { x$name } else { format(x) } } # There is no format() for 'name' in R 3.6.x and before format_name <- function(x) { if (is.name(x)) { as.character(x) } else { format(x) } } # -- public API -------------------------------------------------------- err_env <- environment() parent.env(err_env) <- baseenv() structure( list( .internal = err_env, new_cond = new_cond, new_error = new_error, throw = throw, throw_error = throw_error, chain_error = chain_error, chain_call = chain_call, chain_clean_call = chain_clean_call, add_trace_back = add_trace_back, process_call = process_call, onload_hook = onload_hook, is_interactive = is_interactive, format = list( advice = format_advice, call = format_call, class = format_class, error = format_error, error_heading = format_error_heading, header_line = format_header_line, srcref = format_srcref, trace = format_trace ) ), 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 throw_error <- err$throw_error chain_error <- err$chain_error chain_call <- err$chain_call chain_clean_call <- err$chain_clean_call callr/R/processx-forward.R0000644000176200001440000000025414143453135015207 0ustar liggesusers #' @importFrom processx run #' @export processx::run #' @importFrom processx process #' @export processx::process #' @importFrom processx poll #' @export processx::poll callr/R/run.R0000644000176200001440000000176714326531743012522 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.R0000644000176200001440000000516214143453135013617 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.R0000644000176200001440000001160614326530060012623 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/aaa-rstudio-detect.R0000644000176200001440000001762414564620746015402 0ustar liggesusers rstudio <- local({ standalone_env <- environment() parent.env(standalone_env) <- baseenv() # -- Collect data ------------------------------------------------------ data <- NULL get_data <- function() { envs <- c( "R_BROWSER", "R_PDFVIEWER", "RSTUDIO", "RSTUDIO_TERM", "RSTUDIO_CLI_HYPERLINKS", "RSTUDIO_CONSOLE_COLOR", "RSTUDIOAPI_IPC_REQUESTS_FILE", "XPC_SERVICE_NAME", "ASCIICAST") d <- list( pid = Sys.getpid(), envs = Sys.getenv(envs), api = tryCatch( asNamespace("rstudioapi")$isAvailable(), error = function(err) FALSE ), tty = isatty(stdin()), gui = .Platform$GUI, args = commandArgs(), search = search() ) d$ver <- if (d$api) asNamespace("rstudioapi")$getVersion() d$desktop <- if (d$api) asNamespace("rstudioapi")$versionInfo()$mode d } # -- Auto-detect environment ------------------------------------------- is_rstudio <- function() { Sys.getenv("RSTUDIO") == "1" } detect <- function(clear_cache = FALSE) { # Check this up front, in case we are in a testthat 3e test block. # We cannot cache this, because we might be in RStudio in reality. if (!is_rstudio()) { return(get_caps(type = "not_rstudio")) } # Cached? if (clear_cache) data <<- NULL if (!is.null(data)) return(get_caps(data)) if ((rspid <- Sys.getenv("RSTUDIO_SESSION_PID")) != "" && any(c("ps", "cli") %in% loadedNamespaces())) { detect_new(rspid, clear_cache) } else { detect_old(clear_cache) } } get_parentpid <- function() { if ("cli" %in% loadedNamespaces()) { asNamespace("cli")$get_ppid() } else { ps::ps_ppid() } } detect_new <- function(rspid, clear_cache) { mypid <- Sys.getpid() new <- get_data() if (mypid == rspid) { return(get_caps(new, type = "rstudio_console")) } # need explicit namespace reference because we mess up the environment parentpid <- get_parentpid() pane <- Sys.getenv("RSTUDIO_CHILD_PROCESS_PANE") # this should not happen, but be defensive and fall back if (pane == "") return(detect_old(clear_cache)) # direct subprocess new$type <- if (rspid == parentpid) { if (pane == "job") { "rstudio_job" } else if (pane == "build") { "rstudio_build_pane" } else if (pane == "render") { "rstudio_render_pane" } else if (pane == "terminal" && new$tty && new$envs["ASCIICAST"] != "true") { # not possible, because there is a shell in between, just in case "rstudio_terminal" } else { # don't know what kind of direct subprocess "rstudio_subprocess" } } else if (pane == "terminal" && new$tty && new$envs[["ASCIICAST"]] != "true") { # not a direct subprocess, so check other criteria as well "rstudio_terminal" } else { # don't know what kind of subprocess "rstudio_subprocess" } get_caps(new) } detect_old <- function(clear_cache = FALSE) { # Cache unless told otherwise cache <- TRUE new <- get_data() new$type <- if (new$envs[["RSTUDIO"]] != "1") { # 1. Not RStudio at all "not_rstudio" } else if (new$gui == "RStudio" && new$api) { # 2. RStudio console, properly initialized "rstudio_console" } else if (! new$api && basename(new$args[1]) == "RStudio") { # 3. RStudio console, initializing cache <- FALSE "rstudio_console_starting" } else if (new$gui == "Rgui") { # Still not RStudio, but Rgui that was started from RStudio "not_rstudio" } else if (new$tty && new$envs[["ASCIICAST"]] != "true") { # 4. R in the RStudio terminal # This could also be a subprocess of the console or build pane # with a pseudo-terminal. There isn't really a way to rule that # out, without inspecting some process data with ps::ps_*(). # At least we rule out asciicast "rstudio_terminal" } else if (! new$tty && new$envs[["RSTUDIO_TERM"]] == "" && new$envs[["R_BROWSER"]] == "false" && new$envs[["R_PDFVIEWER"]] == "false" && is_build_pane_command(new$args)) { # 5. R in the RStudio build pane # https://github.com/rstudio/rstudio/blob/master/src/cpp/session/ # modules/build/SessionBuild.cpp#L231-L240 "rstudio_build_pane" } else if (new$envs[["RSTUDIOAPI_IPC_REQUESTS_FILE"]] != "" && grepl("rstudio", new$envs[["XPC_SERVICE_NAME"]])) { # RStudio job, XPC_SERVICE_NAME=0 in the subprocess of a job # process. Hopefully this is reliable. "rstudio_job" } else if (new$envs[["RSTUDIOAPI_IPC_REQUESTS_FILE"]] != "" && any(grepl("SourceWithProgress.R", new$args))) { # Or we can check SourceWithProgress.R in the command line, see # https://github.com/r-lib/cli/issues/367 "rstudio_job" } else { # Otherwise it is a subprocess of the console, terminal or # build pane, and it is hard to say which, so we do not try. "rstudio_subprocess" } installing <- Sys.getenv("R_PACKAGE_DIR", "") if (cache && installing == "") data <<- new get_caps(new) } is_build_pane_command <- function(args) { cmd <- gsub("[\"']", "", args[[length(args)]], useBytes = TRUE) calls <- c( "devtools::build", "devtools::test", "devtools::check", "testthat::test_file" ) any(vapply(calls, grepl, logical(1), cmd)) } # -- Capabilities ------------------------------------------------------ caps <- list() caps$not_rstudio <- function(data) { list( type = "not_rstudio", dynamic_tty = FALSE, ansi_tty = FALSE, ansi_color = FALSE, num_colors = 1L, hyperlink = FALSE ) } caps$rstudio_console <- function(data) { list( type = "rstudio_console", dynamic_tty = TRUE, ansi_tty = FALSE, ansi_color = data$envs[["RSTUDIO_CONSOLE_COLOR"]] != "", num_colors = as.integer(data$envs[["RSTUDIO_CONSOLE_COLOR"]]), hyperlink = data$envs[["RSTUDIO_CLI_HYPERLINKS"]] != "" ) } caps$rstudio_console_starting <- function(data) { res <- caps$rstudio_console(data) res$type <- "rstudio_console_starting" res } caps$rstudio_terminal <- function(data) { list( type = "rstudio_terminal", dynamic_tty = TRUE, ansi_tty = FALSE, ansi_color = FALSE, num_colors = 1L, hyperlink = FALSE ) } caps$rstudio_build_pane <- function(data) { list( type = "rstudio_build_pane", dynamic_tty = TRUE, ansi_tty = FALSE, ansi_color = data$envs[["RSTUDIO_CONSOLE_COLOR"]] != "", num_colors = as.integer(data$envs[["RSTUDIO_CONSOLE_COLOR"]]), hyperlink = data$envs[["RSTUDIO_CLI_HYPERLINKS"]] != "" ) } caps$rstudio_job <- function(data) { list( type = "rstudio_job", dynamic_tty = FALSE, ansi_tty = FALSE, ansi_color = data$envs[["RSTUDIO_CONSOLE_COLOR"]] != "", num_colors = as.integer(data$envs[["RSTUDIO_CONSOLE_COLOR"]]), hyperlink = data$envs[["RSTUDIO_CLI_HYPERLINKS"]] != "" ) } caps$rstudio_render_pane <- function(data) { list( type = "rstudio_render_pane", dynamic_tty = TRUE, ansi_tty = FALSE, ansi_color = FALSE, num_colors = 1L, hyperlink = data$envs[["RSTUDIO_CLI_HYPERLINKS"]] != "" ) } caps$rstudio_subprocess <- function(data) { list( type = "rstudio_subprocess", dynamic_tty = FALSE, ansi_tty = FALSE, ansi_color = FALSE, num_colors = 1L, hyperlink = FALSE ) } get_caps <- function(data, type = data$type) caps[[type]](data) structure( list( .internal = standalone_env, is_rstudio = is_rstudio, detect = detect ), class = c("standalone_rstudio_detect", "standalone") ) }) callr/R/hook.R0000644000176200001440000000627714326534733012661 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") } user_hooks_env <- new.env(parent = emptyenv()) #' Add a user hook to be executed before launching an R subprocess #' #' This function allows users of `callr` to specify functions that get invoked #' whenever an R session is launched. The function can modify the environment #' variables and command line arguments. #' #' The prototype of the hook function is `function (options)`, and it is #' expected to return the modified `options`. #' @param ... Named argument specifying a hook function to add, or `NULL` to #' delete the named hook. #' @return `add_hook` is called for its side-effects. #' @export add_hook <- function(...) { args <- list(...) if (length(args) != 1L) { stop("More than one argument passed to `add_hook`") } name <- names(args) if (is.null(name)) { stop("Argument passed to `add_hook` must be named") } hook <- args[[1]] if (is.null(hook)) { rm(list = name, envir = user_hooks_env) } else { assign(name, match.fun(hook), envir = user_hooks_env) } invisible() } call_user_hooks <- function(options) { for (name in names(user_hooks_env)) { hook <- user_hooks_env[[name]] options <- tryCatch(hook(options), error = function (e) options) } options } callr/R/rscript.R0000644000176200001440000000630214521175632013370 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 `Rscript` 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.R0000644000176200001440000002251214330454541012627 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). #' IF `NULL` (the default), then standard output is not returned, but #' it is recorded and included in the error object if an error happens. #' @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. #' IF `NULL` (the default), then standard output is not returned, but #' it is recorded and included in the error object if an error happens. #' @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. #' #' @section Transporting objects: #' #' `func` and `args` are copied to the child process by first serializing them #' into a temporary file using [saveRDS()] and then loading them back into the #' child session using [readRDS()]. The same strategy is used to copy the result #' of calling `func(args)` to the main session. Note that some objects, notably #' those with `externalptr` type, won't work as expected after being #' saved to a file and loaded back. #' #' For performance reasons `compress=FALSE` is used when serializing with #' [saveRDS()], this can be disabled by setting #' `options(callr.compress_transport = TRUE)`. #' #' @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.R0000644000176200001440000000260514521175632013277 0ustar liggesusers ## 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)) { dir.create(dirname(fn), recursive = TRUE) 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) { hash <- substr(clients[[aa]]$md5, 1, 7) # Filename must be `client.ext` so that `dyn.load()` can find # the init function file.path( tempdir(), "callr", sub("arch-", "", aa), # Might be empty hash, paste0("client", .Platform$dynlib.ext) ) }, character(1)) } .onUnload <- function(libpath) { unlink( normalizePath(c(sofiles, env_file), mustWork = FALSE), recursive = TRUE, force = TRUE ) } callr/R/rcmd-bg.R0000644000176200001440000000201714143453135013211 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.md0000644000176200001440000002126714564626246012474 0ustar liggesusers# callr 3.7.5 * No changes. # callr 3.7.4 * The `r_session$get_running_time()` method now returns the correct values, as documented (#241, @djnavarro). * callr now uses fully qualified function calls in the subprocess to avoid interference with functions defined in the global environment. I.e. `base::stderr()` instead of `stderr()`. Closes #246. # callr 3.7.3 * Errors from callr now include the standard output (in `$stdout`) and standard error (in `stderr`) again. The standard output and error are also printed on error in non-interactive sessions, and a note is printed about them in interactive sessions. # callr 3.7.2 * New function `add_hook()` to hook into the callr process startup and options. This is for experts and it is also currently experimental (#203, @klmr). # callr 3.7.1 * When copying existing startup files, an additional newline is appended to protect against a missing newline at the end of the file. This would cause R ignore that line (#205). * Serialization of objects passed between sessions now uses `compress=FALSE` by default. The default can be changed by setting the `callr.compress_transport` option (#223, @dfalbel). * We have revamped callr's error objects, with lots of improvements to the output. # 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/MD50000644000176200001440000001713714564733763011712 0ustar liggesusers12cb0f072b25ac0cd8e722d573041813 *DESCRIPTION 309b8cc125bf463af3cd744d0debf1c8 *LICENSE fe0e4014d3c079944eebeab7061d3271 *NAMESPACE e336376604918f8c1769a2bf508987ad *NEWS.md aef547478e0af0d573369209fc85039b *R/aaa-rstudio-detect.R 829ab98ce65cfac352a61f73fc908462 *R/callr-package.R 7b4246baa698319c22353ad73253651e *R/check.R 13218bc214f0cfc41da3b4cac8c6cc78 *R/error.R 125e493fe5682e24cb8d2b5ffacf9f30 *R/errors.R 23b4e6cec26f88ae8fa243470ef73da8 *R/eval-bg.R 8c38484053ccd5332beb1e26edcf7c17 *R/eval.R ce18cf1b3f3fbb33ef3b23a3ddae7216 *R/hook.R 104120cd495c69d9e7e257c59d180761 *R/load-client.R 88dea3f0167bb3245c8893be49656c3b *R/options.R 9e31b29f126ec37b30b875195eb9ef81 *R/package.R f69a6206127612a37bee50e104b02f23 *R/presets.R 57e46eb271bbc3020eb4c7bf45fa5625 *R/processx-forward.R b25c8314bed7881c36b13b18d6e64daf *R/r-process.R 5584dd4508c797a34d2395544f73e065 *R/r-session.R 41320594a9a458d32e823ab1fac2bb0f *R/rcmd-bg.R ae4070b19c1a01b9bbf4e77e00b76f77 *R/rcmd-process.R e4c3f71ca6e59932c0dcef14825aa776 *R/rcmd.R 85c793e664439cb947d19376e7729cfb *R/result.R e50f6d80174e80cbbe0fb2839efc59a7 *R/rscript.R fbd5775d259e9d8aa802aa9f873c19fa *R/run.R acbcde4100ae54dcfc541310dfe96374 *R/script.R f8e8c0b762d6e9b762b855e098abc9e5 *R/setup.R 9c1278cef1060b35f91e0b412f795e77 *R/utils.R 37c60ca33fae6e1be2e354e5051be0b1 *R/x-client.R f267d622734d106a00fb43ab0258b555 *README.md f9deeb47aede7111c00b55b87f342942 *inst/COPYRIGHTS e485d467e02d2cb0f601de9d3781918c *inst/WORDLIST f016ae44474fd10d44cef6ccaf294913 *inst/developer-notes.md d0d55ccc8b2bfc5b476fe789cbacc1d3 *man/add_hook.Rd dfd696b3503a64fa4089feaa6192234e *man/callr-package.Rd 66b8df108030a8f7d6857033105fc6ed *man/convert_and_check_my_args.Rd 88d3e9765ac8330eefe6b15b68b3ee75 *man/default_repos.Rd 167c58ceff87da95dbc8a611119ff06d *man/figures/bg-dark.svg 1606c742beb45a3463692046fd4aed61 *man/figures/bg-methods-dark.svg 2c0ca808c41f1de6c390f5576821e0db *man/figures/bg-methods.svg 7f4530eadadf07cdda55188550855111 *man/figures/bg.svg e6e196b817a7c8f88e15710c2369549f *man/figures/error1-dark.svg 23c48f04fbf1b7fb42caa02465342ba9 *man/figures/error1.svg 7d69702958431bc33194ab7ad4e9739d *man/figures/error2-2-dark.svg 6532fefab408800df2415f6931a5f0fa *man/figures/error2-2.svg fce2f4b5a98ed800aeb8d3bf77cf65c4 *man/figures/io-2-dark.svg 7fae5634632feab13582a3a209421ba3 *man/figures/io-2.svg 3c2f56328d44bfe39b7b79c808b4998a *man/figures/io-dark.svg f26a5eeace4935ffea611f9edb51a2ff *man/figures/io.svg 52ebbf4f45b974dea0dfbe93c65a8080 *man/figures/packages-dark.svg fb715e90c225315002b94b93405cf4ee *man/figures/packages.svg f9c74e8fd70555fd1eb48fc6beafd6ea *man/figures/passargsfail-dark.svg d11324e6e5498f90badc7c2a6a96c06c *man/figures/passargsfail.svg 43dc3bbd1b5efd88193dc300ae1d73a8 *man/figures/passargsok-dark.svg a2558c795eb3f386fddba742d5245e2d *man/figures/passargsok.svg 9edddc410b4a6935e27ddc6f98cc3e3a *man/figures/poll-2-dark.svg 7df1732a0e2aaba03415b230a3f80090 *man/figures/poll-2.svg 2be9bc59139c3c4b95779d8464e8fb1f *man/figures/poll-3-dark.svg ae737513f943f15d23a7989f0ab52f8c *man/figures/poll-3.svg 9a9616520d8171d4ad76ad7184fa3b98 *man/figures/poll-4-dark.svg da6dd7290a93fc44292598f8c545bffa *man/figures/poll-4.svg fa5c7c9e81630a201939f0fdefb4858f *man/figures/poll-dark.svg d9f63e180183191cad920f8cebfb2c4b *man/figures/poll.svg 465f34e6317b10c240538a7289be971b *man/figures/rcmd-dark.svg d751a52fc71142d5aefb00a4bf815253 *man/figures/rcmd.svg 84d5871ea0b8247365163729b812a479 *man/figures/rsession-4-dark.svg 50d20af7732c8374cd1dbd711413582a *man/figures/rsession-4.svg 8f851231db102b1e3a189fa9d9b1919b *man/figures/rsession-5-dark.svg 50c5dcaae1a71080c042c91eb2979fc6 *man/figures/rsession-5.svg 15e3b4d10d6ca4a0c6ecf43de1e2fb2d *man/figures/rsession-dark.svg 44edf45aecd7d2d70d2b3c40b1e52ffc *man/figures/rsession.svg 9fab2a9a2b91296a02f3c3c8bc173299 *man/figures/rsession2-2-dark.svg 188df504c95e078a750e1c52646bb539 *man/figures/rsession2-2.svg 82e50cadc572985ab9cfdde9c2237c3d *man/figures/rsession2-dark.svg 680da99bc1e94dbd5e35e32991574913 *man/figures/rsession2.svg 89b82405fed52d9302feb03ff0b64bfb *man/figures/simple-dark.svg b6ed673c8f64caa770f92a266901de6b *man/figures/simple.svg 6c99e2e262e48d066c7851173977774e *man/get_result.Rd c59bb545d1ea85db092eb96346c639d2 *man/new_callr_crash_error.Rd 9beb2a683112e8ae87a3b701d1405451 *man/r.Rd da6bf6e8537c4fd5bc2bc06d58f18117 *man/r_bg.Rd 04d7d6f218527483cf1abf093c19f223 *man/r_copycat.Rd ad4dccfd90a88d709dc02c6635d880e3 *man/r_process.Rd 0ba68b24099710b8a7257b172cfb8d26 *man/r_process_options.Rd 704ef0323afe303d92163fc19aad2e59 *man/r_session.Rd ecb412596faac3a5c89ba4645b8b44d9 *man/r_session_debug.Rd 105cb88a4bed2f5248e9c78574692fb1 *man/r_session_options.Rd b107bfa192b680e15110a6b36598d12a *man/r_vanilla.Rd 4c667f799f67a597039e34b16a50b7bc *man/rcmd.Rd 0c374e56db0d30c0db408dd65c475528 *man/rcmd_bg.Rd 752091f0ab22fa29ad5c93dcda3118bd *man/rcmd_copycat.Rd d26bf40fb2dfd273517821f97e3c4722 *man/rcmd_process.Rd cbbf751d3ff24c9e94b4b022650a5d26 *man/rcmd_process_options.Rd 35170cbc634370608fe653844ea34bbe *man/rcmd_safe_env.Rd 0759420e2f99421950fd3f48603f4847 *man/reexports.Rd c3e9aabbd570a7825225dd29273f0dae *man/roxygen/meta.R c275e1f4d0b4106139458695ba631501 *man/rscript.Rd f6afdf401f3321f846e7bc6ad651b151 *man/rscript_process.Rd 6725f7b56f6004b3085ff7c8fa7445a9 *man/rscript_process_options.Rd 343cd45d6ef35b84073cfb5fb4863723 *man/supported_archs.Rd 9ebccaed611475d811ff7d08ce689c7f *tests/testthat.R 3834367fa623e971e97ad54cbcb849a8 *tests/testthat/_snaps/error.md 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 055a129e59e6aeccd83f88de046fc513 *tests/testthat/helper.R 59a89f2db8a99102c0c0cc08c867440c *tests/testthat/test-archs.R 506a267d9a0d83d2dc78fd66c9807661 *tests/testthat/test-bugs.R 8f89aa9df7e80e36db2ffc1d94b00a3d *tests/testthat/test-callback.R 0d951801ed563dc79236407252228ac8 *tests/testthat/test-clean-subprocess.R a9eeed84c286a1c35d41388fda716ebc *tests/testthat/test-error.R 4e0cb16db3bfc0e41cd3d8cc53356807 *tests/testthat/test-eval.R e5726cb1a08ae6e1f602b3002168f41c *tests/testthat/test-function-env.R 7d68aae5d8955b6a7549ec22b7de90f8 *tests/testthat/test-hook.R 43a44ddbdfb7e1ba5446e1479f4af176 *tests/testthat/test-libpath.R 47b3ccb9bf9fd2bd07bd74dc777a12f7 *tests/testthat/test-load-client.R 3e9664c4c3b0826d6699f81f09b70210 *tests/testthat/test-messages.R 44f80926a7667ad35e1d7ab34ab79e94 *tests/testthat/test-options.R 535e75f34c753894a68dcb9708856d92 *tests/testthat/test-package.R 25412bd4a08acfa6f5e61af4779b8901 *tests/testthat/test-presets.R 5aed223de9252a5e8bf9a8bb90bdd9e6 *tests/testthat/test-quit.R 9fc5a2000a50efa4d99b10228d10ddc9 *tests/testthat/test-r-bg.R f4aaf0b40c5f0aba84fb6669702cf00e *tests/testthat/test-r-process.R 149088623b1352c3b78990e1602e443d *tests/testthat/test-r-session-messages.R 07305c12c9d1afb818806e5967568dc8 *tests/testthat/test-r-session.R 20ea4fb4d11e30f290950ede3c5acb9e *tests/testthat/test-rcmd-bg.R edf260c94673dac51f60a0ffc02ec314 *tests/testthat/test-rcmd-process.R ff16678f42d7f049b570d77f131b65c8 *tests/testthat/test-rcmd.R 626d3950d525e9465f59033517a5cc8e *tests/testthat/test-rscript.R 269bcddf25b496214158d8dff7a432a1 *tests/testthat/test-spelling.R 5a98c1ec368fda43302a00d5bbc39a1c *tests/testthat/test-timeout.R 68941d3de383efe10cc1286bd5310255 *tests/testthat/test-utils.R callr/inst/0000755000176200001440000000000014564626344012342 5ustar liggesuserscallr/inst/COPYRIGHTS0000644000176200001440000000021614564622315013751 0ustar liggesusers(c) 2015-2016 Ascent Digital Services (formerly Mango Solutions) (c) 2017 Gábor Csárdi (c) 2017-2024 Posit Software, PBC (formerly RStudio) callr/inst/developer-notes.md0000644000176200001440000000372214143453135015770 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/WORDLIST0000644000176200001440000000034214521175322013517 0ustar liggesusersCMD CTRL Codecov ESC Eval Finalizer ORCID PBC REPL callr's cli cliapp cloneable eval finalizer funder igraph interruptible keyring lifecycle macOS packrat pkgdown processx renv stderr stdout subcommand subprocess subprocesses