1#' Detect Application Dependencies
2#'
3#' Recursively detect all package dependencies for an application. This function
4#' parses all .R files in the application directory to determine what packages
5#' the application depends on; and for each of those packages what other
6#' packages they depend on.
7#' @inheritParams deployApp
8#' @param appDir Directory containing application. Defaults to current working
9#'   directory.
10#' @return Returns a data frame listing the package
11#'   dependencies detected for the application: \tabular{ll}{ \code{package}
12#'   \tab Name of package \cr \code{version} \tab Version of package\cr }
13#' @details Dependencies are determined by parsing application source code and
14#'   looking for calls to \code{library}, \code{require}, \code{::}, and
15#'   \code{:::}.
16#'
17#'   Recursive dependencies are detected by examining the \code{Depends},
18#'   \code{Imports}, and \code{LinkingTo} fields of the packages immediately
19#'   dependend on by the application.
20#'
21#' @note Since the \code{Suggests} field is not included when determining
22#'   recursive dependencies of packages, it's possible that not every package
23#'   required to run your application will be detected.
24#'
25#'   In this case, you can force a package to be included dependency by
26#'   inserting call(s) to \code{require} within your source directory. This code
27#'   need not actually execute, for example you could create a standalone file
28#'   named \code{dependencies.R} with the following code: \cr \cr
29#'   \code{require(xts)} \cr \code{require(colorspace)} \cr
30#'
31#'   This will force the \code{xts} and \code{colorspace} packages to be
32#'   installed along with the rest of your application when it is deployed.
33#' @examples
34#' \dontrun{
35#'
36#' # dependencies for the app in the current working dir
37#' appDependencies()
38#'
39#' # dependencies for an app in another directory
40#' appDependencies("~/projects/shiny/app1")
41#' }
42#' @seealso \link[rsconnect:rsconnectPackages]{Using Packages with rsconnect}
43#' @export
44appDependencies <- function(appDir = getwd(), appFiles=NULL) {
45  # if the list of files wasn't specified, generate it
46  if (is.null(appFiles)) {
47    appFiles <- bundleFiles(appDir)
48  }
49  bundleDir <- bundleAppDir(appDir, appFiles)
50  deps <- snapshotDependencies(bundleDir)
51  data.frame(package = deps[,"Package"],
52             version = deps[,"Version"],
53             source = deps[,"Source"],
54             row.names = c(1:length(deps[,"Package"])),
55             stringsAsFactors=FALSE)
56}
57
58snapshotDependencies <- function(appDir, implicit_dependencies=c()) {
59
60  # create a packrat "snapshot"
61  addPackratSnapshot(appDir, implicit_dependencies)
62
63  # TODO: should we care about lockfile version or packrat version?
64  lockFilePath <- snapshotLockFile(appDir)
65  df <- as.data.frame(read.dcf(lockFilePath), stringsAsFactors = FALSE)
66
67  # get repos defined in the lockfile
68  repos <- gsub("[\r\n]", " ", df[1, 'Repos'])
69  repos <- strsplit(unlist(strsplit(repos, "\\s*,\\s*", perl = TRUE)), "=", fixed = TRUE)
70  repos <- setNames(
71    sapply(repos, "[[", 2),
72    sapply(repos, "[[", 1)
73  )
74
75  # get Bioconductor repos if any
76  biocRepos = repos[grep('BioC', names(repos), perl=TRUE, value=TRUE)]
77  if (length(biocRepos) > 0) {
78    biocPackages = available.packages(contriburl=contrib.url(biocRepos, type="source"))
79  } else {
80    biocPackages = c()
81  }
82
83  # get packages records defined in the lockfile
84  records <- utils::tail(df, -1)
85
86  # if the package is in a named CRAN-like repository capture it
87  tmp <- sapply(seq.int(nrow(records)), function(i) {
88    pkg <- records[i, "Package"]
89    source <- records[i, "Source"]
90    repository <- NA
91    # capture Bioconcutor repository
92    if (identical(source, "Bioconductor")) {
93      if (pkg %in% biocPackages) {
94        repository <- biocPackages[pkg, 'Repository']
95      }
96    } else {
97      # capture CRAN-like repository
98      repository <- if (source %in% names(repos)) repos[[source]] else NA
99    }
100    repository
101  })
102  records[, "Repository"] <- tmp
103  return(records)
104}
105
106# get source packages from CRAN
107availableCRANSourcePackages <- function() {
108  available.packages("https://cran.rstudio.com/src/contrib", type = "source")
109}
110