I recently found out that when install.packages() fails, it only returns a warning and not an error, i.e., the exit code is 0.
R -q -e 'install.packages("thisdoesnotexist!!!")'
> install.packages("thisdoesnotexist!!!")
Installing package into ‘/home/dtang/R/x86_64-pc-linux-gnu-library/4.3’
(as ‘lib’ is unspecified)
Warning message:
package ‘thisdoesnotexist!!!’ is not available for this version of R
A version of this package for your version of R might be available elsewhere,
see the ideas at
https://cran.r-project.org/doc/manuals/r-patched/R-admin.html#Installing-packages
echo $?
# 0
The exit code is still zero when an existing package fails to install; for example:
R -q -e 'install.packages("ggstatsplot")'
snipped
ERROR: configuration failed for package ‘nloptr’
* removing ‘/home/dtang/R/x86_64-pc-linux-gnu-library/4.3/nloptr’
ERROR: dependency ‘nloptr’ is not available for package ‘lme4’
* removing ‘/home/dtang/R/x86_64-pc-linux-gnu-library/4.3/lme4’
ERROR: dependency ‘lme4’ is not available for package ‘pbkrtest’
* removing ‘/home/dtang/R/x86_64-pc-linux-gnu-library/4.3/pbkrtest’
ERROR: dependency ‘lme4’ is not available for package ‘lmerTest’
* removing ‘/home/dtang/R/x86_64-pc-linux-gnu-library/4.3/lmerTest’
ERROR: dependencies ‘pbkrtest’, ‘lme4’ are not available for package ‘car’
* removing ‘/home/dtang/R/x86_64-pc-linux-gnu-library/4.3/car’
ERROR: dependencies ‘lme4’, ‘pbkrtest’, ‘lmerTest’, ‘car’ are not available for package ‘afex’
* removing ‘/home/dtang/R/x86_64-pc-linux-gnu-library/4.3/afex’
ERROR: dependencies ‘afex’, ‘PMCMRplus’ are not available for package ‘statsExpressions’
* removing ‘/home/dtang/R/x86_64-pc-linux-gnu-library/4.3/statsExpressions’
ERROR: dependency ‘statsExpressions’ is not available for package ‘ggstatsplot’
* removing ‘/home/dtang/R/x86_64-pc-linux-gnu-library/4.3/ggstatsplot’
The downloaded source packages are in
‘/tmp/Rtmpy3R57N/downloaded_packages’
There were 11 warnings (use warnings() to see them)
echo $?
# 0
If you install packages interactively, in an R prompt or using RStudio, you would see that the install failed. But when building images for containers, we rely on exit codes >0.
For Singularity, one easy way is to try to load the library in the test section.
Another way, as suggested by a SO user that mimics the testing approach with Singularity, is to write a function that tries to install a package and then checks whether the package can be loaded.
#!/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')
}
}
The code above can be simplified as pointed out by Yihui:
install.packages(packages, dependencies=TRUE, repos=’https://cran.rstudio.com/’)
for (l in packages) library(l, character.only=TRUE)
I also came up with an approach using tryCatch, which turns all warnings into errors. To demonstrate, I'll show a failed install without tryCatch and the exit code.
R -q -e 'install.packages("beepr", repos = "https://cran.ism.ac.jp/")'
> install.packages("beepr", repos = "https://cran.ism.ac.jp/")
Installing package into ‘/home/dtang/R/x86_64-pc-linux-gnu-library/4.3’
(as ‘lib’ is unspecified)
trying URL 'https://cran.ism.ac.jp/src/contrib/beepr_1.3.tar.gz'
Content type 'application/x-gzip' length 922244 bytes (900 KB)
==================================================
downloaded 900 KB
* installing *source* package ‘beepr’ ...
** package ‘beepr’ successfully unpacked and MD5 sums checked
** using staged installation
** R
** inst
** byte-compile and prepare package for lazy loading
Error in dyn.load(file, DLLpath = DLLpath, ...) :
unable to load shared object '/home/dtang/R/x86_64-pc-linux-gnu-library/4.3/audio/libs/audio.so':
/usr/lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.34' not found (required by /home/dtang/R/x86_64-pc-linux-gnu-library/4.3/audio/libs/audio.so)
Calls: <Anonymous> ... namespaceImport -> loadNamespace -> library.dynam -> dyn.load
Execution halted
ERROR: lazy loading failed for package ‘beepr’
* removing ‘/home/dtang/R/x86_64-pc-linux-gnu-library/4.3/beepr’
* restoring previous ‘/home/dtang/R/x86_64-pc-linux-gnu-library/4.3/beepr’
The downloaded source packages are in
‘/home/dtang/tmp/RtmpLHq2eK/downloaded_packages’
Warning message:
In install.packages("beepr", repos = "https://cran.ism.ac.jp/") :
installation of package ‘beepr’ had non-zero exit status
echo $?
# 0
Now with tryCatch.
R -q -e 'tryCatch(warning = function(x) stop(paste0("Warning detected: ", x$message)), install.packages("beepr", repos = "https://cran.ism.ac.jp"))'
> tryCatch(warning = function(x) stop(paste0("Warning detected: ", x$message)), install.packages("beepr", repos = "https://cran.ism.ac.jp"))
Installing package into ‘/home/dtang/R/x86_64-pc-linux-gnu-library/4.3’
(as ‘lib’ is unspecified)
trying URL 'https://cran.ism.ac.jp/src/contrib/beepr_1.3.tar.gz'
Content type 'application/x-gzip' length 922244 bytes (900 KB)
==================================================
downloaded 900 KB
* installing *source* package ‘beepr’ ...
** package ‘beepr’ successfully unpacked and MD5 sums checked
** using staged installation
** R
** inst
** byte-compile and prepare package for lazy loading
Error in dyn.load(file, DLLpath = DLLpath, ...) :
unable to load shared object '/home/dtang/R/x86_64-pc-linux-gnu-library/4.3/audio/libs/audio.so':
/usr/lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.34' not found (required by /home/dtang/R/x86_64-pc-linux-gnu-library/4.3/audio/libs/audio.so)
Calls: <Anonymous> ... namespaceImport -> loadNamespace -> library.dynam -> dyn.load
Execution halted
ERROR: lazy loading failed for package ‘beepr’
* removing ‘/home/dtang/R/x86_64-pc-linux-gnu-library/4.3/beepr’
* restoring previous ‘/home/dtang/R/x86_64-pc-linux-gnu-library/4.3/beepr’
Error in value[[3L]](cond) :
Warning detected: installation of package ‘beepr’ had non-zero exit status
Calls: tryCatch -> tryCatchList -> tryCatchOne -> <Anonymous>
Execution halted
echo $?
# 1
The tryCatch approach has the advantage that the installation stops immediately after a warning is caught. This is handy for packages like {ggstatsplot}, which has a lot of dependencies; this is because the installation continues trying to install all dependencies despite one of the dependencies failing to install.
R -q -e 'tryCatch(warning = function(x) stop(paste0("Warning detected: ", x$message)), install.packages("ggstatsplot", repos = "https://cran.ism.ac.jp"))'
snipped
configure: error: Header file gmp.h not found; maybe use --with-gmp-include=INCLUDE_PATH
ERROR: configuration failed for package ‘gmp’
* removing ‘/home/dtang/R/x86_64-pc-linux-gnu-library/4.3/gmp’
Error in value[[3L]](cond) :
Warning detected: installation of package ‘gmp’ had non-zero exit status
Calls: tryCatch -> tryCatchList -> tryCatchOne -> <Anonymous>
Execution halted
The installation immediately stops after the {gmp} dependency fails to install.
The downside of the tryCatch approach is that sometimes warnings are harmless. In those cases, the try-to-install and then try-to-load approach is safer.

This work is licensed under a Creative Commons
Attribution 4.0 International License.
if ( ! library(l, character.only=TRUE, logical.return=TRUE) ) {
quit(status=1, save=’no’)
}
That’s equivalent to
if ( ! require(l, character.only=TRUE) ) {
quit(status=1, save=’no’)
}
But I think you can simply do library(l, character.only = TRUE), which will throw an error and halt R if the package is not loadable. So the code
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’)
}
}
can be simplified to two lines:
install.packages(packages, dependencies=TRUE, repos=’https://cran.rstudio.com/’)
for (l in packages) library(l, character.only=TRUE)
BTW, thank you very much for your sponsorship!
Thanks for the simplified code snippet! And thank you for all the amazing packages! I was happy to hear that you found a new opportunity!