You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1202 satır
33KB

  1. local({
  2. # the requested version of renv
  3. version <- "1.0.5"
  4. attr(version, "sha") <- NULL
  5. # the project directory
  6. project <- getwd()
  7. # use start-up diagnostics if enabled
  8. diagnostics <- Sys.getenv("RENV_STARTUP_DIAGNOSTICS", unset = "FALSE")
  9. if (diagnostics) {
  10. start <- Sys.time()
  11. profile <- tempfile("renv-startup-", fileext = ".Rprof")
  12. utils::Rprof(profile)
  13. on.exit({
  14. utils::Rprof(NULL)
  15. elapsed <- signif(difftime(Sys.time(), start, units = "auto"), digits = 2L)
  16. writeLines(sprintf("- renv took %s to run the autoloader.", format(elapsed)))
  17. writeLines(sprintf("- Profile: %s", profile))
  18. print(utils::summaryRprof(profile))
  19. }, add = TRUE)
  20. }
  21. # figure out whether the autoloader is enabled
  22. enabled <- local({
  23. # first, check config option
  24. override <- getOption("renv.config.autoloader.enabled")
  25. if (!is.null(override))
  26. return(override)
  27. # if we're being run in a context where R_LIBS is already set,
  28. # don't load -- presumably we're being run as a sub-process and
  29. # the parent process has already set up library paths for us
  30. rcmd <- Sys.getenv("R_CMD", unset = NA)
  31. rlibs <- Sys.getenv("R_LIBS", unset = NA)
  32. if (!is.na(rlibs) && !is.na(rcmd))
  33. return(FALSE)
  34. # next, check environment variables
  35. # TODO: prefer using the configuration one in the future
  36. envvars <- c(
  37. "RENV_CONFIG_AUTOLOADER_ENABLED",
  38. "RENV_AUTOLOADER_ENABLED",
  39. "RENV_ACTIVATE_PROJECT"
  40. )
  41. for (envvar in envvars) {
  42. envval <- Sys.getenv(envvar, unset = NA)
  43. if (!is.na(envval))
  44. return(tolower(envval) %in% c("true", "t", "1"))
  45. }
  46. # enable by default
  47. TRUE
  48. })
  49. # bail if we're not enabled
  50. if (!enabled) {
  51. # if we're not enabled, we might still need to manually load
  52. # the user profile here
  53. profile <- Sys.getenv("R_PROFILE_USER", unset = "~/.Rprofile")
  54. if (file.exists(profile)) {
  55. cfg <- Sys.getenv("RENV_CONFIG_USER_PROFILE", unset = "TRUE")
  56. if (tolower(cfg) %in% c("true", "t", "1"))
  57. sys.source(profile, envir = globalenv())
  58. }
  59. return(FALSE)
  60. }
  61. # avoid recursion
  62. if (identical(getOption("renv.autoloader.running"), TRUE)) {
  63. warning("ignoring recursive attempt to run renv autoloader")
  64. return(invisible(TRUE))
  65. }
  66. # signal that we're loading renv during R startup
  67. options(renv.autoloader.running = TRUE)
  68. on.exit(options(renv.autoloader.running = NULL), add = TRUE)
  69. # signal that we've consented to use renv
  70. options(renv.consent = TRUE)
  71. # load the 'utils' package eagerly -- this ensures that renv shims, which
  72. # mask 'utils' packages, will come first on the search path
  73. library(utils, lib.loc = .Library)
  74. # unload renv if it's already been loaded
  75. if ("renv" %in% loadedNamespaces())
  76. unloadNamespace("renv")
  77. # load bootstrap tools
  78. `%||%` <- function(x, y) {
  79. if (is.null(x)) y else x
  80. }
  81. catf <- function(fmt, ..., appendLF = TRUE) {
  82. quiet <- getOption("renv.bootstrap.quiet", default = FALSE)
  83. if (quiet)
  84. return(invisible())
  85. msg <- sprintf(fmt, ...)
  86. cat(msg, file = stdout(), sep = if (appendLF) "\n" else "")
  87. invisible(msg)
  88. }
  89. header <- function(label,
  90. ...,
  91. prefix = "#",
  92. suffix = "-",
  93. n = min(getOption("width"), 78))
  94. {
  95. label <- sprintf(label, ...)
  96. n <- max(n - nchar(label) - nchar(prefix) - 2L, 8L)
  97. if (n <= 0)
  98. return(paste(prefix, label))
  99. tail <- paste(rep.int(suffix, n), collapse = "")
  100. paste0(prefix, " ", label, " ", tail)
  101. }
  102. startswith <- function(string, prefix) {
  103. substring(string, 1, nchar(prefix)) == prefix
  104. }
  105. bootstrap <- function(version, library) {
  106. friendly <- renv_bootstrap_version_friendly(version)
  107. section <- header(sprintf("Bootstrapping renv %s", friendly))
  108. catf(section)
  109. # attempt to download renv
  110. catf("- Downloading renv ... ", appendLF = FALSE)
  111. withCallingHandlers(
  112. tarball <- renv_bootstrap_download(version),
  113. error = function(err) {
  114. catf("FAILED")
  115. stop("failed to download:\n", conditionMessage(err))
  116. }
  117. )
  118. catf("OK")
  119. on.exit(unlink(tarball), add = TRUE)
  120. # now attempt to install
  121. catf("- Installing renv ... ", appendLF = FALSE)
  122. withCallingHandlers(
  123. status <- renv_bootstrap_install(version, tarball, library),
  124. error = function(err) {
  125. catf("FAILED")
  126. stop("failed to install:\n", conditionMessage(err))
  127. }
  128. )
  129. catf("OK")
  130. # add empty line to break up bootstrapping from normal output
  131. catf("")
  132. return(invisible())
  133. }
  134. renv_bootstrap_tests_running <- function() {
  135. getOption("renv.tests.running", default = FALSE)
  136. }
  137. renv_bootstrap_repos <- function() {
  138. # get CRAN repository
  139. cran <- getOption("renv.repos.cran", "https://cloud.r-project.org")
  140. # check for repos override
  141. repos <- Sys.getenv("RENV_CONFIG_REPOS_OVERRIDE", unset = NA)
  142. if (!is.na(repos)) {
  143. # check for RSPM; if set, use a fallback repository for renv
  144. rspm <- Sys.getenv("RSPM", unset = NA)
  145. if (identical(rspm, repos))
  146. repos <- c(RSPM = rspm, CRAN = cran)
  147. return(repos)
  148. }
  149. # check for lockfile repositories
  150. repos <- tryCatch(renv_bootstrap_repos_lockfile(), error = identity)
  151. if (!inherits(repos, "error") && length(repos))
  152. return(repos)
  153. # retrieve current repos
  154. repos <- getOption("repos")
  155. # ensure @CRAN@ entries are resolved
  156. repos[repos == "@CRAN@"] <- cran
  157. # add in renv.bootstrap.repos if set
  158. default <- c(FALLBACK = "https://cloud.r-project.org")
  159. extra <- getOption("renv.bootstrap.repos", default = default)
  160. repos <- c(repos, extra)
  161. # remove duplicates that might've snuck in
  162. dupes <- duplicated(repos) | duplicated(names(repos))
  163. repos[!dupes]
  164. }
  165. renv_bootstrap_repos_lockfile <- function() {
  166. lockpath <- Sys.getenv("RENV_PATHS_LOCKFILE", unset = "renv.lock")
  167. if (!file.exists(lockpath))
  168. return(NULL)
  169. lockfile <- tryCatch(renv_json_read(lockpath), error = identity)
  170. if (inherits(lockfile, "error")) {
  171. warning(lockfile)
  172. return(NULL)
  173. }
  174. repos <- lockfile$R$Repositories
  175. if (length(repos) == 0)
  176. return(NULL)
  177. keys <- vapply(repos, `[[`, "Name", FUN.VALUE = character(1))
  178. vals <- vapply(repos, `[[`, "URL", FUN.VALUE = character(1))
  179. names(vals) <- keys
  180. return(vals)
  181. }
  182. renv_bootstrap_download <- function(version) {
  183. sha <- attr(version, "sha", exact = TRUE)
  184. methods <- if (!is.null(sha)) {
  185. # attempting to bootstrap a development version of renv
  186. c(
  187. function() renv_bootstrap_download_tarball(sha),
  188. function() renv_bootstrap_download_github(sha)
  189. )
  190. } else {
  191. # attempting to bootstrap a release version of renv
  192. c(
  193. function() renv_bootstrap_download_tarball(version),
  194. function() renv_bootstrap_download_cran_latest(version),
  195. function() renv_bootstrap_download_cran_archive(version)
  196. )
  197. }
  198. for (method in methods) {
  199. path <- tryCatch(method(), error = identity)
  200. if (is.character(path) && file.exists(path))
  201. return(path)
  202. }
  203. stop("All download methods failed")
  204. }
  205. renv_bootstrap_download_impl <- function(url, destfile) {
  206. mode <- "wb"
  207. # https://bugs.r-project.org/bugzilla/show_bug.cgi?id=17715
  208. fixup <-
  209. Sys.info()[["sysname"]] == "Windows" &&
  210. substring(url, 1L, 5L) == "file:"
  211. if (fixup)
  212. mode <- "w+b"
  213. args <- list(
  214. url = url,
  215. destfile = destfile,
  216. mode = mode,
  217. quiet = TRUE
  218. )
  219. if ("headers" %in% names(formals(utils::download.file)))
  220. args$headers <- renv_bootstrap_download_custom_headers(url)
  221. do.call(utils::download.file, args)
  222. }
  223. renv_bootstrap_download_custom_headers <- function(url) {
  224. headers <- getOption("renv.download.headers")
  225. if (is.null(headers))
  226. return(character())
  227. if (!is.function(headers))
  228. stopf("'renv.download.headers' is not a function")
  229. headers <- headers(url)
  230. if (length(headers) == 0L)
  231. return(character())
  232. if (is.list(headers))
  233. headers <- unlist(headers, recursive = FALSE, use.names = TRUE)
  234. ok <-
  235. is.character(headers) &&
  236. is.character(names(headers)) &&
  237. all(nzchar(names(headers)))
  238. if (!ok)
  239. stop("invocation of 'renv.download.headers' did not return a named character vector")
  240. headers
  241. }
  242. renv_bootstrap_download_cran_latest <- function(version) {
  243. spec <- renv_bootstrap_download_cran_latest_find(version)
  244. type <- spec$type
  245. repos <- spec$repos
  246. baseurl <- utils::contrib.url(repos = repos, type = type)
  247. ext <- if (identical(type, "source"))
  248. ".tar.gz"
  249. else if (Sys.info()[["sysname"]] == "Windows")
  250. ".zip"
  251. else
  252. ".tgz"
  253. name <- sprintf("renv_%s%s", version, ext)
  254. url <- paste(baseurl, name, sep = "/")
  255. destfile <- file.path(tempdir(), name)
  256. status <- tryCatch(
  257. renv_bootstrap_download_impl(url, destfile),
  258. condition = identity
  259. )
  260. if (inherits(status, "condition"))
  261. return(FALSE)
  262. # report success and return
  263. destfile
  264. }
  265. renv_bootstrap_download_cran_latest_find <- function(version) {
  266. # check whether binaries are supported on this system
  267. binary <-
  268. getOption("renv.bootstrap.binary", default = TRUE) &&
  269. !identical(.Platform$pkgType, "source") &&
  270. !identical(getOption("pkgType"), "source") &&
  271. Sys.info()[["sysname"]] %in% c("Darwin", "Windows")
  272. types <- c(if (binary) "binary", "source")
  273. # iterate over types + repositories
  274. for (type in types) {
  275. for (repos in renv_bootstrap_repos()) {
  276. # retrieve package database
  277. db <- tryCatch(
  278. as.data.frame(
  279. utils::available.packages(type = type, repos = repos),
  280. stringsAsFactors = FALSE
  281. ),
  282. error = identity
  283. )
  284. if (inherits(db, "error"))
  285. next
  286. # check for compatible entry
  287. entry <- db[db$Package %in% "renv" & db$Version %in% version, ]
  288. if (nrow(entry) == 0)
  289. next
  290. # found it; return spec to caller
  291. spec <- list(entry = entry, type = type, repos = repos)
  292. return(spec)
  293. }
  294. }
  295. # if we got here, we failed to find renv
  296. fmt <- "renv %s is not available from your declared package repositories"
  297. stop(sprintf(fmt, version))
  298. }
  299. renv_bootstrap_download_cran_archive <- function(version) {
  300. name <- sprintf("renv_%s.tar.gz", version)
  301. repos <- renv_bootstrap_repos()
  302. urls <- file.path(repos, "src/contrib/Archive/renv", name)
  303. destfile <- file.path(tempdir(), name)
  304. for (url in urls) {
  305. status <- tryCatch(
  306. renv_bootstrap_download_impl(url, destfile),
  307. condition = identity
  308. )
  309. if (identical(status, 0L))
  310. return(destfile)
  311. }
  312. return(FALSE)
  313. }
  314. renv_bootstrap_download_tarball <- function(version) {
  315. # if the user has provided the path to a tarball via
  316. # an environment variable, then use it
  317. tarball <- Sys.getenv("RENV_BOOTSTRAP_TARBALL", unset = NA)
  318. if (is.na(tarball))
  319. return()
  320. # allow directories
  321. if (dir.exists(tarball)) {
  322. name <- sprintf("renv_%s.tar.gz", version)
  323. tarball <- file.path(tarball, name)
  324. }
  325. # bail if it doesn't exist
  326. if (!file.exists(tarball)) {
  327. # let the user know we weren't able to honour their request
  328. fmt <- "- RENV_BOOTSTRAP_TARBALL is set (%s) but does not exist."
  329. msg <- sprintf(fmt, tarball)
  330. warning(msg)
  331. # bail
  332. return()
  333. }
  334. catf("- Using local tarball '%s'.", tarball)
  335. tarball
  336. }
  337. renv_bootstrap_download_github <- function(version) {
  338. enabled <- Sys.getenv("RENV_BOOTSTRAP_FROM_GITHUB", unset = "TRUE")
  339. if (!identical(enabled, "TRUE"))
  340. return(FALSE)
  341. # prepare download options
  342. pat <- Sys.getenv("GITHUB_PAT")
  343. if (nzchar(Sys.which("curl")) && nzchar(pat)) {
  344. fmt <- "--location --fail --header \"Authorization: token %s\""
  345. extra <- sprintf(fmt, pat)
  346. saved <- options("download.file.method", "download.file.extra")
  347. options(download.file.method = "curl", download.file.extra = extra)
  348. on.exit(do.call(base::options, saved), add = TRUE)
  349. } else if (nzchar(Sys.which("wget")) && nzchar(pat)) {
  350. fmt <- "--header=\"Authorization: token %s\""
  351. extra <- sprintf(fmt, pat)
  352. saved <- options("download.file.method", "download.file.extra")
  353. options(download.file.method = "wget", download.file.extra = extra)
  354. on.exit(do.call(base::options, saved), add = TRUE)
  355. }
  356. url <- file.path("https://api.github.com/repos/rstudio/renv/tarball", version)
  357. name <- sprintf("renv_%s.tar.gz", version)
  358. destfile <- file.path(tempdir(), name)
  359. status <- tryCatch(
  360. renv_bootstrap_download_impl(url, destfile),
  361. condition = identity
  362. )
  363. if (!identical(status, 0L))
  364. return(FALSE)
  365. renv_bootstrap_download_augment(destfile)
  366. return(destfile)
  367. }
  368. # Add Sha to DESCRIPTION. This is stop gap until #890, after which we
  369. # can use renv::install() to fully capture metadata.
  370. renv_bootstrap_download_augment <- function(destfile) {
  371. sha <- renv_bootstrap_git_extract_sha1_tar(destfile)
  372. if (is.null(sha)) {
  373. return()
  374. }
  375. # Untar
  376. tempdir <- tempfile("renv-github-")
  377. on.exit(unlink(tempdir, recursive = TRUE), add = TRUE)
  378. untar(destfile, exdir = tempdir)
  379. pkgdir <- dir(tempdir, full.names = TRUE)[[1]]
  380. # Modify description
  381. desc_path <- file.path(pkgdir, "DESCRIPTION")
  382. desc_lines <- readLines(desc_path)
  383. remotes_fields <- c(
  384. "RemoteType: github",
  385. "RemoteHost: api.github.com",
  386. "RemoteRepo: renv",
  387. "RemoteUsername: rstudio",
  388. "RemotePkgRef: rstudio/renv",
  389. paste("RemoteRef: ", sha),
  390. paste("RemoteSha: ", sha)
  391. )
  392. writeLines(c(desc_lines[desc_lines != ""], remotes_fields), con = desc_path)
  393. # Re-tar
  394. local({
  395. old <- setwd(tempdir)
  396. on.exit(setwd(old), add = TRUE)
  397. tar(destfile, compression = "gzip")
  398. })
  399. invisible()
  400. }
  401. # Extract the commit hash from a git archive. Git archives include the SHA1
  402. # hash as the comment field of the tarball pax extended header
  403. # (see https://www.kernel.org/pub/software/scm/git/docs/git-archive.html)
  404. # For GitHub archives this should be the first header after the default one
  405. # (512 byte) header.
  406. renv_bootstrap_git_extract_sha1_tar <- function(bundle) {
  407. # open the bundle for reading
  408. # We use gzcon for everything because (from ?gzcon)
  409. # > Reading from a connection which does not supply a 'gzip' magic
  410. # > header is equivalent to reading from the original connection
  411. conn <- gzcon(file(bundle, open = "rb", raw = TRUE))
  412. on.exit(close(conn))
  413. # The default pax header is 512 bytes long and the first pax extended header
  414. # with the comment should be 51 bytes long
  415. # `52 comment=` (11 chars) + 40 byte SHA1 hash
  416. len <- 0x200 + 0x33
  417. res <- rawToChar(readBin(conn, "raw", n = len)[0x201:len])
  418. if (grepl("^52 comment=", res)) {
  419. sub("52 comment=", "", res)
  420. } else {
  421. NULL
  422. }
  423. }
  424. renv_bootstrap_install <- function(version, tarball, library) {
  425. # attempt to install it into project library
  426. dir.create(library, showWarnings = FALSE, recursive = TRUE)
  427. output <- renv_bootstrap_install_impl(library, tarball)
  428. # check for successful install
  429. status <- attr(output, "status")
  430. if (is.null(status) || identical(status, 0L))
  431. return(status)
  432. # an error occurred; report it
  433. header <- "installation of renv failed"
  434. lines <- paste(rep.int("=", nchar(header)), collapse = "")
  435. text <- paste(c(header, lines, output), collapse = "\n")
  436. stop(text)
  437. }
  438. renv_bootstrap_install_impl <- function(library, tarball) {
  439. # invoke using system2 so we can capture and report output
  440. bin <- R.home("bin")
  441. exe <- if (Sys.info()[["sysname"]] == "Windows") "R.exe" else "R"
  442. R <- file.path(bin, exe)
  443. args <- c(
  444. "--vanilla", "CMD", "INSTALL", "--no-multiarch",
  445. "-l", shQuote(path.expand(library)),
  446. shQuote(path.expand(tarball))
  447. )
  448. system2(R, args, stdout = TRUE, stderr = TRUE)
  449. }
  450. renv_bootstrap_platform_prefix <- function() {
  451. # construct version prefix
  452. version <- paste(R.version$major, R.version$minor, sep = ".")
  453. prefix <- paste("R", numeric_version(version)[1, 1:2], sep = "-")
  454. # include SVN revision for development versions of R
  455. # (to avoid sharing platform-specific artefacts with released versions of R)
  456. devel <-
  457. identical(R.version[["status"]], "Under development (unstable)") ||
  458. identical(R.version[["nickname"]], "Unsuffered Consequences")
  459. if (devel)
  460. prefix <- paste(prefix, R.version[["svn rev"]], sep = "-r")
  461. # build list of path components
  462. components <- c(prefix, R.version$platform)
  463. # include prefix if provided by user
  464. prefix <- renv_bootstrap_platform_prefix_impl()
  465. if (!is.na(prefix) && nzchar(prefix))
  466. components <- c(prefix, components)
  467. # build prefix
  468. paste(components, collapse = "/")
  469. }
  470. renv_bootstrap_platform_prefix_impl <- function() {
  471. # if an explicit prefix has been supplied, use it
  472. prefix <- Sys.getenv("RENV_PATHS_PREFIX", unset = NA)
  473. if (!is.na(prefix))
  474. return(prefix)
  475. # if the user has requested an automatic prefix, generate it
  476. auto <- Sys.getenv("RENV_PATHS_PREFIX_AUTO", unset = NA)
  477. if (auto %in% c("TRUE", "True", "true", "1"))
  478. return(renv_bootstrap_platform_prefix_auto())
  479. # empty string on failure
  480. ""
  481. }
  482. renv_bootstrap_platform_prefix_auto <- function() {
  483. prefix <- tryCatch(renv_bootstrap_platform_os(), error = identity)
  484. if (inherits(prefix, "error") || prefix %in% "unknown") {
  485. msg <- paste(
  486. "failed to infer current operating system",
  487. "please file a bug report at https://github.com/rstudio/renv/issues",
  488. sep = "; "
  489. )
  490. warning(msg)
  491. }
  492. prefix
  493. }
  494. renv_bootstrap_platform_os <- function() {
  495. sysinfo <- Sys.info()
  496. sysname <- sysinfo[["sysname"]]
  497. # handle Windows + macOS up front
  498. if (sysname == "Windows")
  499. return("windows")
  500. else if (sysname == "Darwin")
  501. return("macos")
  502. # check for os-release files
  503. for (file in c("/etc/os-release", "/usr/lib/os-release"))
  504. if (file.exists(file))
  505. return(renv_bootstrap_platform_os_via_os_release(file, sysinfo))
  506. # check for redhat-release files
  507. if (file.exists("/etc/redhat-release"))
  508. return(renv_bootstrap_platform_os_via_redhat_release())
  509. "unknown"
  510. }
  511. renv_bootstrap_platform_os_via_os_release <- function(file, sysinfo) {
  512. # read /etc/os-release
  513. release <- utils::read.table(
  514. file = file,
  515. sep = "=",
  516. quote = c("\"", "'"),
  517. col.names = c("Key", "Value"),
  518. comment.char = "#",
  519. stringsAsFactors = FALSE
  520. )
  521. vars <- as.list(release$Value)
  522. names(vars) <- release$Key
  523. # get os name
  524. os <- tolower(sysinfo[["sysname"]])
  525. # read id
  526. id <- "unknown"
  527. for (field in c("ID", "ID_LIKE")) {
  528. if (field %in% names(vars) && nzchar(vars[[field]])) {
  529. id <- vars[[field]]
  530. break
  531. }
  532. }
  533. # read version
  534. version <- "unknown"
  535. for (field in c("UBUNTU_CODENAME", "VERSION_CODENAME", "VERSION_ID", "BUILD_ID")) {
  536. if (field %in% names(vars) && nzchar(vars[[field]])) {
  537. version <- vars[[field]]
  538. break
  539. }
  540. }
  541. # join together
  542. paste(c(os, id, version), collapse = "-")
  543. }
  544. renv_bootstrap_platform_os_via_redhat_release <- function() {
  545. # read /etc/redhat-release
  546. contents <- readLines("/etc/redhat-release", warn = FALSE)
  547. # infer id
  548. id <- if (grepl("centos", contents, ignore.case = TRUE))
  549. "centos"
  550. else if (grepl("redhat", contents, ignore.case = TRUE))
  551. "redhat"
  552. else
  553. "unknown"
  554. # try to find a version component (very hacky)
  555. version <- "unknown"
  556. parts <- strsplit(contents, "[[:space:]]")[[1L]]
  557. for (part in parts) {
  558. nv <- tryCatch(numeric_version(part), error = identity)
  559. if (inherits(nv, "error"))
  560. next
  561. version <- nv[1, 1]
  562. break
  563. }
  564. paste(c("linux", id, version), collapse = "-")
  565. }
  566. renv_bootstrap_library_root_name <- function(project) {
  567. # use project name as-is if requested
  568. asis <- Sys.getenv("RENV_PATHS_LIBRARY_ROOT_ASIS", unset = "FALSE")
  569. if (asis)
  570. return(basename(project))
  571. # otherwise, disambiguate based on project's path
  572. id <- substring(renv_bootstrap_hash_text(project), 1L, 8L)
  573. paste(basename(project), id, sep = "-")
  574. }
  575. renv_bootstrap_library_root <- function(project) {
  576. prefix <- renv_bootstrap_profile_prefix()
  577. path <- Sys.getenv("RENV_PATHS_LIBRARY", unset = NA)
  578. if (!is.na(path))
  579. return(paste(c(path, prefix), collapse = "/"))
  580. path <- renv_bootstrap_library_root_impl(project)
  581. if (!is.null(path)) {
  582. name <- renv_bootstrap_library_root_name(project)
  583. return(paste(c(path, prefix, name), collapse = "/"))
  584. }
  585. renv_bootstrap_paths_renv("library", project = project)
  586. }
  587. renv_bootstrap_library_root_impl <- function(project) {
  588. root <- Sys.getenv("RENV_PATHS_LIBRARY_ROOT", unset = NA)
  589. if (!is.na(root))
  590. return(root)
  591. type <- renv_bootstrap_project_type(project)
  592. if (identical(type, "package")) {
  593. userdir <- renv_bootstrap_user_dir()
  594. return(file.path(userdir, "library"))
  595. }
  596. }
  597. renv_bootstrap_validate_version <- function(version, description = NULL) {
  598. # resolve description file
  599. #
  600. # avoid passing lib.loc to `packageDescription()` below, since R will
  601. # use the loaded version of the package by default anyhow. note that
  602. # this function should only be called after 'renv' is loaded
  603. # https://github.com/rstudio/renv/issues/1625
  604. description <- description %||% packageDescription("renv")
  605. # check whether requested version 'version' matches loaded version of renv
  606. sha <- attr(version, "sha", exact = TRUE)
  607. valid <- if (!is.null(sha))
  608. renv_bootstrap_validate_version_dev(sha, description)
  609. else
  610. renv_bootstrap_validate_version_release(version, description)
  611. if (valid)
  612. return(TRUE)
  613. # the loaded version of renv doesn't match the requested version;
  614. # give the user instructions on how to proceed
  615. remote <- if (!is.null(description[["RemoteSha"]])) {
  616. paste("rstudio/renv", description[["RemoteSha"]], sep = "@")
  617. } else {
  618. paste("renv", description[["Version"]], sep = "@")
  619. }
  620. # display both loaded version + sha if available
  621. friendly <- renv_bootstrap_version_friendly(
  622. version = description[["Version"]],
  623. sha = description[["RemoteSha"]]
  624. )
  625. fmt <- paste(
  626. "renv %1$s was loaded from project library, but this project is configured to use renv %2$s.",
  627. "- Use `renv::record(\"%3$s\")` to record renv %1$s in the lockfile.",
  628. "- Use `renv::restore(packages = \"renv\")` to install renv %2$s into the project library.",
  629. sep = "\n"
  630. )
  631. catf(fmt, friendly, renv_bootstrap_version_friendly(version), remote)
  632. FALSE
  633. }
  634. renv_bootstrap_validate_version_dev <- function(version, description) {
  635. expected <- description[["RemoteSha"]]
  636. is.character(expected) && startswith(expected, version)
  637. }
  638. renv_bootstrap_validate_version_release <- function(version, description) {
  639. expected <- description[["Version"]]
  640. is.character(expected) && identical(expected, version)
  641. }
  642. renv_bootstrap_hash_text <- function(text) {
  643. hashfile <- tempfile("renv-hash-")
  644. on.exit(unlink(hashfile), add = TRUE)
  645. writeLines(text, con = hashfile)
  646. tools::md5sum(hashfile)
  647. }
  648. renv_bootstrap_load <- function(project, libpath, version) {
  649. # try to load renv from the project library
  650. if (!requireNamespace("renv", lib.loc = libpath, quietly = TRUE))
  651. return(FALSE)
  652. # warn if the version of renv loaded does not match
  653. renv_bootstrap_validate_version(version)
  654. # execute renv load hooks, if any
  655. hooks <- getHook("renv::autoload")
  656. for (hook in hooks)
  657. if (is.function(hook))
  658. tryCatch(hook(), error = warnify)
  659. # load the project
  660. renv::load(project)
  661. TRUE
  662. }
  663. renv_bootstrap_profile_load <- function(project) {
  664. # if RENV_PROFILE is already set, just use that
  665. profile <- Sys.getenv("RENV_PROFILE", unset = NA)
  666. if (!is.na(profile) && nzchar(profile))
  667. return(profile)
  668. # check for a profile file (nothing to do if it doesn't exist)
  669. path <- renv_bootstrap_paths_renv("profile", profile = FALSE, project = project)
  670. if (!file.exists(path))
  671. return(NULL)
  672. # read the profile, and set it if it exists
  673. contents <- readLines(path, warn = FALSE)
  674. if (length(contents) == 0L)
  675. return(NULL)
  676. # set RENV_PROFILE
  677. profile <- contents[[1L]]
  678. if (!profile %in% c("", "default"))
  679. Sys.setenv(RENV_PROFILE = profile)
  680. profile
  681. }
  682. renv_bootstrap_profile_prefix <- function() {
  683. profile <- renv_bootstrap_profile_get()
  684. if (!is.null(profile))
  685. return(file.path("profiles", profile, "renv"))
  686. }
  687. renv_bootstrap_profile_get <- function() {
  688. profile <- Sys.getenv("RENV_PROFILE", unset = "")
  689. renv_bootstrap_profile_normalize(profile)
  690. }
  691. renv_bootstrap_profile_set <- function(profile) {
  692. profile <- renv_bootstrap_profile_normalize(profile)
  693. if (is.null(profile))
  694. Sys.unsetenv("RENV_PROFILE")
  695. else
  696. Sys.setenv(RENV_PROFILE = profile)
  697. }
  698. renv_bootstrap_profile_normalize <- function(profile) {
  699. if (is.null(profile) || profile %in% c("", "default"))
  700. return(NULL)
  701. profile
  702. }
  703. renv_bootstrap_path_absolute <- function(path) {
  704. substr(path, 1L, 1L) %in% c("~", "/", "\\") || (
  705. substr(path, 1L, 1L) %in% c(letters, LETTERS) &&
  706. substr(path, 2L, 3L) %in% c(":/", ":\\")
  707. )
  708. }
  709. renv_bootstrap_paths_renv <- function(..., profile = TRUE, project = NULL) {
  710. renv <- Sys.getenv("RENV_PATHS_RENV", unset = "renv")
  711. root <- if (renv_bootstrap_path_absolute(renv)) NULL else project
  712. prefix <- if (profile) renv_bootstrap_profile_prefix()
  713. components <- c(root, renv, prefix, ...)
  714. paste(components, collapse = "/")
  715. }
  716. renv_bootstrap_project_type <- function(path) {
  717. descpath <- file.path(path, "DESCRIPTION")
  718. if (!file.exists(descpath))
  719. return("unknown")
  720. desc <- tryCatch(
  721. read.dcf(descpath, all = TRUE),
  722. error = identity
  723. )
  724. if (inherits(desc, "error"))
  725. return("unknown")
  726. type <- desc$Type
  727. if (!is.null(type))
  728. return(tolower(type))
  729. package <- desc$Package
  730. if (!is.null(package))
  731. return("package")
  732. "unknown"
  733. }
  734. renv_bootstrap_user_dir <- function() {
  735. dir <- renv_bootstrap_user_dir_impl()
  736. path.expand(chartr("\\", "/", dir))
  737. }
  738. renv_bootstrap_user_dir_impl <- function() {
  739. # use local override if set
  740. override <- getOption("renv.userdir.override")
  741. if (!is.null(override))
  742. return(override)
  743. # use R_user_dir if available
  744. tools <- asNamespace("tools")
  745. if (is.function(tools$R_user_dir))
  746. return(tools$R_user_dir("renv", "cache"))
  747. # try using our own backfill for older versions of R
  748. envvars <- c("R_USER_CACHE_DIR", "XDG_CACHE_HOME")
  749. for (envvar in envvars) {
  750. root <- Sys.getenv(envvar, unset = NA)
  751. if (!is.na(root))
  752. return(file.path(root, "R/renv"))
  753. }
  754. # use platform-specific default fallbacks
  755. if (Sys.info()[["sysname"]] == "Windows")
  756. file.path(Sys.getenv("LOCALAPPDATA"), "R/cache/R/renv")
  757. else if (Sys.info()[["sysname"]] == "Darwin")
  758. "~/Library/Caches/org.R-project.R/R/renv"
  759. else
  760. "~/.cache/R/renv"
  761. }
  762. renv_bootstrap_version_friendly <- function(version, shafmt = NULL, sha = NULL) {
  763. sha <- sha %||% attr(version, "sha", exact = TRUE)
  764. parts <- c(version, sprintf(shafmt %||% " [sha: %s]", substring(sha, 1L, 7L)))
  765. paste(parts, collapse = "")
  766. }
  767. renv_bootstrap_exec <- function(project, libpath, version) {
  768. if (!renv_bootstrap_load(project, libpath, version))
  769. renv_bootstrap_run(version, libpath)
  770. }
  771. renv_bootstrap_run <- function(version, libpath) {
  772. # perform bootstrap
  773. bootstrap(version, libpath)
  774. # exit early if we're just testing bootstrap
  775. if (!is.na(Sys.getenv("RENV_BOOTSTRAP_INSTALL_ONLY", unset = NA)))
  776. return(TRUE)
  777. # try again to load
  778. if (requireNamespace("renv", lib.loc = libpath, quietly = TRUE)) {
  779. return(renv::load(project = getwd()))
  780. }
  781. # failed to download or load renv; warn the user
  782. msg <- c(
  783. "Failed to find an renv installation: the project will not be loaded.",
  784. "Use `renv::activate()` to re-initialize the project."
  785. )
  786. warning(paste(msg, collapse = "\n"), call. = FALSE)
  787. }
  788. renv_json_read <- function(file = NULL, text = NULL) {
  789. jlerr <- NULL
  790. # if jsonlite is loaded, use that instead
  791. if ("jsonlite" %in% loadedNamespaces()) {
  792. json <- tryCatch(renv_json_read_jsonlite(file, text), error = identity)
  793. if (!inherits(json, "error"))
  794. return(json)
  795. jlerr <- json
  796. }
  797. # otherwise, fall back to the default JSON reader
  798. json <- tryCatch(renv_json_read_default(file, text), error = identity)
  799. if (!inherits(json, "error"))
  800. return(json)
  801. # report an error
  802. if (!is.null(jlerr))
  803. stop(jlerr)
  804. else
  805. stop(json)
  806. }
  807. renv_json_read_jsonlite <- function(file = NULL, text = NULL) {
  808. text <- paste(text %||% readLines(file, warn = FALSE), collapse = "\n")
  809. jsonlite::fromJSON(txt = text, simplifyVector = FALSE)
  810. }
  811. renv_json_read_default <- function(file = NULL, text = NULL) {
  812. # find strings in the JSON
  813. text <- paste(text %||% readLines(file, warn = FALSE), collapse = "\n")
  814. pattern <- '["](?:(?:\\\\.)|(?:[^"\\\\]))*?["]'
  815. locs <- gregexpr(pattern, text, perl = TRUE)[[1]]
  816. # if any are found, replace them with placeholders
  817. replaced <- text
  818. strings <- character()
  819. replacements <- character()
  820. if (!identical(c(locs), -1L)) {
  821. # get the string values
  822. starts <- locs
  823. ends <- locs + attr(locs, "match.length") - 1L
  824. strings <- substring(text, starts, ends)
  825. # only keep those requiring escaping
  826. strings <- grep("[[\\]{}:]", strings, perl = TRUE, value = TRUE)
  827. # compute replacements
  828. replacements <- sprintf('"\032%i\032"', seq_along(strings))
  829. # replace the strings
  830. mapply(function(string, replacement) {
  831. replaced <<- sub(string, replacement, replaced, fixed = TRUE)
  832. }, strings, replacements)
  833. }
  834. # transform the JSON into something the R parser understands
  835. transformed <- replaced
  836. transformed <- gsub("{}", "`names<-`(list(), character())", transformed, fixed = TRUE)
  837. transformed <- gsub("[[{]", "list(", transformed, perl = TRUE)
  838. transformed <- gsub("[]}]", ")", transformed, perl = TRUE)
  839. transformed <- gsub(":", "=", transformed, fixed = TRUE)
  840. text <- paste(transformed, collapse = "\n")
  841. # parse it
  842. json <- parse(text = text, keep.source = FALSE, srcfile = NULL)[[1L]]
  843. # construct map between source strings, replaced strings
  844. map <- as.character(parse(text = strings))
  845. names(map) <- as.character(parse(text = replacements))
  846. # convert to list
  847. map <- as.list(map)
  848. # remap strings in object
  849. remapped <- renv_json_read_remap(json, map)
  850. # evaluate
  851. eval(remapped, envir = baseenv())
  852. }
  853. renv_json_read_remap <- function(json, map) {
  854. # fix names
  855. if (!is.null(names(json))) {
  856. lhs <- match(names(json), names(map), nomatch = 0L)
  857. rhs <- match(names(map), names(json), nomatch = 0L)
  858. names(json)[rhs] <- map[lhs]
  859. }
  860. # fix values
  861. if (is.character(json))
  862. return(map[[json]] %||% json)
  863. # handle true, false, null
  864. if (is.name(json)) {
  865. text <- as.character(json)
  866. if (text == "true")
  867. return(TRUE)
  868. else if (text == "false")
  869. return(FALSE)
  870. else if (text == "null")
  871. return(NULL)
  872. }
  873. # recurse
  874. if (is.recursive(json)) {
  875. for (i in seq_along(json)) {
  876. json[i] <- list(renv_json_read_remap(json[[i]], map))
  877. }
  878. }
  879. json
  880. }
  881. # load the renv profile, if any
  882. renv_bootstrap_profile_load(project)
  883. # construct path to library root
  884. root <- renv_bootstrap_library_root(project)
  885. # construct library prefix for platform
  886. prefix <- renv_bootstrap_platform_prefix()
  887. # construct full libpath
  888. libpath <- file.path(root, prefix)
  889. # run bootstrap code
  890. renv_bootstrap_exec(project, libpath, version)
  891. invisible()
  892. })