您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

1181 行
32KB

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