Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
quarto-dev
GitHub Repository: quarto-dev/quarto-cli
Path: blob/main/tests/renv/activate.R
12924 views
1
2
local({
3
4
# the requested version of renv
5
version <- "1.2.0"
6
attr(version, "md5") <- "b7d230b07507f361d3bcf794d157a188"
7
attr(version, "sha") <- NULL
8
9
# the project directory
10
project <- Sys.getenv("RENV_PROJECT")
11
if (!nzchar(project))
12
project <- getwd()
13
14
# use start-up diagnostics if enabled
15
diagnostics <- Sys.getenv("RENV_STARTUP_DIAGNOSTICS", unset = "FALSE")
16
if (diagnostics) {
17
start <- Sys.time()
18
profile <- tempfile("renv-startup-", fileext = ".Rprof")
19
utils::Rprof(profile)
20
on.exit({
21
utils::Rprof(NULL)
22
elapsed <- signif(difftime(Sys.time(), start, units = "auto"), digits = 2L)
23
writeLines(sprintf("- renv took %s to run the autoloader.", format(elapsed)))
24
writeLines(sprintf("- Profile: %s", profile))
25
print(utils::summaryRprof(profile))
26
}, add = TRUE)
27
}
28
29
# figure out whether the autoloader is enabled
30
enabled <- local({
31
32
# first, check config option
33
override <- getOption("renv.config.autoloader.enabled")
34
if (!is.null(override))
35
return(override)
36
37
# if we're being run in a context where R_LIBS is already set,
38
# don't load -- presumably we're being run as a sub-process and
39
# the parent process has already set up library paths for us
40
rcmd <- Sys.getenv("R_CMD", unset = NA)
41
rlibs <- Sys.getenv("R_LIBS", unset = NA)
42
if (!is.na(rlibs) && !is.na(rcmd))
43
return(FALSE)
44
45
# next, check environment variables
46
# prefer using the configuration one in the future
47
envvars <- c(
48
"RENV_CONFIG_AUTOLOADER_ENABLED",
49
"RENV_AUTOLOADER_ENABLED",
50
"RENV_ACTIVATE_PROJECT"
51
)
52
53
for (envvar in envvars) {
54
envval <- Sys.getenv(envvar, unset = NA)
55
if (!is.na(envval))
56
return(tolower(envval) %in% c("true", "t", "1"))
57
}
58
59
# enable by default
60
TRUE
61
62
})
63
64
# bail if we're not enabled
65
if (!enabled) {
66
67
# if we're not enabled, we might still need to manually load
68
# the user profile here
69
profile <- Sys.getenv("R_PROFILE_USER", unset = "~/.Rprofile")
70
if (file.exists(profile)) {
71
cfg <- Sys.getenv("RENV_CONFIG_USER_PROFILE", unset = "TRUE")
72
if (tolower(cfg) %in% c("true", "t", "1"))
73
sys.source(profile, envir = globalenv())
74
}
75
76
return(FALSE)
77
78
}
79
80
# avoid recursion
81
if (identical(getOption("renv.autoloader.running"), TRUE)) {
82
warning("ignoring recursive attempt to run renv autoloader")
83
return(invisible(TRUE))
84
}
85
86
# signal that we're loading renv during R startup
87
options(renv.autoloader.running = TRUE)
88
on.exit(options(renv.autoloader.running = NULL), add = TRUE)
89
90
# signal that we've consented to use renv
91
options(renv.consent = TRUE)
92
93
# load the 'utils' package eagerly -- this ensures that renv shims, which
94
# mask 'utils' packages, will come first on the search path
95
library(utils, lib.loc = .Library)
96
97
# unload renv if it's already been loaded
98
if ("renv" %in% loadedNamespaces())
99
unloadNamespace("renv")
100
101
# load bootstrap tools
102
ansify <- function(text) {
103
if (renv_ansify_enabled())
104
renv_ansify_enhanced(text)
105
else
106
renv_ansify_default(text)
107
}
108
109
renv_ansify_enabled <- function() {
110
111
override <- Sys.getenv("RENV_ANSIFY_ENABLED", unset = NA)
112
if (!is.na(override))
113
return(as.logical(override))
114
115
pane <- Sys.getenv("RSTUDIO_CHILD_PROCESS_PANE", unset = NA)
116
if (identical(pane, "build"))
117
return(FALSE)
118
119
testthat <- Sys.getenv("TESTTHAT", unset = "false")
120
if (tolower(testthat) %in% "true")
121
return(FALSE)
122
123
iderun <- Sys.getenv("R_CLI_HAS_HYPERLINK_IDE_RUN", unset = "false")
124
if (tolower(iderun) %in% "false")
125
return(FALSE)
126
127
TRUE
128
129
}
130
131
renv_ansify_default <- function(text) {
132
text
133
}
134
135
renv_ansify_enhanced <- function(text) {
136
137
# R help links
138
pattern <- "`\\?(renv::(?:[^`])+)`"
139
replacement <- "`\033]8;;x-r-help:\\1\a?\\1\033]8;;\a`"
140
text <- gsub(pattern, replacement, text, perl = TRUE)
141
142
# runnable code
143
pattern <- "`(renv::(?:[^`])+)`"
144
replacement <- "`\033]8;;x-r-run:\\1\a\\1\033]8;;\a`"
145
text <- gsub(pattern, replacement, text, perl = TRUE)
146
147
# return ansified text
148
text
149
150
}
151
152
renv_ansify_init <- function() {
153
154
envir <- renv_envir_self()
155
if (renv_ansify_enabled())
156
assign("ansify", renv_ansify_enhanced, envir = envir)
157
else
158
assign("ansify", renv_ansify_default, envir = envir)
159
160
}
161
162
`%||%` <- function(x, y) {
163
if (is.null(x)) y else x
164
}
165
166
catf <- function(fmt, ..., appendLF = TRUE) {
167
168
quiet <- getOption("renv.bootstrap.quiet", default = FALSE)
169
if (quiet)
170
return(invisible())
171
172
# also check for config environment variables that should suppress messages
173
# https://github.com/rstudio/renv/issues/2214
174
enabled <- Sys.getenv("RENV_CONFIG_STARTUP_QUIET", unset = NA)
175
if (!is.na(enabled) && tolower(enabled) %in% c("true", "1"))
176
return(invisible())
177
178
enabled <- Sys.getenv("RENV_CONFIG_SYNCHRONIZED_CHECK", unset = NA)
179
if (!is.na(enabled) && tolower(enabled) %in% c("false", "0"))
180
return(invisible())
181
182
msg <- sprintf(fmt, ...)
183
cat(msg, file = stdout(), sep = if (appendLF) "\n" else "")
184
185
invisible(msg)
186
187
}
188
189
header <- function(label,
190
...,
191
prefix = "#",
192
suffix = "-",
193
n = min(getOption("width"), 78))
194
{
195
label <- sprintf(label, ...)
196
n <- max(n - nchar(label) - nchar(prefix) - 2L, 8L)
197
if (n <= 0)
198
return(paste(prefix, label))
199
200
tail <- paste(rep.int(suffix, n), collapse = "")
201
paste0(prefix, " ", label, " ", tail)
202
203
}
204
205
heredoc <- function(text, leave = 0) {
206
207
# remove leading, trailing whitespace
208
trimmed <- gsub("^\\s*\\n|\\n\\s*$", "", text)
209
210
# split into lines
211
lines <- strsplit(trimmed, "\n", fixed = TRUE)[[1L]]
212
213
# compute common indent
214
indent <- regexpr("[^[:space:]]", lines)
215
common <- min(setdiff(indent, -1L)) - leave
216
text <- paste(substring(lines, common), collapse = "\n")
217
218
# substitute in ANSI links for executable renv code
219
ansify(text)
220
221
}
222
223
bootstrap <- function(version, library) {
224
225
friendly <- renv_bootstrap_version_friendly(version)
226
section <- header(sprintf("Bootstrapping renv %s", friendly))
227
catf(section)
228
229
# ensure the target library path exists; required for file.copy(..., recursive = TRUE)
230
dir.create(library, showWarnings = FALSE, recursive = TRUE)
231
232
# try to install renv from cache
233
md5 <- attr(version, "md5", exact = TRUE)
234
if (length(md5)) {
235
pkgpath <- renv_bootstrap_find(version)
236
if (length(pkgpath) && file.exists(pkgpath)) {
237
ok <- file.copy(pkgpath, library, recursive = TRUE)
238
if (isTRUE(ok))
239
return(invisible())
240
}
241
}
242
243
# attempt to download renv
244
catf("- Downloading renv ... ", appendLF = FALSE)
245
withCallingHandlers(
246
tarball <- renv_bootstrap_download(version),
247
error = function(err) {
248
catf("FAILED")
249
stop("failed to download:\n", conditionMessage(err))
250
}
251
)
252
catf("OK")
253
on.exit(unlink(tarball), add = TRUE)
254
255
# now attempt to install
256
catf("- Installing renv ... ", appendLF = FALSE)
257
withCallingHandlers(
258
status <- renv_bootstrap_install(version, tarball, library),
259
error = function(err) {
260
catf("FAILED")
261
stop("failed to install:\n", conditionMessage(err))
262
}
263
)
264
catf("OK")
265
266
# add empty line to break up bootstrapping from normal output
267
catf("")
268
return(invisible())
269
}
270
271
renv_bootstrap_tests_running <- function() {
272
getOption("renv.tests.running", default = FALSE)
273
}
274
275
renv_bootstrap_repos <- function() {
276
277
# get CRAN repository
278
cran <- getOption("renv.repos.cran", "https://cloud.r-project.org")
279
280
# check for repos override
281
repos <- Sys.getenv("RENV_CONFIG_REPOS_OVERRIDE", unset = NA)
282
if (!is.na(repos)) {
283
284
# split on ';' if present
285
parts <- strsplit(repos, ";", fixed = TRUE)[[1L]]
286
287
# split into named repositories if present
288
idx <- regexpr("=", parts, fixed = TRUE)
289
keys <- substring(parts, 1L, idx - 1L)
290
vals <- substring(parts, idx + 1L)
291
names(vals) <- keys
292
293
# if we have a single unnamed repository, call it CRAN
294
if (length(vals) == 1L && identical(keys, ""))
295
names(vals) <- "CRAN"
296
297
return(vals)
298
299
}
300
301
# check for lockfile repositories
302
repos <- tryCatch(renv_bootstrap_repos_lockfile(), error = identity)
303
if (!inherits(repos, "error") && length(repos))
304
return(repos)
305
306
# retrieve current repos
307
repos <- getOption("repos")
308
309
# ensure @CRAN@ entries are resolved
310
repos[repos == "@CRAN@"] <- cran
311
312
# add in renv.bootstrap.repos if set
313
default <- c(FALLBACK = "https://cloud.r-project.org")
314
extra <- getOption("renv.bootstrap.repos", default = default)
315
repos <- c(repos, extra)
316
317
# remove duplicates that might've snuck in
318
dupes <- duplicated(repos) | duplicated(names(repos))
319
repos[!dupes]
320
321
}
322
323
renv_bootstrap_repos_lockfile <- function() {
324
325
lockpath <- Sys.getenv("RENV_PATHS_LOCKFILE", unset = "renv.lock")
326
if (!file.exists(lockpath))
327
return(NULL)
328
329
lockfile <- tryCatch(renv_json_read(lockpath), error = identity)
330
if (inherits(lockfile, "error")) {
331
warning(lockfile)
332
return(NULL)
333
}
334
335
repos <- lockfile$R$Repositories
336
if (length(repos) == 0)
337
return(NULL)
338
339
keys <- vapply(repos, `[[`, "Name", FUN.VALUE = character(1))
340
vals <- vapply(repos, `[[`, "URL", FUN.VALUE = character(1))
341
names(vals) <- keys
342
343
return(vals)
344
345
}
346
347
renv_bootstrap_download <- function(version) {
348
349
sha <- attr(version, "sha", exact = TRUE)
350
351
methods <- if (!is.null(sha)) {
352
353
# attempting to bootstrap a development version of renv
354
c(
355
function() renv_bootstrap_download_tarball(sha),
356
function() renv_bootstrap_download_github(sha)
357
)
358
359
} else {
360
361
# attempting to bootstrap a release version of renv
362
c(
363
function() renv_bootstrap_download_tarball(version),
364
function() renv_bootstrap_download_cran_latest(version),
365
function() renv_bootstrap_download_cran_archive(version)
366
)
367
368
}
369
370
for (method in methods) {
371
path <- tryCatch(method(), error = identity)
372
if (is.character(path) && file.exists(path))
373
return(path)
374
}
375
376
stop("All download methods failed")
377
378
}
379
380
renv_bootstrap_download_impl <- function(url, destfile) {
381
382
mode <- "wb"
383
384
# https://bugs.r-project.org/bugzilla/show_bug.cgi?id=17715
385
fixup <-
386
Sys.info()[["sysname"]] == "Windows" &&
387
substring(url, 1L, 5L) == "file:"
388
389
if (fixup)
390
mode <- "w+b"
391
392
args <- list(
393
url = url,
394
destfile = destfile,
395
mode = mode,
396
quiet = TRUE
397
)
398
399
if ("headers" %in% names(formals(utils::download.file))) {
400
headers <- renv_bootstrap_download_custom_headers(url)
401
if (length(headers) && is.character(headers))
402
args$headers <- headers
403
}
404
405
do.call(utils::download.file, args)
406
407
}
408
409
renv_bootstrap_download_custom_headers <- function(url) {
410
411
headers <- getOption("renv.download.headers")
412
if (is.null(headers))
413
return(character())
414
415
if (!is.function(headers))
416
stopf("'renv.download.headers' is not a function")
417
418
headers <- headers(url)
419
if (length(headers) == 0L)
420
return(character())
421
422
if (is.list(headers))
423
headers <- unlist(headers, recursive = FALSE, use.names = TRUE)
424
425
ok <-
426
is.character(headers) &&
427
is.character(names(headers)) &&
428
all(nzchar(names(headers)))
429
430
if (!ok)
431
stop("invocation of 'renv.download.headers' did not return a named character vector")
432
433
headers
434
435
}
436
437
renv_bootstrap_download_cran_latest <- function(version) {
438
439
spec <- renv_bootstrap_download_cran_latest_find(version)
440
type <- spec$type
441
repos <- spec$repos
442
443
baseurl <- utils::contrib.url(repos = repos, type = type)
444
ext <- if (identical(type, "source"))
445
".tar.gz"
446
else if (Sys.info()[["sysname"]] == "Windows")
447
".zip"
448
else
449
".tgz"
450
name <- sprintf("renv_%s%s", version, ext)
451
url <- paste(baseurl, name, sep = "/")
452
453
destfile <- file.path(tempdir(), name)
454
status <- tryCatch(
455
renv_bootstrap_download_impl(url, destfile),
456
condition = identity
457
)
458
459
if (inherits(status, "condition"))
460
return(FALSE)
461
462
# report success and return
463
destfile
464
465
}
466
467
renv_bootstrap_download_cran_latest_find <- function(version) {
468
469
# check whether binaries are supported on this system
470
binary <-
471
getOption("renv.bootstrap.binary", default = TRUE) &&
472
!identical(.Platform$pkgType, "source") &&
473
!identical(getOption("pkgType"), "source") &&
474
Sys.info()[["sysname"]] %in% c("Darwin", "Windows")
475
476
types <- c(if (binary) "binary", "source")
477
478
# iterate over types + repositories
479
for (type in types) {
480
for (repos in renv_bootstrap_repos()) {
481
482
# build arguments for utils::available.packages() call
483
args <- list(type = type, repos = repos)
484
485
# add custom headers if available -- note that
486
# utils::available.packages() will pass this to download.file()
487
if ("headers" %in% names(formals(utils::download.file))) {
488
headers <- renv_bootstrap_download_custom_headers(repos)
489
if (length(headers) && is.character(headers))
490
args$headers <- headers
491
}
492
493
# retrieve package database
494
db <- tryCatch(
495
as.data.frame(
496
do.call(utils::available.packages, args),
497
stringsAsFactors = FALSE
498
),
499
error = identity
500
)
501
502
if (inherits(db, "error"))
503
next
504
505
# check for compatible entry
506
entry <- db[db$Package %in% "renv" & db$Version %in% version, ]
507
if (nrow(entry) == 0)
508
next
509
510
# found it; return spec to caller
511
spec <- list(entry = entry, type = type, repos = repos)
512
return(spec)
513
514
}
515
}
516
517
# if we got here, we failed to find renv
518
fmt <- "renv %s is not available from your declared package repositories"
519
stop(sprintf(fmt, version))
520
521
}
522
523
renv_bootstrap_download_cran_archive <- function(version) {
524
525
name <- sprintf("renv_%s.tar.gz", version)
526
repos <- renv_bootstrap_repos()
527
urls <- file.path(repos, "src/contrib/Archive/renv", name)
528
destfile <- file.path(tempdir(), name)
529
530
for (url in urls) {
531
532
status <- tryCatch(
533
renv_bootstrap_download_impl(url, destfile),
534
condition = identity
535
)
536
537
if (identical(status, 0L))
538
return(destfile)
539
540
}
541
542
return(FALSE)
543
544
}
545
546
renv_bootstrap_find <- function(version) {
547
548
path <- renv_bootstrap_find_cache(version)
549
if (length(path) && file.exists(path)) {
550
catf("- Using renv %s from global package cache", version)
551
return(path)
552
}
553
554
}
555
556
renv_bootstrap_find_cache <- function(version) {
557
558
md5 <- attr(version, "md5", exact = TRUE)
559
if (is.null(md5))
560
return()
561
562
# infer path to renv cache
563
cache <- Sys.getenv("RENV_PATHS_CACHE", unset = "")
564
if (!nzchar(cache)) {
565
root <- Sys.getenv("RENV_PATHS_ROOT", unset = NA)
566
if (!is.na(root))
567
cache <- file.path(root, "cache")
568
}
569
570
if (!nzchar(cache)) {
571
tools <- asNamespace("tools")
572
if (is.function(tools$R_user_dir)) {
573
root <- tools$R_user_dir("renv", "cache")
574
cache <- file.path(root, "cache")
575
}
576
}
577
578
# start completing path to cache
579
file.path(
580
cache,
581
renv_bootstrap_cache_version(),
582
renv_bootstrap_platform_prefix(),
583
"renv",
584
version,
585
md5,
586
"renv"
587
)
588
589
}
590
591
renv_bootstrap_download_tarball <- function(version) {
592
593
# if the user has provided the path to a tarball via
594
# an environment variable, then use it
595
tarball <- Sys.getenv("RENV_BOOTSTRAP_TARBALL", unset = NA)
596
if (is.na(tarball))
597
return()
598
599
# allow directories
600
if (dir.exists(tarball)) {
601
name <- sprintf("renv_%s.tar.gz", version)
602
tarball <- file.path(tarball, name)
603
}
604
605
# bail if it doesn't exist
606
if (!file.exists(tarball)) {
607
608
# let the user know we weren't able to honour their request
609
fmt <- "- RENV_BOOTSTRAP_TARBALL is set (%s) but does not exist."
610
msg <- sprintf(fmt, tarball)
611
warning(msg)
612
613
# bail
614
return()
615
616
}
617
618
catf("- Using local tarball '%s'.", tarball)
619
tarball
620
621
}
622
623
renv_bootstrap_github_token <- function() {
624
for (envvar in c("GITHUB_TOKEN", "GITHUB_PAT", "GH_TOKEN")) {
625
envval <- Sys.getenv(envvar, unset = NA)
626
if (!is.na(envval))
627
return(envval)
628
}
629
}
630
631
renv_bootstrap_download_github <- function(version) {
632
633
enabled <- Sys.getenv("RENV_BOOTSTRAP_FROM_GITHUB", unset = "TRUE")
634
if (!identical(enabled, "TRUE"))
635
return(FALSE)
636
637
# prepare download options
638
token <- renv_bootstrap_github_token()
639
if (is.null(token))
640
token <- ""
641
642
if (nzchar(Sys.which("curl")) && nzchar(token)) {
643
fmt <- "--location --fail --header \"Authorization: token %s\""
644
extra <- sprintf(fmt, token)
645
saved <- options("download.file.method", "download.file.extra")
646
options(download.file.method = "curl", download.file.extra = extra)
647
on.exit(do.call(base::options, saved), add = TRUE)
648
} else if (nzchar(Sys.which("wget")) && nzchar(token)) {
649
fmt <- "--header=\"Authorization: token %s\""
650
extra <- sprintf(fmt, token)
651
saved <- options("download.file.method", "download.file.extra")
652
options(download.file.method = "wget", download.file.extra = extra)
653
on.exit(do.call(base::options, saved), add = TRUE)
654
}
655
656
url <- file.path("https://api.github.com/repos/rstudio/renv/tarball", version)
657
name <- sprintf("renv_%s.tar.gz", version)
658
destfile <- file.path(tempdir(), name)
659
660
status <- tryCatch(
661
renv_bootstrap_download_impl(url, destfile),
662
condition = identity
663
)
664
665
if (!identical(status, 0L))
666
return(FALSE)
667
668
renv_bootstrap_download_augment(destfile)
669
670
return(destfile)
671
672
}
673
674
# Add Sha to DESCRIPTION. This is stop gap until #890, after which we
675
# can use renv::install() to fully capture metadata.
676
renv_bootstrap_download_augment <- function(destfile) {
677
sha <- renv_bootstrap_git_extract_sha1_tar(destfile)
678
if (is.null(sha)) {
679
return()
680
}
681
682
# Untar
683
tempdir <- tempfile("renv-github-")
684
on.exit(unlink(tempdir, recursive = TRUE), add = TRUE)
685
untar(destfile, exdir = tempdir)
686
pkgdir <- dir(tempdir, full.names = TRUE)[[1]]
687
688
# Modify description
689
desc_path <- file.path(pkgdir, "DESCRIPTION")
690
desc_lines <- readLines(desc_path)
691
remotes_fields <- c(
692
"RemoteType: github",
693
"RemoteHost: api.github.com",
694
"RemoteRepo: renv",
695
"RemoteUsername: rstudio",
696
"RemotePkgRef: rstudio/renv",
697
paste("RemoteRef: ", sha),
698
paste("RemoteSha: ", sha)
699
)
700
writeLines(c(desc_lines[desc_lines != ""], remotes_fields), con = desc_path)
701
702
# Re-tar
703
local({
704
old <- setwd(tempdir)
705
on.exit(setwd(old), add = TRUE)
706
707
tar(destfile, compression = "gzip")
708
})
709
invisible()
710
}
711
712
# Extract the commit hash from a git archive. Git archives include the SHA1
713
# hash as the comment field of the tarball pax extended header
714
# (see https://www.kernel.org/pub/software/scm/git/docs/git-archive.html)
715
# For GitHub archives this should be the first header after the default one
716
# (512 byte) header.
717
renv_bootstrap_git_extract_sha1_tar <- function(bundle) {
718
719
# open the bundle for reading
720
# We use gzcon for everything because (from ?gzcon)
721
# > Reading from a connection which does not supply a 'gzip' magic
722
# > header is equivalent to reading from the original connection
723
conn <- gzcon(file(bundle, open = "rb", raw = TRUE))
724
on.exit(close(conn))
725
726
# The default pax header is 512 bytes long and the first pax extended header
727
# with the comment should be 51 bytes long
728
# `52 comment=` (11 chars) + 40 byte SHA1 hash
729
len <- 0x200 + 0x33
730
res <- rawToChar(readBin(conn, "raw", n = len)[0x201:len])
731
732
if (grepl("^52 comment=", res)) {
733
sub("52 comment=", "", res)
734
} else {
735
NULL
736
}
737
}
738
739
renv_bootstrap_install <- function(version, tarball, library) {
740
741
# attempt to install it into project library
742
dir.create(library, showWarnings = FALSE, recursive = TRUE)
743
output <- renv_bootstrap_install_impl(library, tarball)
744
745
# check for successful install
746
status <- attr(output, "status")
747
if (is.null(status) || identical(status, 0L))
748
return(status)
749
750
# an error occurred; report it
751
header <- "installation of renv failed"
752
lines <- paste(rep.int("=", nchar(header)), collapse = "")
753
text <- paste(c(header, lines, output), collapse = "\n")
754
stop(text)
755
756
}
757
758
renv_bootstrap_install_impl <- function(library, tarball) {
759
760
# invoke using system2 so we can capture and report output
761
bin <- R.home("bin")
762
exe <- if (Sys.info()[["sysname"]] == "Windows") "R.exe" else "R"
763
R <- file.path(bin, exe)
764
765
args <- c(
766
"--vanilla", "CMD", "INSTALL", "--no-multiarch",
767
"-l", shQuote(path.expand(library)),
768
shQuote(path.expand(tarball))
769
)
770
771
system2(R, args, stdout = TRUE, stderr = TRUE)
772
773
}
774
775
renv_bootstrap_platform_prefix_default <- function() {
776
777
# read version component
778
version <- Sys.getenv("RENV_PATHS_VERSION", unset = "R-%v")
779
780
# expand placeholders
781
placeholders <- list(
782
list("%v", format(getRversion()[1, 1:2])),
783
list("%V", format(getRversion()[1, 1:3]))
784
)
785
786
for (placeholder in placeholders)
787
version <- gsub(placeholder[[1L]], placeholder[[2L]], version, fixed = TRUE)
788
789
# include SVN revision for development versions of R
790
# (to avoid sharing platform-specific artefacts with released versions of R)
791
devel <-
792
identical(R.version[["status"]], "Under development (unstable)") ||
793
identical(R.version[["nickname"]], "Unsuffered Consequences")
794
795
if (devel)
796
version <- paste(version, R.version[["svn rev"]], sep = "-r")
797
798
version
799
800
}
801
802
renv_bootstrap_platform_prefix <- function() {
803
804
# construct version prefix
805
version <- renv_bootstrap_platform_prefix_default()
806
807
# build list of path components
808
components <- c(version, R.version$platform)
809
810
# include prefix if provided by user
811
prefix <- renv_bootstrap_platform_prefix_impl()
812
if (!is.na(prefix) && nzchar(prefix))
813
components <- c(prefix, components)
814
815
# build prefix
816
paste(components, collapse = "/")
817
818
}
819
820
renv_bootstrap_platform_prefix_impl <- function() {
821
822
# if an explicit prefix has been supplied, use it
823
prefix <- Sys.getenv("RENV_PATHS_PREFIX", unset = NA)
824
if (!is.na(prefix))
825
return(prefix)
826
827
# if the user has requested an automatic prefix, generate it
828
auto <- Sys.getenv("RENV_PATHS_PREFIX_AUTO", unset = NA)
829
if (is.na(auto) && getRversion() >= "4.4.0")
830
auto <- "TRUE"
831
832
if (auto %in% c("TRUE", "True", "true", "1"))
833
return(renv_bootstrap_platform_prefix_auto())
834
835
# empty string on failure
836
""
837
838
}
839
840
renv_bootstrap_platform_prefix_auto <- function() {
841
842
prefix <- tryCatch(renv_bootstrap_platform_os(), error = identity)
843
if (inherits(prefix, "error") || prefix %in% "unknown") {
844
845
msg <- paste(
846
"failed to infer current operating system",
847
"please file a bug report at https://github.com/rstudio/renv/issues",
848
sep = "; "
849
)
850
851
warning(msg)
852
853
}
854
855
prefix
856
857
}
858
859
renv_bootstrap_platform_os <- function() {
860
861
sysinfo <- Sys.info()
862
sysname <- sysinfo[["sysname"]]
863
864
# handle Windows + macOS up front
865
if (sysname == "Windows")
866
return("windows")
867
else if (sysname == "Darwin")
868
return("macos")
869
870
# check for os-release files
871
for (file in c("/etc/os-release", "/usr/lib/os-release"))
872
if (file.exists(file))
873
return(renv_bootstrap_platform_os_via_os_release(file, sysinfo))
874
875
# check for redhat-release files
876
if (file.exists("/etc/redhat-release"))
877
return(renv_bootstrap_platform_os_via_redhat_release())
878
879
"unknown"
880
881
}
882
883
renv_bootstrap_platform_os_via_os_release <- function(file, sysinfo) {
884
885
# read /etc/os-release
886
release <- utils::read.table(
887
file = file,
888
sep = "=",
889
quote = c("\"", "'"),
890
col.names = c("Key", "Value"),
891
comment.char = "#",
892
stringsAsFactors = FALSE
893
)
894
895
vars <- as.list(release$Value)
896
names(vars) <- release$Key
897
898
# get os name
899
os <- tolower(sysinfo[["sysname"]])
900
901
# read id
902
id <- "unknown"
903
for (field in c("ID", "ID_LIKE")) {
904
if (field %in% names(vars) && nzchar(vars[[field]])) {
905
id <- vars[[field]]
906
break
907
}
908
}
909
910
# read version
911
version <- "unknown"
912
for (field in c("UBUNTU_CODENAME", "VERSION_CODENAME", "VERSION_ID", "BUILD_ID")) {
913
if (field %in% names(vars) && nzchar(vars[[field]])) {
914
version <- vars[[field]]
915
break
916
}
917
}
918
919
# join together
920
paste(c(os, id, version), collapse = "-")
921
922
}
923
924
renv_bootstrap_platform_os_via_redhat_release <- function() {
925
926
# read /etc/redhat-release
927
contents <- readLines("/etc/redhat-release", warn = FALSE)
928
929
# infer id
930
id <- if (grepl("centos", contents, ignore.case = TRUE))
931
"centos"
932
else if (grepl("redhat", contents, ignore.case = TRUE))
933
"redhat"
934
else
935
"unknown"
936
937
# try to find a version component (very hacky)
938
version <- "unknown"
939
940
parts <- strsplit(contents, "[[:space:]]")[[1L]]
941
for (part in parts) {
942
943
nv <- tryCatch(numeric_version(part), error = identity)
944
if (inherits(nv, "error"))
945
next
946
947
version <- nv[1, 1]
948
break
949
950
}
951
952
paste(c("linux", id, version), collapse = "-")
953
954
}
955
956
renv_bootstrap_library_root_name <- function(project) {
957
958
# use project name as-is if requested
959
asis <- Sys.getenv("RENV_PATHS_LIBRARY_ROOT_ASIS", unset = "FALSE")
960
if (asis)
961
return(basename(project))
962
963
# otherwise, disambiguate based on project's path
964
id <- substring(renv_bootstrap_hash_text(project), 1L, 8L)
965
paste(basename(project), id, sep = "-")
966
967
}
968
969
renv_bootstrap_library_root <- function(project) {
970
971
prefix <- renv_bootstrap_profile_prefix()
972
973
path <- Sys.getenv("RENV_PATHS_LIBRARY", unset = NA)
974
if (!is.na(path))
975
return(paste(c(path, prefix), collapse = "/"))
976
977
path <- renv_bootstrap_library_root_impl(project)
978
if (!is.null(path)) {
979
name <- renv_bootstrap_library_root_name(project)
980
return(paste(c(path, prefix, name), collapse = "/"))
981
}
982
983
renv_bootstrap_paths_renv("library", project = project)
984
985
}
986
987
renv_bootstrap_library_root_impl <- function(project) {
988
989
root <- Sys.getenv("RENV_PATHS_LIBRARY_ROOT", unset = NA)
990
if (!is.na(root))
991
return(root)
992
993
type <- renv_bootstrap_project_type(project)
994
if (identical(type, "package")) {
995
userdir <- renv_bootstrap_user_dir()
996
return(file.path(userdir, "library"))
997
}
998
999
}
1000
1001
renv_bootstrap_validate_version <- function(version, description = NULL) {
1002
1003
# resolve description file
1004
#
1005
# avoid passing lib.loc to `packageDescription()` below, since R will
1006
# use the loaded version of the package by default anyhow. note that
1007
# this function should only be called after 'renv' is loaded
1008
# https://github.com/rstudio/renv/issues/1625
1009
description <- description %||% packageDescription("renv")
1010
1011
# check whether requested version 'version' matches loaded version of renv
1012
sha <- attr(version, "sha", exact = TRUE)
1013
valid <- if (!is.null(sha))
1014
renv_bootstrap_validate_version_dev(sha, description)
1015
else
1016
renv_bootstrap_validate_version_release(version, description)
1017
1018
if (valid)
1019
return(TRUE)
1020
1021
# the loaded version of renv doesn't match the requested version;
1022
# give the user instructions on how to proceed
1023
dev <- identical(description[["RemoteType"]], "github")
1024
remote <- if (dev)
1025
paste("rstudio/renv", description[["RemoteSha"]], sep = "@")
1026
else
1027
paste("renv", description[["Version"]], sep = "@")
1028
1029
# display both loaded version + sha if available
1030
friendly <- renv_bootstrap_version_friendly(
1031
version = description[["Version"]],
1032
sha = if (dev) description[["RemoteSha"]]
1033
)
1034
1035
fmt <- heredoc("
1036
renv %1$s was loaded from project library, but this project is configured to use renv %2$s.
1037
- Use `renv::record(\"%3$s\")` to record renv %1$s in the lockfile.
1038
- Use `renv::restore(packages = \"renv\")` to install renv %2$s into the project library.
1039
")
1040
catf(fmt, friendly, renv_bootstrap_version_friendly(version), remote)
1041
1042
FALSE
1043
1044
}
1045
1046
renv_bootstrap_validate_version_dev <- function(version, description) {
1047
1048
expected <- description[["RemoteSha"]]
1049
if (!is.character(expected))
1050
return(FALSE)
1051
1052
pattern <- sprintf("^\\Q%s\\E", version)
1053
grepl(pattern, expected, perl = TRUE)
1054
1055
}
1056
1057
renv_bootstrap_validate_version_release <- function(version, description) {
1058
expected <- description[["Version"]]
1059
is.character(expected) && identical(c(expected), c(version))
1060
}
1061
1062
renv_bootstrap_hash_text <- function(text) {
1063
1064
hashfile <- tempfile("renv-hash-")
1065
on.exit(unlink(hashfile), add = TRUE)
1066
1067
writeLines(text, con = hashfile)
1068
tools::md5sum(hashfile)
1069
1070
}
1071
1072
renv_bootstrap_load <- function(project, libpath, version) {
1073
1074
# try to load renv from the project library
1075
if (!requireNamespace("renv", lib.loc = libpath, quietly = TRUE))
1076
return(FALSE)
1077
1078
# warn if the version of renv loaded does not match
1079
renv_bootstrap_validate_version(version)
1080
1081
# execute renv load hooks, if any
1082
hooks <- getHook("renv::autoload")
1083
for (hook in hooks)
1084
if (is.function(hook))
1085
tryCatch(hook(), error = warnify)
1086
1087
# load the project
1088
renv::load(project)
1089
1090
TRUE
1091
1092
}
1093
1094
renv_bootstrap_profile_load <- function(project) {
1095
1096
# if RENV_PROFILE is already set, just use that
1097
profile <- Sys.getenv("RENV_PROFILE", unset = NA)
1098
if (!is.na(profile) && nzchar(profile))
1099
return(profile)
1100
1101
# check for a profile file (nothing to do if it doesn't exist)
1102
path <- renv_bootstrap_paths_renv("profile", profile = FALSE, project = project)
1103
if (!file.exists(path))
1104
return(NULL)
1105
1106
# read the profile, and set it if it exists
1107
contents <- readLines(path, warn = FALSE)
1108
if (length(contents) == 0L)
1109
return(NULL)
1110
1111
# set RENV_PROFILE
1112
profile <- contents[[1L]]
1113
if (!profile %in% c("", "default"))
1114
Sys.setenv(RENV_PROFILE = profile)
1115
1116
profile
1117
1118
}
1119
1120
renv_bootstrap_profile_prefix <- function() {
1121
profile <- renv_bootstrap_profile_get()
1122
if (!is.null(profile))
1123
return(file.path("profiles", profile, "renv"))
1124
}
1125
1126
renv_bootstrap_profile_get <- function() {
1127
profile <- Sys.getenv("RENV_PROFILE", unset = "")
1128
renv_bootstrap_profile_normalize(profile)
1129
}
1130
1131
renv_bootstrap_profile_set <- function(profile) {
1132
profile <- renv_bootstrap_profile_normalize(profile)
1133
if (is.null(profile))
1134
Sys.unsetenv("RENV_PROFILE")
1135
else
1136
Sys.setenv(RENV_PROFILE = profile)
1137
}
1138
1139
renv_bootstrap_profile_normalize <- function(profile) {
1140
1141
if (is.null(profile) || profile %in% c("", "default"))
1142
return(NULL)
1143
1144
profile
1145
1146
}
1147
1148
renv_bootstrap_path_absolute <- function(path) {
1149
1150
substr(path, 1L, 1L) %in% c("~", "/", "\\") || (
1151
substr(path, 1L, 1L) %in% c(letters, LETTERS) &&
1152
substr(path, 2L, 3L) %in% c(":/", ":\\")
1153
)
1154
1155
}
1156
1157
renv_bootstrap_paths_renv <- function(..., profile = TRUE, project = NULL) {
1158
renv <- Sys.getenv("RENV_PATHS_RENV", unset = "renv")
1159
root <- if (renv_bootstrap_path_absolute(renv)) NULL else project
1160
prefix <- if (profile) renv_bootstrap_profile_prefix()
1161
components <- c(root, renv, prefix, ...)
1162
paste(components, collapse = "/")
1163
}
1164
1165
renv_bootstrap_project_type <- function(path) {
1166
1167
descpath <- file.path(path, "DESCRIPTION")
1168
if (!file.exists(descpath))
1169
return("unknown")
1170
1171
desc <- tryCatch(
1172
read.dcf(descpath, all = TRUE),
1173
error = identity
1174
)
1175
1176
if (inherits(desc, "error"))
1177
return("unknown")
1178
1179
type <- desc$Type
1180
if (!is.null(type))
1181
return(tolower(type))
1182
1183
package <- desc$Package
1184
if (!is.null(package))
1185
return("package")
1186
1187
"unknown"
1188
1189
}
1190
1191
renv_bootstrap_user_dir <- function() {
1192
dir <- renv_bootstrap_user_dir_impl()
1193
path.expand(chartr("\\", "/", dir))
1194
}
1195
1196
renv_bootstrap_user_dir_impl <- function() {
1197
1198
# use local override if set
1199
override <- getOption("renv.userdir.override")
1200
if (!is.null(override))
1201
return(override)
1202
1203
# use R_user_dir if available
1204
tools <- asNamespace("tools")
1205
if (is.function(tools$R_user_dir))
1206
return(tools$R_user_dir("renv", "cache"))
1207
1208
# try using our own backfill for older versions of R
1209
envvars <- c("R_USER_CACHE_DIR", "XDG_CACHE_HOME")
1210
for (envvar in envvars) {
1211
root <- Sys.getenv(envvar, unset = NA)
1212
if (!is.na(root))
1213
return(file.path(root, "R/renv"))
1214
}
1215
1216
# use platform-specific default fallbacks
1217
if (Sys.info()[["sysname"]] == "Windows")
1218
file.path(Sys.getenv("LOCALAPPDATA"), "R/cache/R/renv")
1219
else if (Sys.info()[["sysname"]] == "Darwin")
1220
"~/Library/Caches/org.R-project.R/R/renv"
1221
else
1222
"~/.cache/R/renv"
1223
1224
}
1225
1226
renv_bootstrap_version_friendly <- function(version, shafmt = NULL, sha = NULL) {
1227
sha <- sha %||% attr(version, "sha", exact = TRUE)
1228
parts <- c(version, sprintf(shafmt %||% " [sha: %s]", substring(sha, 1L, 7L)))
1229
paste(parts, collapse = "")
1230
}
1231
1232
renv_bootstrap_exec <- function(project, libpath, version) {
1233
if (!renv_bootstrap_load(project, libpath, version))
1234
renv_bootstrap_run(project, libpath, version)
1235
}
1236
1237
renv_bootstrap_run <- function(project, libpath, version) {
1238
tryCatch(
1239
renv_bootstrap_run_impl(project, libpath, version),
1240
error = function(e) {
1241
msg <- paste(
1242
"failed to bootstrap renv: the project will not be loaded.",
1243
paste("Reason:", conditionMessage(e)),
1244
"Use `renv::activate()` to re-initialize the project.",
1245
sep = "\n"
1246
)
1247
warning(msg, call. = FALSE)
1248
}
1249
)
1250
}
1251
1252
renv_bootstrap_run_impl <- function(project, libpath, version) {
1253
1254
# perform bootstrap
1255
bootstrap(version, libpath)
1256
1257
# exit early if we're just testing bootstrap
1258
if (!is.na(Sys.getenv("RENV_BOOTSTRAP_INSTALL_ONLY", unset = NA)))
1259
return(TRUE)
1260
1261
# try again to load
1262
if (requireNamespace("renv", lib.loc = libpath, quietly = TRUE)) {
1263
return(renv::load(project = project))
1264
}
1265
1266
# failed to download or load renv; warn the user
1267
msg <- c(
1268
"Failed to find an renv installation: the project will not be loaded.",
1269
"Use `renv::activate()` to re-initialize the project."
1270
)
1271
1272
warning(paste(msg, collapse = "\n"), call. = FALSE)
1273
1274
}
1275
1276
renv_bootstrap_cache_version <- function() {
1277
# NOTE: users should normally not override the cache version;
1278
# this is provided just to make testing easier
1279
Sys.getenv("RENV_CACHE_VERSION", unset = "v5")
1280
}
1281
1282
renv_bootstrap_cache_version_previous <- function() {
1283
version <- renv_bootstrap_cache_version()
1284
number <- as.integer(substring(version, 2L))
1285
paste("v", number - 1L, sep = "")
1286
}
1287
1288
renv_json_read <- function(file = NULL, text = NULL) {
1289
1290
jlerr <- NULL
1291
1292
# if jsonlite is loaded, use that instead
1293
if ("jsonlite" %in% loadedNamespaces()) {
1294
1295
json <- tryCatch(renv_json_read_jsonlite(file, text), error = identity)
1296
if (!inherits(json, "error"))
1297
return(json)
1298
1299
jlerr <- json
1300
1301
}
1302
1303
# otherwise, fall back to the default JSON reader
1304
json <- tryCatch(renv_json_read_default(file, text), error = identity)
1305
if (!inherits(json, "error"))
1306
return(json)
1307
1308
# report an error
1309
if (!is.null(jlerr))
1310
stop(jlerr)
1311
else
1312
stop(json)
1313
1314
}
1315
1316
renv_json_read_jsonlite <- function(file = NULL, text = NULL) {
1317
text <- paste(text %||% readLines(file, warn = FALSE), collapse = "\n")
1318
jsonlite::fromJSON(txt = text, simplifyVector = FALSE)
1319
}
1320
1321
renv_json_read_patterns <- function() {
1322
1323
list(
1324
1325
# objects
1326
list("{", "\t\n\tobject(\t\n\t", TRUE),
1327
list("}", "\t\n\t)\t\n\t", TRUE),
1328
1329
# arrays
1330
list("[", "\t\n\tarray(\t\n\t", TRUE),
1331
list("]", "\n\t\n)\n\t\n", TRUE),
1332
1333
# maps
1334
list(":", "\t\n\t=\t\n\t", TRUE),
1335
1336
# newlines
1337
list("\\u000a", "\n", FALSE)
1338
1339
)
1340
1341
}
1342
1343
renv_json_read_envir <- function() {
1344
1345
envir <- new.env(parent = emptyenv())
1346
1347
envir[["+"]] <- `+`
1348
envir[["-"]] <- `-`
1349
1350
envir[["object"]] <- function(...) {
1351
result <- list(...)
1352
names(result) <- as.character(names(result))
1353
result
1354
}
1355
1356
envir[["array"]] <- list
1357
1358
envir[["true"]] <- TRUE
1359
envir[["false"]] <- FALSE
1360
envir[["null"]] <- NULL
1361
1362
envir
1363
1364
}
1365
1366
renv_json_read_remap <- function(object, patterns) {
1367
1368
# repair names if necessary
1369
if (!is.null(names(object))) {
1370
1371
nms <- names(object)
1372
for (pattern in patterns)
1373
nms <- gsub(pattern[[2L]], pattern[[1L]], nms, fixed = TRUE)
1374
names(object) <- nms
1375
1376
}
1377
1378
# repair strings if necessary
1379
if (is.character(object)) {
1380
for (pattern in patterns)
1381
object <- gsub(pattern[[2L]], pattern[[1L]], object, fixed = TRUE)
1382
}
1383
1384
# recurse for other objects
1385
if (is.recursive(object))
1386
for (i in seq_along(object))
1387
object[i] <- list(renv_json_read_remap(object[[i]], patterns))
1388
1389
# return remapped object
1390
object
1391
1392
}
1393
1394
renv_json_read_default <- function(file = NULL, text = NULL) {
1395
1396
# read json text
1397
text <- paste(text %||% readLines(file, warn = FALSE), collapse = "\n")
1398
1399
# convert into something the R parser will understand
1400
patterns <- renv_json_read_patterns()
1401
transformed <- text
1402
for (pattern in patterns)
1403
transformed <- gsub(pattern[[1L]], pattern[[2L]], transformed, fixed = TRUE)
1404
1405
# parse it
1406
rfile <- tempfile("renv-json-", fileext = ".R")
1407
on.exit(unlink(rfile), add = TRUE)
1408
writeLines(transformed, con = rfile)
1409
json <- parse(rfile, keep.source = FALSE, srcfile = NULL)[[1L]]
1410
1411
# evaluate in safe environment
1412
result <- eval(json, envir = renv_json_read_envir())
1413
1414
# fix up strings if necessary -- do so only with reversible patterns
1415
patterns <- Filter(function(pattern) pattern[[3L]], patterns)
1416
renv_json_read_remap(result, patterns)
1417
1418
}
1419
1420
1421
# load the renv profile, if any
1422
renv_bootstrap_profile_load(project)
1423
1424
# construct path to library root
1425
root <- renv_bootstrap_library_root(project)
1426
1427
# construct library prefix for platform
1428
prefix <- renv_bootstrap_platform_prefix()
1429
1430
# construct full libpath
1431
libpath <- file.path(root, prefix)
1432
1433
# run bootstrap code
1434
renv_bootstrap_exec(project, libpath, version)
1435
1436
invisible()
1437
1438
})
1439
1440