cboettig
cboettig

Reputation: 12707

how do I make install.packages return an error if an R package cannot be installed?

install.packages() returns a warning if a package cannot be installed (for instance, if it is unavailable); for example:

install.packages("notapackage")

(EDIT: I'd like to throw an error regardless of the reason the package cannot be installed, not just this example case of a missing package).

I am running the install.packages command in a script, and I would like it to trigger a proper error and exit execution. I don't see an obvious option inside install.packages for handling this behavior. Any suggestions?

Upvotes: 17

Views: 5898

Answers (6)

dpritch
dpritch

Reputation: 1279

I've added another version that essentially tweaks @mnl's solution (which is in turn inspired by @dirk-eddelbuettel's install_packages2 function) to collect and report all of the package build failures.

# `install.packages` unfortunately does not throw an error if package
# installation fails but rather a warning. So we check the warnings and promote
# the appropriate ones to errors. The regex conditions are taken from the
# `install_packages2` function in
# https://github.com/eddelbuettel/littler/blob/master/inst/examples/install2.r
warns <- character(0L)
withCallingHandlers(
  expr = {
    install.packages(c("pk1", "pkg2", "etc"))
  }, warning = function(w) {
    catch <- (
      grepl("download of package .* failed", w$message)
      || grepl("(dependenc|package).*(is|are) not available", w$message)
      || grepl("installation of package.*had non-zero exit status", w$message)
      || grepl("installation of one or more packages failed", w$message)
    )
    if (catch) {
      warns <<- c(warns, w$message)
      invokeRestart("muffleWarning")
    }
  }
)
if (length(warns) >= 1L) {
  msg <- paste(warns, collapse = "\n")
  stop(msg)
}

Upvotes: 0

Mnl
Mnl

Reputation: 997

try to install then look for warnings to stop the execution and return an error. also calls library() , just in case !

 install_or_fail <- function(package_name){ 

   tryCatch({install.packages(package_name, dependencies = TRUE) 
         library(package_name)}, 
         error = function(e){ print(e) }, 
         warning = function(w){
           catch <-
             grepl("download of package .* failed", w$message) ||
             grepl("(dependenc|package).*(is|are) not available", w$message) ||
             grepl("installation of package.*had non-zero exit status", w$message) ||
             grepl("installation of one or more packages failed", w$message)
           if(catch){ print(w$message)
             stop(paste("installation failed for:",package_name ))}}
         )

 }

inspired by : https://github.com/eddelbuettel/littler/blob/master/inst/examples/install2.r

Upvotes: 3

Aphoid
Aphoid

Reputation: 401

Building off of Cameron Kerr's answer, here's another solution that would work in a Dockerfile (or at a unix command line) without needing to add an additional file/layer:

RUN R -e " \
   install_packages_or_die <- function (pkgs, repos='http://cran.rstudio.com/') { \
   for (l in pkgs) {  install.packages(l, dependencies=TRUE, repos=repos); \
       if ( ! library(l, character.only=TRUE, logical.return=TRUE) ) { \
          stop ('ERROR: failed installing requested package \'',l,'\'') } } } ; \
   install_packages_or_die (pkgs= c('mime')); "

RUN R -e " \
   install_packages_or_die <- function (pkgs, repos='http://cran.rstudio.com/') { \
   for (l in pkgs) {  install.packages(l, dependencies=TRUE, repos=repos); \
       if ( ! library(l, character.only=TRUE, logical.return=TRUE) ) { \
          stop ('ERROR: failed installing requested package \'',l,'\'') } } } ; \
   install_packages_or_die (pkgs= c('packagedoesnotexist')); "

Note: sometimes packages install dependencies after the requested package failed, and you'll still have to search the log to see what the actual error was.

Upvotes: 0

Cameron Kerr
Cameron Kerr

Reputation: 1875

Having just solved this for myself, I noted that install.packages() results in calling library(thepackage) to see it its usable.

I made a R script that installs the given packages, uses library on each to see if its loadable, and if not calls quit with a non-0 status.

I call it install_packages_or_die.R

#!/usr/bin/env Rscript

packages = commandArgs(trailingOnly=TRUE)

for (l in packages) {

    install.packages(l, dependencies=TRUE, repos='https://cran.rstudio.com/');

    if ( ! library(l, character.only=TRUE, logical.return=TRUE) ) {
        quit(status=1, save='no')
    }
}

Use it like this in your Dockerfile. I'm grouping them in useful chunks to try and make reasonable use of Docker build cache.

ADD install_packages_or_die.R /

RUN Rscript --no-save install_packages_or_die.R profvis devtools memoise

RUN Rscript --no-save install_packages_or_die.R memoise nosuchpackage

Disclaimer: I do not consider myself an R programmer at all currently, so there may very well be better ways of doing this.

Upvotes: 16

cboettig
cboettig

Reputation: 12707

The R function WithCallingHandlers() lets us handle any warnings with an explicitly defined function. For instance, we can tell the R to stop if it receives any warnings (and return the warning message as an error message).

withCallingHandlers(install.packages("notapackage"),
                    warning = function(w) stop(w))

I am guessing that this is not ideal, since presumably a package could install successfully but still throw a warning; but haven't encountered that case. As Dirk suggests, testing require for the package is probably more robust.

Upvotes: 6

Dirk is no longer here
Dirk is no longer here

Reputation: 368399

Expanding on the quick comment:

R> AP <- available.packages()
R> "notapackage" %in% AP[,1]      # expected to yield FALSE
[1] FALSE
R> "digest" %in% AP[,1]           # whereas this should be TRUE
[1] TRUE
R> 

Upvotes: 0

Related Questions