/
clean.R
361 lines (287 loc) · 8.74 KB
/
clean.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
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
#' Clean a project
#'
#' Clean up a project and its associated \R libraries.
#'
#' # Actions
#'
#' The following clean actions are available:
#'
#' \describe{
#'
#' \item{`package.locks`}{
#'
#' During package installation, \R will create package locks in the
#' library path, typically named `00LOCK-<package>`. On occasion, if package
#' installation fails or \R is terminated while installing a package, these
#' locks can be left behind and will inhibit future attempts to reinstall
#' that package. Use this action to remove such left-over package locks.
#'
#' }
#'
#' \item{`library.tempdirs`}{
#'
#' During package installation, \R may create temporary directories with
#' names of the form `file\w{12}`, and on occasion those files can be
#' left behind even after they are no longer in use. Use this action to
#' remove such left-over directories.
#' }
#'
#' \item{`system.library`}{
#'
#' In general, it is recommended that only packages distributed with \R
#' are installed into the default library (the library path referred to
#' by `.Library`). Use this action to remove any user-installed packages
#' that have been installed to the system library.
#'
#' Because this action is destructive, it is by default never run -- it
#' must be explicitly requested by the user.
#'
#' }
#'
#' \item{`unused.packages`}{
#'
#' Remove packages that are installed in the project library, but no longer
#' appear to be used in the project sources.
#'
#' Because this action is destructive, it is by default only run in
#' interactive sessions when prompting is enabled.
#'
#' }
#'
#' }
#'
#'
#' @inherit renv-params
#'
#' @param actions The set of clean actions to take. See the documentation in
#' **Actions** for a list of available actions, and the default actions
#' taken when no actions are supplied.
#'
#' @export
#'
#' @examples
#' \dontrun{
#'
#' # clean the current project
#' renv::clean()
#'
#' }
clean <- function(project = NULL,
...,
actions = NULL,
prompt = interactive())
{
renv_scope_error_handler()
renv_dots_check(...)
project <- renv_project_resolve(project)
renv_project_lock(project = project)
renv_scope_verbose_if(prompt)
renv_activate_prompt("clean", NULL, prompt, project)
actions <- actions %||% renv_clean_actions(prompt)
all <- list(
package.locks = renv_clean_package_locks,
library.tempdirs = renv_clean_library_tempdirs,
system.library = renv_clean_system_library,
unused.packages = renv_clean_unused_packages
)
methods <- all[actions]
for (method in methods)
tryCatch(method(project, prompt), error = warnify)
writef("- The project has been cleaned.")
invisible(project)
}
renv_clean_actions <- function(prompt) {
default <- c(
"package.locks",
"library.tempdirs"
)
unsafe <- c(
# "system.library",
"unused.packages"
)
c(default, if (prompt) unsafe)
}
renv_clean_library_tempdirs <- function(project, prompt) {
ntd <- function() {
writef("- No temporary directories were found in the project library.")
FALSE
}
library <- renv_paths_library(project = project)
children <- list.files(library, full.names = TRUE)
bad <- grep("/file\\w{12}$", children, value = TRUE)
if (empty(bad))
return(ntd())
# nocov start
if (prompt || renv_verbose()) {
caution_bullets("The following directories will be removed:", bad)
if (prompt && !proceed())
cancel()
}
# nocov end
unlink(bad, recursive = TRUE)
TRUE
}
# remove user packages in system library
renv_clean_system_library <- function(project, prompt) {
ntd <- function() {
writef("- No non-system packages were discovered in the system library.")
FALSE
}
# explicitly query for packages
syslib <- renv_path_normalize(renv_libpaths_system())
db <- installed_packages(lib.loc = syslib, priority = "NA")
packages <- setdiff(db$Package, "translations")
# also look for leftover package folders
# (primarily for Windows, where .dlls from old packages can be left behind)
# nocov start
if (renv_platform_windows()) {
folders <- list.files(syslib, full.names = TRUE)
descpaths <- file.path(folders, "DESCRIPTION")
missing <- !file.exists(descpaths)
packages <- union(packages, basename(folders)[missing])
}
# nocov end
# check for any packages needing removal
if (empty(packages))
return(ntd())
# nocov start
if (prompt || renv_verbose()) {
caution_bullets(
"The following non-system packages are installed in the system library:",
packages,
c(
"Normally, only packages distributed with R should be installed in the system library.",
"These packages will be removed.",
"If necessary, consider reinstalling these packages in your site library."
)
)
if (prompt && !proceed())
cancel()
}
# nocov end
remove(packages, library = syslib)
TRUE
}
renv_clean_unused_packages <- function(project, prompt) {
ntd <- function() {
writef("- No unused packages were found in the project library.")
FALSE
}
# find packages installed in the project library
library <- renv_paths_library(project = project)
installed <- list.files(library)
if (empty(installed))
return(ntd())
# find packages used in the project and their recursive dependencies
packages <- renv_snapshot_dependencies(project, dev = TRUE)
paths <- renv_package_dependencies(packages, project = project)
packages <- names(paths)
# figure out which packages aren't needed
removable <- renv_vector_diff(installed, packages)
if (empty(removable))
return(ntd())
# nocov start
if (prompt || renv_verbose()) {
caution_bullets(
c(
"The following packages are installed in the project library,",
"but appear to be no longer used in your project."
),
removable,
"These packages will be removed."
)
if (prompt && !proceed())
cancel()
}
# nocov end
remove(removable, library = library)
return(TRUE)
}
renv_clean_package_locks <- function(project, prompt) {
ntd <- function() {
writef("- No stale package locks were found.")
FALSE
}
# find 00LOCK directories in library
library <- renv_paths_library(project = project)
lock <- list.files(path = library, pattern = "^00LOCK", full.names = TRUE)
if (empty(lock))
return(ntd())
# check to see which are old
now <- Sys.time()
mtime <- file.mtime(lock)
mtime[is.na(mtime)] <- now
diff <- difftime(now, mtime, units = "secs")
old <- lock[diff > 120]
if (empty(old))
return(ntd())
# nocov start
if (prompt || renv_verbose()) {
caution_bullets(
"The following stale package locks were discovered in your library:",
basename(old),
"These locks will be removed."
)
if (prompt && !proceed())
cancel()
}
# nocov end
unlink(old, recursive = TRUE)
TRUE
}
# nocov start
renv_clean_cache <- function(project, prompt) {
ntd <- function() {
writef("- No unused packages were found in the renv cache.")
FALSE
}
# find projects monitored by renv
projects <- renv_paths_root("projects")
projlist <- character()
if (file.exists(projects))
projlist <- readLines(projects, warn = FALSE, encoding = "UTF-8")
# inform user if any projects are missing
missing <- !file.exists(projlist)
if (any(missing)) {
caution_bullets(
"The following projects are monitored by renv, but no longer exist:",
projlist[missing],
"These projects will be removed from renv's project list."
)
if (prompt && !proceed())
cancel()
writeLines(projlist[!missing], con = projects, useBytes = TRUE)
}
action <- function(project) {
library <- renv_paths_library(project = project)
packages <- list.files(library, full.names = TRUE)
descs <- file.path(packages, "DESCRIPTION")
existing <- file.exists(descs)
map_chr(descs[existing], renv_cache_path, USE.NAMES = FALSE)
}
# for each project, find packages used in their renv private library,
# and look for entries in the cache
projlist <- projlist[!missing]
callback <- renv_progress_callback(action, length(projlist))
used <- uapply(projlist, callback)
# check what packages are actually available in the cache
available <- renv_cache_list()
diff <- renv_vector_diff(available, used)
if (empty(diff))
return(ntd())
if (prompt || renv_verbose()) {
caution_bullets(
"The following packages are installed in the cache but no longer used:",
renv_cache_format_path(diff),
"These packages will be removed."
)
if (prompt && !proceed())
cancel()
}
# remove the directories
unlink(diff, recursive = TRUE)
renv_cache_clean_empty()
writef("- %i package(s) have been removed.", length(diff))
TRUE
}
# nocov end