Reputation: 6812
In most scripting languages (e.g. Ruby, Python, etc.) the package manager (e.g. gem, pip, etc.) can install scripts as executables and link them to a directory referenced in the PATH variable (e.g. /usr/local/bin). This turns those executable scripts into shell commands that the user can run in an standalone manner and outside the programming interface.
I wonder if there is such a possibility in R as well. Given that R uses standard Makefiles, I guess there must be a way to do so, albeit a non-standard one. I already know we can read command line arguments in a R script using the docopt
package. But is there a way to install the script as an executable upon the installation of a package?
It would be great to have a lead on this topic, but a single working example from CRAN would suffice as well.
Upvotes: 7
Views: 1353
Reputation: 1505
I had a similar goal, this was the solution I settled on. I added a function to my R package that goes through and (verbosely) creates symlinks from ~/.local/bin
to each script in my packages exec
folder.
Other sensible default locations might be ~/.local/lib/R/bin
or ~/bin
, but I like ~/.local/bin
the best.
So after installing the package, I direct users to run
Rscript -e 'mypackage::install_executable_scripts()'
#' @export
install_executable_scripts <- function(into = "~/.local/bin", overwrite = FALSE) {
scripts <- dir(system.file("exec", package = "mypackage"),
full.names = TRUE)
if (!dir.exists(into)) dir.create(into)
into <- normalizePath(into)
dests <- file.path(normalizePath(into), basename(scripts))
if (any(already_exist <- file.exists(dests))) {
if (overwrite) {
to_del <- dests[already_exist]
cli::cat_bullet("Deleting existing file: ", to_del,
bullet_col = "red")
unlink(to_del)
} else {
cli::cat_bullet(sprintf(
"Skipping script '%s' because a file by that name already exists at the destination",
basename(scripts[already_exist])))
scripts <- scripts[!already_exist]
dests <- dests[!already_exist]
}
}
if (length(scripts)) {
file.symlink(scripts, dests)
cli::cat_line("Created symlinks:")
cli::cat_bullet(dests, " ->\n ", scripts, bullet_col = "green")
} else
cli::cat_line("Nothing installed")
PATHS <- normalizePath(strsplit(Sys.getenv("PATH"), ":", fixed = TRUE)[[1]],
mustWork = FALSE)
if(!into %in% PATHS)
warning(sprintf("destination '%s' is not on the PATH", into))
}
Upvotes: 5
Reputation: 368241
Short (and very sad) answer: You cannot. But read on.
Reasoning: R will only ever write package content to its own .libPaths()
directory (or the first in case several are given), or a directory given by the user.
So, say, /usr/local/bin/
is simply out of reach. That is a defensible strategy.
It is also rather sad--I wrote littler (also CRAN page) for exactly this purpose: executable R scripts. And we have dozens of those at work called from cron
jobs. So what do we do? A one-time soft-link from the scripts/
subdirectory of the package containing the script to /usr/local/bin
. At package upgrades, the link persists as a soft-link.
And that's what I do for e.g. all the examples shipping with littler and more from other packages. Many of them use docopt too.
Upvotes: 5