-
Notifications
You must be signed in to change notification settings - Fork 91
/
Copy pathdependencies.R
205 lines (171 loc) · 6.26 KB
/
dependencies.R
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
# Detect Application Dependencies
#
# Recursively detect all package dependencies for an application. This function
# parses all .R files in the application directory to determine what packages
# the application depends directly.
#
# Only direct dependencies are detected (i.e. no recursion is done to find the
# dependencies of the dependencies).
#
# @param project Directory containing application. Defaults to current working
# directory.
# @return Returns a list of the names of the packages on which R code in the
# application depends.
# @details Dependencies are determined by parsing application source code and
# looking for calls to \code{library}, \code{require}, \code{::}, and
# \code{:::}.
#
# @examples
#
# \dontrun{
#
# # dependencies for the app in the current working dir
# appDependencies()
#
# # dependencies for an app in another directory
# appDependencies("~/projects/shiny/app1")
# }
# @keywords internal
appDependencies <- function(project = NULL, available.packages = NULL) {
if (is.null(available.packages)) available.packages <- available.packages()
project <- getProjectDir(project)
## For R packages, we only use the DESCRIPTION file
if (file.exists(file.path(project, "DESCRIPTION"))) {
## Make sure we get records recursively from the packages in DESCRIPTION
parentDeps <-
pkgDescriptionDependencies(file.path(project, "DESCRIPTION"))$Package
## For downstream dependencies, we don't grab their Suggests:
## Presumedly, we can build child dependencies without vignettes, and hence
## do not need suggests -- for the package itself, we should make sure
## we grab suggests, however
childDeps <- recursivePackageDependencies(parentDeps,
available.packages)
} else {
parentDeps <- setdiff(unique(c(dirDependencies(project))), "packrat")
childDeps <- recursivePackageDependencies(parentDeps, available.packages)
}
sort(unique(c(parentDeps, childDeps, "packrat")))
}
# detect all package dependencies for a directory of files
dirDependencies <- function(dir) {
dir <- normalizePath(dir, winslash='/')
# first get the packages referred to in source code
pattern <- "\\.[rR]$|\\.[rR]md$|\\.[rR]nw$|\\.[rR]pres$"
pkgs <- character()
R_files <- list.files(dir,
pattern = pattern,
ignore.case = TRUE,
recursive = TRUE
)
## Avoid anything within the packrat directory itself -- all inference
## should be done on user code
packratDirRegex <- paste("^", .packrat$packratFolderName, sep = "")
R_files <- grep(packratDirRegex, R_files, invert = TRUE, value = TRUE)
sapply(R_files, function(file) {
filePath <- file.path(dir, file)
pkgs <<- append(pkgs, fileDependencies(file.path(dir, file)))
})
unique(pkgs)
}
# detect all package dependencies for a source file (parses the file and then
# recursively examines all expressions in the file)
# ad-hoc dispatch based on the file extension
fileDependencies <- function(file) {
fileext <- tolower(gsub(".*\\.", "", file))
switch (fileext,
r = fileDependencies.R(file),
rmd = fileDependencies.Rmd(file),
rnw = fileDependencies.Rnw(file),
rpres = fileDependencies.Rpres(file),
stop("Unrecognized file type '", file, "'")
)
}
fileDependencies.Rmd <- fileDependencies.Rpres <- function(file) {
if (require("knitr")) {
tempfile <- tempfile()
on.exit(unlink(tempfile))
tryCatch(silent(
knitr::knit(file, output = tempfile, tangle = TRUE)
), error = function(e) {
message("Unable to knit file '", file, "'; cannot parse dependencies")
character()
})
fileDependencies.R(tempfile)
} else {
warning("knitr is required to parse dependencies from .Rmd files, but is not available")
character()
}
}
fileDependencies.Rnw <- function(file) {
tempfile <- tempfile()
on.exit(unlink(tempfile))
tryCatch(silent(
Stangle(file, output = tempfile)
), error = function(e) {
message("Unable to stangle file '", file, "'; cannot parse dependencies")
character()
})
fileDependencies.R(tempfile)
}
fileDependencies.R <- function(file) {
if (!file.exists(file)) {
warning("No file at path '", file, "'.")
return(character())
}
# build a list of package dependencies to return
pkgs <- character()
# parse file and examine expressions
tryCatch({
# parse() generates a warning when the file has an incomplete last line, but
# it still parses the file correctly; ignore this and other warnings.
# We'll still halt when parsing fails.
exprs <- suppressWarnings(parse(file, n = -1L))
for (i in seq_along(exprs))
pkgs <- append(pkgs, expressionDependencies(exprs[[i]]))
}, error = function(e) {
warning(paste("Failed to parse", file, "; dependencies in this file will",
"not be discovered."))
})
# return packages
unique(pkgs)
}
# detect the package dependencies of an expression (adapted from
# tools:::.check_packages_used)
#
# expressionDependencies(quote(library("h")))
# expressionDependencies(quote(library(10, package = "h")))
# expressionDependencies(quote(library(h)))
# expressionDependencies(quote({library(h); library(g)}))
# expressionDependencies(quote(h::f))
expressionDependencies <- function(e) {
# base case
if (is.atomic(e) || is.name(e)) return()
# recursive case: expression (= list of calls)
if (is.expression(e)) {
return(unlist(lapply(e, expressionDependencies)))
}
# base case: a call
fname <- as.character(e[[1L]])
# a refclass method call, so return
if (length(fname) > 1) return()
if (length(fname) == 1) {
# base case: call to library/require
if (fname %in% c("library", "require")) {
mc <- match.call(get(fname, baseenv()), e)
if (is.null(mc$package)) return(NULL)
if (isTRUE(mc$character.only)) return(NULL)
return(as.character(mc$package))
}
# base case: call to :: or :::
if (fname %in% c("::", ":::")) (
return(as.character(e[[2L]]))
)
# base case: methods functions
if (fname %in% c("setClass", "setRefClass", "setMethod", "setGeneric")) {
return("methods")
}
}
# recursive case: all other calls
children <- lapply(as.list(e[-1]), expressionDependencies)
unique(unlist(children))
}