55

Is there a simple way to get a list of R package dependencies (all recursive dependencies) for a given package, without installing the package and it's dependencies? Something similar to a fake install in portupgrade or apt.

smci
  • 32,567
  • 20
  • 113
  • 146
Jonathan Lisic
  • 1,710
  • 1
  • 13
  • 21
  • 3
    Thanks, that would of saved me some time :), Since it isn't explicit in the documentation, an example for let's say ggplot would be dependsOnPkgs("ggplot2",installed=available.packages()) – Jonathan Lisic Feb 01 '13 at 17:18
  • If there is any helper function somewhere (`utils`, `tools`?) to extract all deps non-recursively from just local `DESCRIPTION` file then it would be nice to have it posted as answer too. Otherwise a wrapper on `read.dcf` extracting various dep types + stripping whitespaces, can achieve that. – jangorecki Apr 17 '16 at 11:36
  • 1
    An all R + recursive solution is here: http://stackoverflow.com/questions/38686427/determine-minimum-r-version-for-all-package-dependencies/38687310?noredirect=1#comment64767951_38687310 – hrbrmstr Aug 01 '16 at 12:24

6 Answers6

49

You can use the result of the available.packages function. For example, to see what ggplot2 depends on :

pack <- available.packages()
pack["ggplot2","Depends"]

Which gives :

[1] "R (>= 2.14), stats, methods"

Note that depending on what you want to achieve, you may need to check the Imports field, too.

juba
  • 47,631
  • 14
  • 113
  • 118
  • 1
    Cool -- I always like to find out about handy tools. Sadly, this will not work for those of us stuck behind a corporate firewall. We may be stuck doing something like `browseURL('http://cran.r-project.org/web/packages/package.name')` – Carl Witthoft Feb 01 '13 at 12:39
  • Thanks, that helped a lot, I did change the question scope a bit, but by recursively searching the list for Depends and Imports I was available to build out a complete list. – Jonathan Lisic Feb 01 '13 at 14:07
  • @CarlWitthoft if you're on windows, `setInternet2()` may help. – hadley Feb 01 '13 at 17:06
  • 1
    @hadley, thanks, but I went thru that exercise a while back. outgoing requests are blocked off. Clearly we need a new package `r.apt-get` :-) – Carl Witthoft Feb 01 '13 at 17:27
  • @CarlWitthoft I don't know if you still have the same issue, but if the problem has to do with your proxy and applications not being able to authenticate against it, you might want to look into cntlm (http://cntlm.sourceforge.net/) – Mark Feb 10 '15 at 20:44
  • @Mark sadly, having been laid off from said company, I can't test `cntim` in that environment, but I do thank you for the link. – Carl Witthoft Feb 11 '15 at 23:21
  • @CarlWitthoft Sorry to hear that - hope things are working out. – Mark Feb 12 '15 at 14:03
  • 1
    `pack["ggplot2","Depends"]` appears to no longer return the package dependencies, but `pack["ggplot2","Imports"]` does - as @juba suggests. At least for me: R version 4.0.2 (2020-06-22) – Dylan_Gomes Jan 12 '21 at 18:49
37

I am surprised no one mentioned tools::package_dependencies() , which is the simplest solution, and has a recursive argument (which the accepted solution does not offer).

Simple example looking at the recursive dependencies for the first 200 packages on CRAN:

library(tidyverse)
avail_pks <- available.packages()
deps <- tools::package_dependencies(packages = avail_pks[1:200, "Package"],
                                    recursive = TRUE)

tibble(Package=names(deps),
       data=map(deps, as_tibble)) %>% 
  unnest(data)
#> # A tibble: 7,125 x 2
#>    Package value         
#>    <chr>   <chr>         
#>  1 A3      xtable        
#>  2 A3      pbapply       
#>  3 A3      parallel      
#>  4 A3      stats         
#>  5 A3      utils         
#>  6 aaSEA   DT            
#>  7 aaSEA   networkD3     
#>  8 aaSEA   shiny         
#>  9 aaSEA   shinydashboard
#> 10 aaSEA   magrittr      
#> # … with 7,115 more rows

Created on 2020-12-04 by the reprex package (v0.3.0)

Matifou
  • 7,968
  • 3
  • 47
  • 52
  • 1
    I share the amazement, this should definitely be the accepted answer: it's in itself dependency-less, not hacky at all and exactly answers the question of the OP. The tidyverse transformation is a nice touch. – MS Berends Oct 21 '21 at 09:03
19

Another neat and simple solution is the internal function recursivePackageDependencies from the library packrat. However, the package must be installed in some library on your machine. The advantage is that it works with selfmade non-CRAN packages as well. Example:

packrat:::recursivePackageDependencies("ggplot2", ignore = "", lib.loc = .libPaths()[1])

giving:

 [1] "R6"           "RColorBrewer" "Rcpp"         "colorspace"   "dichromat"    "digest"       "gtable"      
 [8] "labeling"     "lazyeval"     "magrittr"     "munsell"      "plyr"         "reshape2"     "rlang"       
 [15] "scales"       "stringi"      "stringr"      "tibble"       "viridisLite" 
Ben Bolker
  • 211,554
  • 25
  • 370
  • 453
zerweck
  • 657
  • 7
  • 14
  • 2
    For those looking for a quick and dirty solution be aware that accessing internal functions with `:::` is a design mistake. https://stat.ethz.ch/R-manual/R-devel/library/base/html/ns-dblcolon.html – Leo Aug 01 '18 at 12:51
  • 1
    I would not say that this statement is true in general. While that advice given in the documentation might sometimes be true, there is absolutely no reason to not use this code in a script for your own use. The statement probably refers more to cases where one uses functions like this in another package for public use. – zerweck Aug 01 '18 at 13:21
  • You are right. I meant exactly that, you have to think where you are going to use this. While coding you can do almost everything ;) – Leo Aug 01 '18 at 13:38
  • Re "there is absolutely no reason to not use this code in a script for your own use", well you might like your own scripts to be robust. internal functions might change names or disappear in the next version, the maintainers don't commit to keep them around – moodymudskipper Oct 04 '22 at 10:07
  • 1
    Sure enough this is not valid anymore, we need `ignores = NULL` – moodymudskipper Jun 06 '23 at 11:08
9

I do not have R installed and I needed to find out which R Packages were dependencies upon a list of R Packages being requested for usage at my company.

I wrote a bash script that iterates over a list of R Packages in a file and will recursively discover dependencies.

The script uses a file named rinput_orig.txt as input (example below). The script will create a file named rinput.txt as it does its work.

The script will create the following files:

  • rdepsfound.txt - Lists dependencies found including the R Package that is dependent upon it (example below).
  • routput.txt - Lists all R Packages (from original list and list of dependencies) along with the license and CRAN URL (example below).
  • r404.txt - List of R Packages where a 404 was received when trying to curl. This is handy if your original list has any typos.

Bash script:

#!/bin/bash

# CLEANUP
rm routput.txt
rm rdepsfound.txt
rm r404.txt

# COPY ORIGINAL INPUT TO WORKING INPUT
cp rinput_orig.txt rinput.txt

IFS=","
while read PACKAGE; do
    echo Processing $PACKAGE...

    PACKAGEURL="http://cran.r-project.org/web/packages/${PACKAGE}/index.html"

    if [ `curl -o /dev/null --silent --head --write-out '%{http_code}\n' ${PACKAGEURL}` != 404 ]; then
        # GET LICENSE INFO OF PACKAGE
        LICENSEINFO=$(curl ${PACKAGEURL} 2>/dev/null | grep -A1 "License:" | grep -v "License:" | gawk 'match($0, /<a href=".*">(.*)<\/a>/, a) {print a[0]}' | sed "s/|/,/g" | sed "s/+/,/g")
        for x in ${LICENSEINFO[*]}
        do
            # SAVE LICENSE
            LICENSE=$(echo ${x} | gawk 'match($0, /<a href=".*">(.*)<\/a>/, a) {print a[1]}')
            break
        done

        # WRITE PACKAGE AND LICENSE TO OUTPUT FILE
        echo $PACKAGE $LICENSE $PACKAGEURL >> routput.txt

        # GET DEPENDENCIES OF PACKAGE
        DEPS=$(curl ${PACKAGEURL} 2>/dev/null | grep -A1 "Depends:" | grep -v "Depends:" | gawk 'match($0, /<a href=".*">(.*)<\/a>/, a) {print a[0]}')
        for x in ${DEPS[*]}
        do
            FOUNDDEP=$(echo "${x}" | gawk 'match($0, /<a href=".*">(.*)<\/a>/, a) {print a[1]}' | sed "s/<\/span>//g")
            if [ "$FOUNDDEP" != "" ]; then
                echo Found dependency $FOUNDDEP for $PACKAGE...
                grep $FOUNDDEP rinput.txt > /dev/null
                if [ "$?" = "0" ]; then
                    echo $FOUNDDEP already exists in package list...
                else
                    echo Adding $FOUNDDEP to package list...
                    # SAVE FOUND DEPENDENCY BACK TO INPUT LIST
                    echo $FOUNDDEP >> rinput.txt
                    # SAVE FOUND DEPENDENCY TO DEPENDENCY LIST FOR EASY VIEWING OF ALL FOUND DEPENDENCIES
                    echo $FOUNDDEP is a dependency of $PACKAGE >> rdepsfound.txt
                fi
            fi
        done
    else
        echo Skipping $PACKAGE because 404 was received...
        echo $PACKAGE $PACKAGEURL >> r404.txt
    fi

done < rinput.txt
echo -e "\nRESULT:"
sort -u routput.txt

Example rinput_orig.txt:

shiny
rmarkdown
xtable
RODBC
RJDBC
XLConnect
openxlsx
xlsx
Rcpp

Example console output when running script:

Processing shiny...
Processing rmarkdown...
Processing xtable...
Processing RODBC...
Processing RJDBC...
Found dependency DBI for RJDBC...
Adding DBI to package list...
Found dependency rJava for RJDBC...
Adding rJava to package list...
Processing XLConnect...
Found dependency XLConnectJars for XLConnect...
Adding XLConnectJars to package list...
Processing openxlsx...
Processing xlsx...
Found dependency rJava for xlsx...
rJava already exists in package list...
Found dependency xlsxjars for xlsx...
Adding xlsxjars to package list...
Processing Rcpp...
Processing DBI...
Processing rJava...
Processing XLConnectJars...
Processing xlsxjars...
Found dependency rJava for xlsxjars...
rJava already exists in package list...

Example rdepsfound.txt:

DBI is a dependency of RJDBC
rJava is a dependency of RJDBC
XLConnectJars is a dependency of XLConnect
xlsxjars is a dependency of xlsx

Example routput.txt:

shiny GPL-3 http://cran.r-project.org/web/packages/shiny/index.html
rmarkdown GPL-3 http://cran.r-project.org/web/packages/rmarkdown/index.html
xtable GPL-2 http://cran.r-project.org/web/packages/xtable/index.html
RODBC GPL-2 http://cran.r-project.org/web/packages/RODBC/index.html
RJDBC GPL-2 http://cran.r-project.org/web/packages/RJDBC/index.html
XLConnect GPL-3 http://cran.r-project.org/web/packages/XLConnect/index.html
openxlsx GPL-3 http://cran.r-project.org/web/packages/openxlsx/index.html
xlsx GPL-3 http://cran.r-project.org/web/packages/xlsx/index.html
Rcpp GPL-2 http://cran.r-project.org/web/packages/Rcpp/index.html
DBI LGPL-2 http://cran.r-project.org/web/packages/DBI/index.html
rJava GPL-2 http://cran.r-project.org/web/packages/rJava/index.html
XLConnectJars GPL-3 http://cran.r-project.org/web/packages/XLConnectJars/index.html
xlsxjars GPL-3 http://cran.r-project.org/web/packages/xlsxjars/index.html

I hope this helps someone!

Jesse
  • 1,603
  • 19
  • 20
4

I tested my own solution (local installed packages checked) against packrat and tools ones.
You could find out clear differences between methods.
tools::package_dependencies looks to give too much for older R versions (till 4.1.0 and recursive = TRUE) and is not efficient solution.

R 4.1.0 NEWS
"Function tools::package_dependencies() (in package tools) can now use different dependency types for direct and recursive dependencies."

packrat:::recursivePackageDependencies is using available.packages so it is based on newest remote packages, not local ones.

My function by default is skipping base packages, change the base arg if you want to attach them too.

Tested under R 4.1.0:

get_deps <- function(package, fields = c("Depends", "Imports", "LinkingTo"), base = FALSE, lib.loc = NULL) {
  stopifnot((length(package) == 1) && is.character(package))
  stopifnot(all(fields %in% c("Depends", "Imports", "Suggests", "LinkingTo")))
  stopifnot(is.logical(base))
  stopifnot(package %in% rownames(utils::installed.packages(lib.loc = lib.loc)))

  paks_global <- NULL
  
  deps <- function(pak, fileds) {
    pks <- packageDescription(pak)
    res <- NULL
    for (f in fileds) {
      ff <- pks[[f]]
      if (!is.null(ff)) {
        res <- c(
          res,
          vapply(
            strsplit(trimws(strsplit(ff, ",")[[1]]), "[ \n\\(]"),
            function(x) x[1],
            character(1)
          )
        )
      }
    }
    if (is.null(res)) {
      return(NULL)
    }
    for (r in res) {
      if (r != "R" && !r %in% paks_global) {
        paks_global <<- c(r, paks_global)
        deps(r, fields)
      }
    }
  }
  
  deps(package, fields)
  
  setdiff(unique(paks_global), c(
     package,
     "R",
     if (!base) {
       c(
         "stats",
         "graphics",
         "grDevices",
         "utils",
         "datasets",
         "methods",
         "base", 
         "tools"
       )
     } else {
       NULL
     }
   ))
}

own = get_deps("shiny", fields = c("Depends", "Imports"))

packrat = packrat:::recursivePackageDependencies("shiny", lib.loc = .libPaths(), fields = c("Depends", "Imports"))

tools = tools::package_dependencies("shiny", which =  c("Depends", "Imports"), recursive = TRUE)[[1]]

setdiff(own, packrat)
#> character(0)
setdiff(packrat, own)
#> character(0)
setdiff(own, tools)
#> character(0)
setdiff(tools, own)
#> [1] "methods"   "utils"     "grDevices" "tools"     "stats"     "graphics"
setdiff(packrat, tools)
#> character(0)
setdiff(tools, packrat)
#> [1] "methods"   "utils"     "grDevices" "tools"     "stats"     "graphics"

own
#>  [1] "lifecycle"   "ellipsis"    "cachem"      "jquerylib"   "rappdirs"   
#>  [6] "fs"          "sass"        "bslib"       "glue"        "commonmark" 
#> [11] "withr"       "fastmap"     "crayon"      "sourcetools" "base64enc"  
#> [16] "htmltools"   "digest"      "xtable"      "jsonlite"    "mime"       
#> [21] "magrittr"    "rlang"       "later"       "promises"    "R6"         
#> [26] "Rcpp"        "httpuv"
packrat
#>  [1] "R6"          "Rcpp"        "base64enc"   "bslib"       "cachem"     
#>  [6] "commonmark"  "crayon"      "digest"      "ellipsis"    "fastmap"    
#> [11] "fs"          "glue"        "htmltools"   "httpuv"      "jquerylib"  
#> [16] "jsonlite"    "later"       "lifecycle"   "magrittr"    "mime"       
#> [21] "promises"    "rappdirs"    "rlang"       "sass"        "sourcetools"
#> [26] "withr"       "xtable"
tools
#>  [1] "methods"     "utils"       "grDevices"   "httpuv"      "mime"       
#>  [6] "jsonlite"    "xtable"      "digest"      "htmltools"   "R6"         
#> [11] "sourcetools" "later"       "promises"    "tools"       "crayon"     
#> [16] "rlang"       "fastmap"     "withr"       "commonmark"  "glue"       
#> [21] "bslib"       "cachem"      "ellipsis"    "lifecycle"   "sass"       
#> [26] "jquerylib"   "magrittr"    "base64enc"   "Rcpp"        "stats"      
#> [31] "graphics"    "fs"          "rappdirs"

microbenchmark::microbenchmark(get_deps("shiny", fields = c("Depends", "Imports")),
                               packrat:::recursivePackageDependencies("shiny", lib.loc = .libPaths(), fields = c("Depends", "Imports")),
                               tools = tools::package_dependencies("shiny", which =  c("Depends", "Imports"), recursive = TRUE)[[1]],
                               times = 5
)
#> Warning in microbenchmark::microbenchmark(get_deps("shiny", fields =
#> c("Depends", : less accurate nanosecond times to avoid potential integer
#> overflows
#> Unit: milliseconds
#>                                                                                                           expr
#>                                                            get_deps("shiny", fields = c("Depends", "Imports"))
#>  packrat:::recursivePackageDependencies("shiny", lib.loc = .libPaths(),      fields = c("Depends", "Imports"))
#>                                                                                                          tools
#>         min         lq       mean     median         uq        max neval
#>    5.316552   5.607365   6.054568   5.674359   6.633308   7.041258     5
#>   18.767340  19.387588  21.739127  21.581457  23.526169  25.433079     5
#>  411.589734 449.179354 458.526354 465.431262 468.440211 497.991207     5

Created on 2021-06-25 by the reprex package (v0.3.0)

Proof that sth was wrong with tools solution under older R versions. Tested under R 3.6.3.

paks <- tools::package_dependencies("shiny", which =  c("Depends", "Imports"), recursive = TRUE)[[1]]
"lifecycle" %in% paks
#> [1] TRUE
any(c(paks, "shiny") %in% tools::dependsOnPkgs("lifecycle"))
#> [1] FALSE

Created on 2021-06-25 by the reprex package (v0.3.0)

polkas
  • 3,797
  • 1
  • 12
  • 25
  • 1
    I am using R 4.1.1: ```> own = get_deps("shiny", fields = c("Depends", "Imports")) Error in utils::installed.packages(lib.loc = lib.loc) : object 'lib.loc' not found``` Are there additional environment variable that I need to define? – geekyj Sep 11 '21 at 18:21
  • 1
    Good catch, just updated the code, lib.loc is now an additional argument with NULL by default. – polkas Sep 11 '21 at 19:03
  • 1
    Please consider usage of https://cran.r-project.org/web/packages/pacs/index.html package. There is a validated function `pacs::pac_deps` with a lot of functionalities. – polkas Sep 11 '21 at 19:04
  • Thank you very much for this very helpful function and examples! – geekyj Sep 13 '21 at 20:11
0

Try this: tools::package_dependencies(recursive = TRUE)$package_name

As an example- here are the dependencies for dplyr:

tools::package_dependencies(recursive = TRUE)$dplyr
 [1] "ellipsis"   "generics"   "glue"       "lifecycle"  "magrittr"   "methods"   
 [7] "R6"         "rlang"      "tibble"     "tidyselect" "utils"      "vctrs"     
[13] "cli"        "crayon"     "fansi"      "pillar"     "pkgconfig"  "purrr"     
[19] "digest"     "assertthat" "grDevices"  "utf8"       "tools"  
Carlo Carandang
  • 187
  • 1
  • 8