renv/0000755000176200001440000000000014761174012011227 5ustar liggesusersrenv/tests/0000755000176200001440000000000014761167256012405 5ustar liggesusersrenv/tests/testthat/0000755000176200001440000000000014761174012014231 5ustar liggesusersrenv/tests/testthat/test-dcf.R0000644000176200001440000000453114731330073016066 0ustar liggesusers test_that("we can read different types of DCFs", { expected <- list(A = "1", B = "2") # plain jane actual <- renv_dcf_read(text = "A: 1\nB: 2") expect_equal(actual, expected) # extra whitespace between fields actual <- renv_dcf_read(text = "A: 1\n\nB: 2\n") expect_equal(actual, expected) }) test_that("we allow for unindented continuations", { actual <- renv_dcf_read(text = "A: This field\nisn't indented.\nB: 42") expected <- list(A = "This field isn't indented.", B = "42") expect_equal(actual, expected) }) test_that("we can read a latin-1 DESCRIPTION file", { # declared latin1; is latin1 contents <- heredoc({' Encoding: latin1 Dessert: crème brûlée '}) latin1 <- iconv(enc2utf8(contents), from = "UTF-8", to = "latin1") file <- renv_scope_tempfile("DESCRIPTION-") writeLines(latin1, con = file, useBytes = TRUE) dcf <- renv_dcf_read(file) expect_equal(dcf$Dessert, "crème brûlée") }) test_that("we can read a custom encoded DESCRIPTION file", { skip_if(!"CP936" %in% iconvlist()) nihao <- enc2utf8("\u4f60\u597d") # declared CP936, is CP936 contents <- heredoc({' Encoding: CP936 Greeting: \u4f60\u597d '}) bytes <- iconv( x = enc2utf8(contents), from = "UTF-8", to = "CP936", toRaw = TRUE ) file <- renv_scope_tempfile("DESCRIPTION-") writeBin(bytes[[1L]], con = file) dcf <- renv_dcf_read(file) expect_equal(dcf$Greeting, nihao) }) test_that("we can read mis-encoded DESCRIPTION files", { # declared UTF-8; but latin1 contents <- heredoc(' Encoding: UTF-8 Dessert: crème brûlée ') latin1 <- iconv(enc2utf8(contents), from = "UTF-8", to = "latin1") file <- renv_scope_tempfile("DESCRIPTION-") writeLines(latin1, con = file, useBytes = TRUE) dcf <- renv_dcf_read(file) expect_equal(dcf$Dessert, "crème brûlée") }) test_that("we can read and write a dcf file", { contents <- heredoc(' Title: The title. Description: The Description field is quite long. It needs to wrap across multiple lines. ') descfile <- renv_scope_tempfile("renv-description-") writeLines(contents, con = descfile) old <- renv_dcf_read(descfile) renv_dcf_write(old, file = descfile) new <- read.dcf(descfile, all = TRUE) expect_equal( gsub("[[:space:]]+", " ", old$Field), gsub("[[:space:]]+", " ", new$Field) ) }) renv/tests/testthat/test-preflight.R0000644000176200001440000000135014731330073017312 0ustar liggesusers test_that("snapshot preflight tests catch common issues", { renv_tests_scope() libpath <- renv_paths_library() ensure_parent_directory(libpath) # library is a file, not directory file.create(libpath) expect_error(snapshot(library = libpath)) unlink(libpath) # project library does not exist expect_error(snapshot(library = libpath)) # arbitrary library does not exist expect_error(snapshot(library = tempfile())) }) test_that("renv warns when snapshotting missing dependencies", { skip_on_cran() project <- renv_tests_scope("breakfast") init() remove("oatmeal") expect_snapshot(snapshot(), error = TRUE) lockfile <- renv_lockfile_load(project) expect_true(!is.null(lockfile$Packages$oatmeal)) }) renv/tests/testthat/test-update.R0000644000176200001440000000631614751445414016627 0ustar liggesusers test_that("update() finds packages requiring updates from CRAN", { skip_on_cran() renv_tests_scope() init() install("breakfast@0.1.0") expect_true(renv_package_version("breakfast") == "0.1.0") update() expect_true(renv_package_version("breakfast") == "1.0.0") }) test_that("update() can upgrade GitHub packages", { skip_if(getRversion() < "3.5.3") skip_on_cran() skip_if_no_github_auth() skip_slow() renv_tests_scope() init() # download old commit from GitHub and track master install("kevinushey/skeleton@5fd5d3bc616794f869e47fdf3a8b4bcaa2afcf53") pkgpath <- find.package("skeleton") descpath <- file.path(pkgpath, "DESCRIPTION") dcf <- renv_dcf_read(descpath) expect_true(dcf$Version == "0.0.0.9000") remotes <- dcf[grep("^Remote", names(dcf))] remotes$RemoteRef <- "master" renv_package_augment(pkgpath, remotes) # try updating update(packages = "skeleton") # check for new version of package dcf <- renv_dcf_read(descpath) expect_true(dcf$Version == "1.0.1") }) test_that("update() can upgrade Git packages", { skip_if(getRversion() < "3.5.3") skip_on_cran() skip_if_no_github_auth() skip_slow() # this test appears to fail on CI (ssh clone from GitHub disallowed?) testthat::skip_on_ci() renv_tests_scope() init() # download old commit from GitHub and track master local({ renv_scope_envvars(RENV_AUTOLOADER_ENABLED = FALSE) remotes::install_git( url = "https://github.com/kevinushey/skeleton", ref = "5fd5d3bc616794f869e47fdf3a8b4bcaa2afcf53", quiet = TRUE, INSTALL_opts = "--no-multiarch" ) }) pkgpath <- find.package("skeleton") descpath <- file.path(pkgpath, "DESCRIPTION") dcf <- renv_dcf_read(descpath) expect_true(dcf$Version == "0.0.0.9000") remotes <- dcf[grep("^Remote", names(dcf))] remotes$RemoteRef <- "master" renv_package_augment(pkgpath, remotes) # try updating update(packages = "skeleton") # check for new version of package dcf <- renv_dcf_read(descpath) expect_true(dcf$Version == "1.0.1") }) test_that("can upgrade bitbucket", { skip_on_cran() latest <- outdated <- renv_remotes_resolve("bitbucket::kevinushey/skeleton") outdated$Version <- "1.0.0" outdated$RemoteSha <- "5fd5d3b" updated <- renv_update_find(list(outdated)) expect_equal(updated$skeleton$Version, latest$Version) expect_equal(updated$skeleton$RemoteSha, latest$RemoteSha) }) test_that("can upgrade gitlab", { skip_on_cran() latest <- outdated <- renv_remotes_resolve("gitlab::kevinushey/skeleton") outdated$Version <- "1.0.0" outdated$RemoteSha <- "5fd5d3b" updated <- renv_update_find(list(outdated)) expect_equal(updated$skeleton$Version, latest$Version) expect_equal(updated$skeleton$RemoteSha, latest$RemoteSha) }) test_that("we guard against invalid mc.cores values", { skip_on_windows() renv_scope_options(renv.config.updates.parallel = TRUE) local({ renv_scope_options(mc.cores = 4L) expect_equal(renv_parallel_cores(), 4L) }) local({ renv_scope_options(mc.cores = NA) expect_equal(renv_parallel_cores(), 1L) }) local({ renv_scope_options(mc.cores = "oops") expect_equal(renv_parallel_cores(), 1L) }) }) renv/tests/testthat/test-watchdog.R0000644000176200001440000000425614731330073017136 0ustar liggesusers test_that("the watchdog process acquires and releases locks as expected", { skip_on_cran() skip_if(getRversion() < "4.0.0") renv_scope_envvars(RENV_WATCHDOG_ENABLED = TRUE) script <- renv_test_code({ renv:::summon() path <- tempfile() renv_watchdog_notify("LockAcquired", list(path = path)) locks <- renv_watchdog_request("ListLocks") stopifnot(path %in% locks) renv_watchdog_notify("LockReleased", list(path = path)) locks <- renv_watchdog_request("ListLocks") stopifnot(!path %in% locks) }) renv_scope_envvars(RENV_WATCHDOG_ENABLED = "TRUE") output <- expect_no_error( system2( command = R(), args = c("--vanilla", "-s", "-f", renv_shell_path(script)), stdout = FALSE, stderr = FALSE, timeout = 10 ) ) }) test_that("the watchdog process releases locks from killed processes", { skip_on_cran() skip_if(getRversion() < "4.0.0") skip_on_ci() renv_scope_envvars(RENV_WATCHDOG_ENABLED = TRUE) # start a socket server server <- tryCatch(renv_socket_server(), error = skip) defer(close(server$socket)) # acquire lock in a background process path <- tempfile("renv-lock-") expect_false(file.exists(path)) script <- renv_test_code({ renv:::summon() # acquire lock in child process renv_scope_lock(path) # let parent know we have the lock conn <- renv_socket_connect(port, open = "wb") defer(close(conn)) serialize(Sys.getpid(), connection = conn) # commit seppuku command <- paste("kill -TERM", Sys.getpid()) system(command) }, list(path = path, port = server$port)) renv_scope_envvars(RENV_WATCHDOG_ENABLED = "TRUE") system2( command = R(), args = c("--vanilla", "-s", "-f", renv_shell_path(script)), stdout = FALSE, stderr = FALSE, wait = FALSE ) # get PID of child process pid <- local({ conn <- renv_socket_accept(server$socket, open = "rb") defer(close(conn)) unserialize(conn) }) # wait for watchdog to try and remove the process clock <- timer() wait_until(function() { !file.exists(path) || clock$elapsed() > 3 }) # check that the file no longer exists expect_false(file.exists(path)) }) renv/tests/testthat/test-rmd.R0000644000176200001440000000426614731330073016121 0ustar liggesusers test_that("The chunk header parser works as expected", { cases <- list( # R Markdown ---- list( input = "```{r}", type = "md", expected = list(engine = "r") ), list( input = "```{r a-b-c}", type = "md", expected = list(engine = "r", label = "a-b-c") ), list( input = "```{r, a-b-c}", type = "md", expected = list(engine = "r", label = "a-b-c") ), list( input = "```{r, a-b-c, a=1+1}", type = "md", expected = list(engine = "r", label = "a-b-c", a = quote(1 + 1)) ), list( input = "```{python}", type = "md", expected = list(engine = "python") ), list( input = "```{r engine = 'Rcpp'}", type = "md", expected = list(engine = "Rcpp") ), list( input = "```{r, engine = 'Rcpp'}", type = "md", expected = list(engine = "Rcpp") ), list( input = "```{r a?weird?label}", type = "md", expected = list(engine = "r", label = "a?weird?label") ), list( input = "```{r a'weird'label, engine = 'bash'}", type = "md", expected = list(engine = "bash", label = "a'weird'label") ), list( input = "```{r, 'a=weird=label', engine = 'bash'}", type = "md", expected = list(engine = "bash", label = "a=weird=label") ), # Sweave ---- list( input = "<>=", type = "rnw", expected = list(engine = "r", label = "a-b-c") ), list( input = "<>=", type = "rnw", expected = list(engine = "r", label = "a-b-c", a = 1, b = 2) ) ) for (case in cases) { output <- renv_knitr_options_header(case$input, case$type) expect_same_elements(output, case$expected) } }) test_that("we can parse chunk YAML options", { cases <- list( list( input = "#| a: true", expected = list(a = TRUE) ), list( input = "#| a=TRUE", expected = list(a = TRUE) ) ) for (case in cases) { output <- renv_knitr_options_chunk(case$input) expect_same_elements(output, case$expected) } }) renv/tests/testthat/packages/0000755000176200001440000000000014740070207016005 5ustar liggesusersrenv/tests/testthat/packages/windowsonly/0000755000176200001440000000000014731330073020401 5ustar liggesusersrenv/tests/testthat/packages/windowsonly/DESCRIPTION0000644000176200001440000000037714731330073022116 0ustar liggesusersPackage: windowsonly Type: Package Version: 1.0.0 Repository: CRAN License: GPL Description: renv test package Title: renv test package Author: Anonymous Person Maintainer: Anonymous Person OS_type: windows renv/tests/testthat/packages/today/0000755000176200001440000000000014740070207017125 5ustar liggesusersrenv/tests/testthat/packages/today/DESCRIPTION0000644000176200001440000000040114740070207020626 0ustar liggesusersPackage: today Type: Package Version: 1.0.0 Depends: R (>= 99.99.99) Repository: CRAN License: GPL Description: renv test package Title: renv test package Author: Anonymous Person Maintainer: Anonymous Person renv/tests/testthat/packages/packrat/0000755000176200001440000000000014731330073017432 5ustar liggesusersrenv/tests/testthat/packages/packrat/README0000644000176200001440000000020314731330073020305 0ustar liggesusersThe version in the DESCRIPTION file is dynamically set during tests, to match the version of Packrat currently available on CRAN. renv/tests/testthat/packages/packrat/DESCRIPTION0000644000176200001440000000035414731330073021142 0ustar liggesusersPackage: packrat Type: Package Version: 0.0.0 Repository: CRAN License: GPL Description: renv test package Title: renv test package Author: Anonymous Person Maintainer: Anonymous Person renv/tests/testthat/packages/phone/0000755000176200001440000000000014731330073017116 5ustar liggesusersrenv/tests/testthat/packages/phone/DESCRIPTION0000644000176200001440000000035214731330073020624 0ustar liggesusersPackage: phone Type: Package Version: 1.0.0 Repository: CRAN License: GPL Description: renv test package Title: renv test package Author: Anonymous Person Maintainer: Anonymous Person renv/tests/testthat/packages/jamie/0000755000176200001440000000000014740056044017075 5ustar liggesusersrenv/tests/testthat/packages/jamie/DESCRIPTION0000644000176200001440000000041214740056044020600 0ustar liggesusersPackage: jamie Type: Package Version: 1.0.0 Repository: CRAN License: GPL Description: renv test package Title: renv test package Author: Anonymous Person Maintainer: Anonymous Person Depends: phone (>= 0.1.0), kevin renv/tests/testthat/packages/brunch/0000755000176200001440000000000014740056044017271 5ustar liggesusersrenv/tests/testthat/packages/brunch/DESCRIPTION0000644000176200001440000000041614740056044021000 0ustar liggesusersPackage: brunch Type: Package Version: 1.0.0 Depends: oatmeal, toast (>= 1.0.1) Repository: CRAN License: GPL Description: renv test package Title: renv test package Author: Anonymous Person Maintainer: Anonymous Person renv/tests/testthat/packages/egg/0000755000176200001440000000000014731330073016547 5ustar liggesusersrenv/tests/testthat/packages/egg/DESCRIPTION0000644000176200001440000000035014731330073020253 0ustar liggesusersPackage: egg Type: Package Version: 1.0.0 Repository: CRAN License: GPL Description: renv test package Title: renv test package Author: Anonymous Person Maintainer: Anonymous Person renv/tests/testthat/packages/toast/0000755000176200001440000000000014740056044017142 5ustar liggesusersrenv/tests/testthat/packages/toast/DESCRIPTION0000644000176200001440000000037114740056044020651 0ustar liggesusersPackage: toast Depends: bread Type: Package Version: 1.0.0 Repository: CRAN License: GPL Description: renv test package Title: renv test package Author: Anonymous Person Maintainer: Anonymous Person renv/tests/testthat/packages/unixonly/0000755000176200001440000000000014731330073017672 5ustar liggesusersrenv/tests/testthat/packages/unixonly/DESCRIPTION0000644000176200001440000000037214731330073021402 0ustar liggesusersPackage: unixonly Type: Package Version: 1.0.0 Repository: CRAN License: GPL Description: renv test package Title: renv test package Author: Anonymous Person Maintainer: Anonymous Person OS_type: unix renv/tests/testthat/packages/breakfast/0000755000176200001440000000000014740056044017752 5ustar liggesusersrenv/tests/testthat/packages/breakfast/DESCRIPTION0000644000176200001440000000047014740056044021461 0ustar liggesusersPackage: breakfast Type: Package Version: 1.0.0 Depends: oatmeal, toast (>= 1.0.0) Suggests: egg Repository: CRAN License: GPL Description: renv test package Title: renv test package Author: Anonymous Person Maintainer: Anonymous Person Config/Needs/protein: egg renv/tests/testthat/packages/oatmeal/0000755000176200001440000000000014731330073017427 5ustar liggesusersrenv/tests/testthat/packages/oatmeal/DESCRIPTION0000644000176200001440000000035414731330073021137 0ustar liggesusersPackage: oatmeal Type: Package Version: 1.0.0 Repository: CRAN License: GPL Description: renv test package Title: renv test package Author: Anonymous Person Maintainer: Anonymous Person renv/tests/testthat/packages/renv/0000755000176200001440000000000014731330073016757 5ustar liggesusersrenv/tests/testthat/packages/renv/DESCRIPTION0000644000176200001440000000034714731330073020471 0ustar liggesusersPackage: renv Type: Package Version: 1.0.0 License: GPL Description: renv test package Title: renv test package Author: Anonymous Person Maintainer: Anonymous Person Repository: CRAN renv/tests/testthat/packages/bread/0000755000176200001440000000000014731330073017062 5ustar liggesusersrenv/tests/testthat/packages/bread/DESCRIPTION0000644000176200001440000000040314731330073020565 0ustar liggesusersPackage: bread Type: Package Version: 1.0.0 Repository: CRAN License: GPL Description: renv test package Title: renv test package Author: Anonymous Person Maintainer: Anonymous Person Config/Needs/protein: egg renv/tests/testthat/packages/halloween/0000755000176200001440000000000014731330073017763 5ustar liggesusersrenv/tests/testthat/packages/halloween/DESCRIPTION0000644000176200001440000000043514731330073021473 0ustar liggesusersPackage: halloween Type: Package Version: 1.0.0 Repository: CRAN License: GPL Description: renv test package Title: renv test package Author: Anonymous Person Maintainer: Anonymous Person Imports: skeleton Remotes: kevinushey/skeleton renv/tests/testthat/packages/kevin/0000755000176200001440000000000014740056044017124 5ustar liggesusersrenv/tests/testthat/packages/kevin/DESCRIPTION0000644000176200001440000000040314740056044020627 0ustar liggesusersPackage: kevin Type: Package Version: 1.0.0 Repository: CRAN License: GPL Description: renv test package Title: renv test package Author: Anonymous Person Maintainer: Anonymous Person Depends: phone (>= 1.0.0) renv/tests/testthat/packages/future/0000755000176200001440000000000014740070207017317 5ustar liggesusersrenv/tests/testthat/packages/future/DESCRIPTION0000644000176200001440000000037014740070207021025 0ustar liggesusersPackage: future Type: Package Version: 1.0.0 Depends: today Repository: CRAN License: GPL Description: renv test package Title: renv test package Author: Anonymous Person Maintainer: Anonymous Person renv/tests/testthat/helper-skip.R0000644000176200001440000000302414731330073016574 0ustar liggesusers skip_if_no_github_auth <- function() { token <- renv_bootstrap_github_token() skip_if(empty(token), "GITHUB_PAT is not set") } skip_if_no_python <- function() { has_python <- Sys.which("python3") != "" || Sys.which("python") != "" skip_if_not(has_python, "python is not installed") } sys_python <- function() { for (python in c("python3", "python")) { python <- Sys.which(python) if (nzchar(python)) return(python) } skip("python is not available") } skip_if_no_virtualenv <- function() { skip_if_no_python() skip_if_not(has_virtualenv(), "virtualenv module not installed") TRUE } the$has_virtualenv <- NULL has_virtualenv <- function() { the$has_virtualenv <- the$has_virtualenv %||% { python <- sys_python() version <- renv_python_version(python) module <- if (numeric_version(version) >= "3.2") "venv" else "virtualenv" renv_python_module_available(python, module) } } skip_if_no_miniconda <- function(min_version = NULL) { skip_if_no_python() if (!is.null(min_version)) { has_version <- renv_version_ge(renv_python_version(sys_python()), min_version) skip_if_not(has_version, paste0("python ", min_version, " not installed")) } skip_if_not_installed("reticulate", "1.28") path <- reticulate::miniconda_path() skip_if_not(file.exists(path), "miniconda is not installed") } skip_if_local <- function() { ci <- Sys.getenv("CI", unset = NA) testthat::skip_if(is.na(ci), "Running tests locally") } skip_on_windows <- function() { testthat::skip_on_os("windows") } renv/tests/testthat/test-checkout.R0000644000176200001440000000170214731330073017134 0ustar liggesusers test_that("we can check out packages from our local repository", { # enter test scope project <- renv_tests_scope() init() # check out a package + its dependencies; this invocation is # similar in spirit to a plain `install()` call renv_tests_dependencies("breakfast") checkout(packages = "breakfast") # check that they were installed expect_true(renv_package_installed("breakfast")) expect_true(renv_package_installed("bread")) }) test_that("we can check out packages from the package manager instance", { skip_on_cran() skip_if(Sys.info()[["machine"]] == "aarch64") renv_tests_scope() init() # ensure we reset repos on exit renv_scope_options(repos = getOption("repos")) # install rlang from an old snapshot checkout(date = "2023-01-02", packages = "rlang") # check that we installed the requested version expect_true(renv_package_installed("rlang")) expect_true(renv_package_version("rlang") == "1.0.6") }) renv/tests/testthat/test-utils.R0000644000176200001440000001164514731330073016476 0ustar liggesusers test_that("case() handles control flow as expected", { for (i in 1:3) case(i == 2 ~ break) expect_equal(i, 2) value <- local({ case(FALSE ~ stop(), TRUE ~ return(42)) 24 }) expect_equal(value, 42) }) test_that("common utils work as expected", { expect_equal(lines(1, 2, 3), "1\n2\n3") if (nzchar(Sys.which("git"))) expect_equal(git(), Sys.which("git")) else expect_error(git()) }) test_that("versions are compared as expected", { expect_equal(renv_version_compare("0.1.0", "0.2.0"), -1L) expect_equal(renv_version_compare("0.2.0", "0.2.0"), +0L) expect_equal(renv_version_compare("0.3.0", "0.2.0"), +1L) }) test_that("insert inserts text at expected anchor point", { text <- c("alpha", "beta", "gamma") inserted <- insert(text, "beta", "BETA") expect_equal(inserted, c("alpha", "BETA", "gamma")) inserted <- insert(text, "BETA", "BETA", "beta") expect_equal(inserted, c("alpha", "beta", "BETA", "gamma")) }) test_that("renv_path_aliased() correctly forms aliased path", { path <- "~/some/path" expanded <- path.expand(path) expect_equal(path, renv_path_aliased(expanded)) }) test_that("find() returns first non-null matching value", { data <- list(x = 1, y = 2, z = 3) value <- find(data, function(datum) { if (datum == 2) return(42) }) expect_equal(value, 42) value <- find(data, function(datum) { if (datum == 4) return(42) }) expect_null(value) }) test_that("recursing() reports if we're recursing", { f <- function(i) { if (recursing()) expect_true(i == 2) else expect_true(i == 1) if (i < 2) f(i + 1) if (recursing()) expect_true(i == 2) else expect_true(i == 1) } f(1) }) test_that("sys.call(sys.parent()) does what we think it does", { inner <- function() { as.character(sys.call(sys.parent())[[1L]]) } outer <- function() { inner() } expect_equal(outer(), "outer") }) test_that("new() creates objects", { oop <- new({ .data <- NULL get <- function() { .data } set <- function(data) { .data <<- data } }) expect_identical(oop$get(), NULL) expect_identical(oop$set(42L), 42L) expect_identical(oop$get(), 42L) expect_null(oop$data) data <- get(".data", envir = oop) expect_equal(data, 42L) }) test_that("rows(), cols() does what we want", { indices <- list( c(1, 3, 5), c(TRUE, FALSE) ) for (index in indices) { lhs <- mtcars[index, ] rhs <- rows(mtcars, index) rownames(lhs) <- rownames(rhs) <- NULL expect_equal(lhs, rhs) lhs <- mtcars[index] rhs <- cols(mtcars, index) expect_equal(lhs, rhs) } }) test_that("visited() works as expected", { envir <- new.env(parent = emptyenv()) expect_false(visited("hello", envir)) expect_true(visited("hello", envir)) expect_true(visited("hello", envir)) envir$hello <- FALSE expect_false(visited("hello", envir)) expect_true(visited("hello", envir)) }) test_that("ensure_directory() works even under contention", { skip_on_cran() skip_if(getRversion() < "4.0.0") n <- 4 waitfile <- tempfile("renv-wait-") target <- tempfile("renv-directory-") server <- tryCatch(renv_socket_server(), error = skip) defer(close(server$socket)) script <- renv_test_code({ renv:::summon() # create the directory wait_until(file.exists, waitfile) ok <- tryCatch( { ensure_directory(target); TRUE }, error = function(e) FALSE ) # notify parent conn <- renv_socket_connect(port = port, open = "wb") defer(close(conn)) serialize(ok, connection = conn) }, list(port = server$port, waitfile = waitfile, target = target)) for (i in 1:n) { system2( command = R(), args = c("--vanilla", "--slave", "-f", renv_shell_path(script)), stdout = FALSE, stderr = FALSE, wait = FALSE ) } file.create(waitfile) responses <- stack() for (i in 1:n) local({ conn <- renv_socket_accept(server$socket, open = "rb", timeout = 3) defer(close(conn)) responses$push(unserialize(conn)) }) expect_true(all(unlist(responses$data()))) }) test_that("warnify() propagates errors as warnings", { # here's why we don't use 'error = warning' result <- tryCatch( tryCatch(stop("ouch"), error = warning), error = function(e) "error", warning = function(w) "warning" ) expect_equal(result, "error") # warnify does something closer to what we expect result <- tryCatch( tryCatch(stop("ouch"), error = warnify), error = function(e) "error", warning = function(w) "warning" ) expect_equal(result, "warning") }) # https://github.com/rstudio/renv/issues/1733 test_that("plural() and nplural() handle non-scalar counts", { actual <- plural("file", 0:3) expected <- c("files", "file", "files", "files") expect_equal(actual, expected) actual <- nplural("file", 0:3) expected <- c("0 files", "1 file", "2 files", "3 files") expect_equal(actual, expected) }) renv/tests/testthat/test-memoize.R0000644000176200001440000000156614731330073017004 0ustar liggesusers test_that("memoization works as expected", { global <- 0L scope <- basename(renv_scope_tempfile("renv-memoize-")) value <- memoize( scope = scope, key = "example", value = global <- global + 1L ) expect_equal(value, 1L) expect_equal(global, 1L) value <- memoize( scope = scope, key = "example", value = global <- global + 1L ) expect_equal(value, 1L) expect_equal(global, 1L) value <- memoize( scope = scope, key = "example2", value = global <- global + 1L ) expect_equal(value, 2L) expect_equal(global, 2L) }) test_that("memoize avoids evaluating expression multiple times", { value <- 0L scope <- basename(renv_scope_tempfile("renv-memoize-")) memoize("example", { value <- value + 1L }, scope = scope) memoize("example", { value <- value + 1L }, scope = scope) expect_equal(value, 1L) }) renv/tests/testthat/test-path.R0000644000176200001440000000133614731330073016266 0ustar liggesusers test_that("renv_path_absolute() reports common absolute paths", { abs <- c( "/path/to/file", "~/path/to/file", "C:/path/to/file", "C:\\path\\to\\file", "\\\\server\\path\\to\\file" ) rel <- c( "path/to/file", "./path/to/file", "../path/to/file", "::/weird/path" ) for (path in abs) expect_true(renv_path_absolute(path)) for (path in rel) expect_false(renv_path_absolute(path)) }) test_that("renv_path_normalize() normalizes relative paths that don't exist", { prefix <- renv_path_normalize(".", mustWork = TRUE) path <- "i/dont/exist" actual <- renv_path_normalize(path) expected <- paste(prefix, "i/dont/exist", sep = "/") expect_equal(actual, expected) }) renv/tests/testthat/test-reload.R0000644000176200001440000000150614761163114016602 0ustar liggesusers test_that("renv can be reloaded within the same session", { skip_on_cran() script <- renv_test_code({ # load renv tools renv:::summon() # set up temporary library path libdir <- tempfile("renv-library-") ensure_directory(libdir) .libPaths(libdir) # install CRAN version of renv options(warn = 2L) options(repos = c(CRAN = "https://cloud.R-project.org")) install("renv@1.0.0") # unload and reload renv unloadNamespace("renv") loadNamespace("renv") # try writing out some data writeLines(paste(renv_package_version("renv"), renv_namespace_version("renv"))) }) output <- renv_system_exec( command = R(), args = c("--vanilla", "-s", "-f", renv_shell_path(script)), action = "reloading renv" ) expect_equal(tail(output, n = 1L), "1.0.0 1.0.0") }) renv/tests/testthat/test-consent.R0000644000176200001440000000022514731330073016777 0ustar liggesusers test_that("init fails if no consent has been provided", { renv_tests_scope() renv_scope_options(renv.consent = FALSE) expect_error(init()) }) renv/tests/testthat/test-infrastructure.R0000644000176200001440000000676414731330073020424 0ustar liggesusers test_that("renv.lock is added to .Rbuildignore", { skip_on_cran() renv_tests_scope() # use custom userdir userdir <- renv_scope_tempfile("renv-userdir-override") ensure_directory(userdir) userdir <- renv_path_normalize(userdir, mustWork = TRUE) renv_scope_options(renv.userdir.override = userdir) # sanity check userdir <- renv_bootstrap_user_dir() if (!identical(expect_true(renv_path_within(userdir, tempdir())), TRUE)) skip("userdir not in tempdir; cannot proceed") # initialize writeLines(c("Type: Package", "Package: test"), con = "DESCRIPTION") init() # check expect_true(file.exists(".Rbuildignore")) contents <- readLines(".Rbuildignore") expect_true("^renv\\.lock$" %in% contents) }) test_that("infrastructure can be removed", { renv_tests_scope("breakfast") before <- list.files(recursive = TRUE) init() deactivate() renv_infrastructure_remove() unlink("renv.lock") after <- list.files(recursive = TRUE) expect_setequal(before, after) }) test_that("library/ is excluded from .gitignore as appropriate", { skip_if_not(nzchar(Sys.which("git")), "git is not available") renv_tests_scope() system("git init", ignore.stdout = TRUE, ignore.stderr = TRUE) init(bare = TRUE) contents <- readLines("renv/.gitignore") expect_true("library/" %in% contents) settings$vcs.ignore.library(FALSE) contents <- readLines("renv/.gitignore") expect_true("# library/" %in% contents) expect_false("library/" %in% contents) settings$vcs.ignore.library(TRUE) contents <- readLines("renv/.gitignore") expect_true("library/" %in% contents) expect_false("# library/" %in% contents) settings$vcs.ignore.local(TRUE) contents <- readLines("renv/.gitignore") expect_true("local/" %in% contents) expect_false("# local/" %in% contents) settings$vcs.ignore.local(FALSE) contents <- readLines("renv/.gitignore") expect_false("local/" %in% contents) expect_true("# local/" %in% contents) }) test_that("whitespace in infrastructure file is preserved", { renv_tests_scope() before <- c("^renv$", "", "^renv\\.lock$", "", "c") writeLines(before, ".Rbuildignore") renv_infrastructure_write() after <- readLines(".Rbuildignore") expect_equal(before, after) }) test_that("lines are commented, uncommented as appropriate", { renv_tests_scope() text <- " # Some pre-existing text" writeLines(text, con = ".Rprofile") init(bare = TRUE) before <- readLines(".Rprofile") deactivate() after <- readLines(".Rprofile") expected <- c("# source(\"renv/activate.R\")", text) expect_identical(after, expected) }) test_that("comments after a required line are preserved", { renv_tests_scope() before <- c("# a comment", "source(\"renv/activate.R\")") after <- c("# a comment", "# source(\"renv/activate.R\")") writeLines(before, con = ".Rprofile") renv_infrastructure_remove() expect_identical(readLines(con = ".Rprofile"), after) renv_infrastructure_write() expect_identical(readLines(con = ".Rprofile"), before) }) test_that("the project .Rprofile is removed if only autoloader exists", { renv_tests_scope() writeLines("source(\"renv/activate.R\")", con = ".Rprofile") renv_infrastructure_remove() expect_false(file.exists(".Rprofile")) }) test_that("the renv .gitignore is updated for sub-projects", { renv_tests_scope() dir.create(".git") project <- file.path(getwd(), "project") dir.create(project) renv_infrastructure_write(project = project) expect_true(file.exists("project/renv/.gitignore")) }) renv/tests/testthat/test-scope.R0000644000176200001440000000537314731330073016450 0ustar liggesusers test_that(".libPaths() scoping works as expected", { libpaths <- .libPaths() local({ dir <- renv_path_normalize(tempdir()) renv_scope_libpaths(dir) expect_true(.libPaths()[1] == dir) }) expect_true(.libPaths()[1] == libpaths[1]) }) test_that("options() scoping works as expected", { opts <- list( download.file.method = "curl", download.file.extra = NULL ) old <- options("download.file.method", "download.file.extra") local({ do.call(renv_scope_options, opts) expect_equal(opts, options("download.file.method", "download.file.extra")) }) expect_equal(old, options("download.file.method", "download.file.extra")) }) test_that("environment variable scoping works as expected", { renv_scope_envvars("RENV_TEST_ENVVAR_B" = "0") # set and later unset a variable local({ renv_scope_envvars("RENV_TEST_ENVVAR_A" = "1") expect_identical(Sys.getenv("RENV_TEST_ENVVAR_A"), "1") }) expect_identical(Sys.getenv("RENV_TEST_ENVVAR_A", unset = NA), NA_character_) # override and restore variables local({ renv_scope_envvars("RENV_TEST_ENVVAR_A" = "1", "RENV_TEST_ENVVAR_B" = "2") expect_identical(Sys.getenv("RENV_TEST_ENVVAR_A"), "1") expect_identical(Sys.getenv("RENV_TEST_ENVVAR_B"), "2") }) expect_identical(Sys.getenv("RENV_TEST_ENVVAR_B"), "0") expect_identical(Sys.getenv("RENV_TEST_ENVVAR_A", unset = NA), NA_character_) # unset and reset variable local({ renv_scope_envvars("RENV_TEST_ENVVAR_B" = NULL) expect_identical(Sys.getenv("RENV_TEST_ENVVAR_B", unset = NA), NA_character_) }) expect_identical(Sys.getenv("RENV_TEST_ENVVAR_B"), "0") }) test_that("nested attempts to scope libpaths are properly handled", { libpaths <- .libPaths() local({ dir <- renv_path_normalize(tempdir()) renv_scope_libpaths(dir) renv_scope_libpaths(dir) expect_true(.libPaths()[1] == dir) }) expect_true(.libPaths()[1] == libpaths[1]) }) test_that("renv_scope_trace works", { count <- 0 local({ renv_scope_trace(what = identity, tracer = function() count <<- count + 1) identity(1) }) identity(1) expect_equal(count, 1L) }) test_that("can temporarily replace binding", { envir <- environment() x <- 10 local({ renv_scope_binding(envir, "x", 20) expect_equal(x, 20) }) expect_equal(x, 10) }) test_that("can temporarily create binding", { envir <- environment() local({ renv_scope_binding(envir, "x", 20) expect_equal(x, 20) }) expect_false(exists("x", inherits = FALSE)) }) test_that("can temporarily replace locked binding", { original <- utils::adist utils <- asNamespace("utils") local({ renv_scope_binding(utils, "adist", 20) expect_equal(utils::adist, 20) }) expect_equal(utils::adist, original) }) renv/tests/testthat/test-job.R0000644000176200001440000000022514746261044016107 0ustar liggesusers test_that("jobs can be run", { skip_on_cran() encoded <- job(function() { renv_base64_encode("hello") }) expect_equal(encoded, "aGVsbG8=") }) renv/tests/testthat/test-hydrate.R0000644000176200001440000000207714731330073016775 0ustar liggesusers test_that("hydrate does not change library paths", { renv_tests_scope() lib <- renv_scope_tempfile() ensure_directory(lib) .libPaths(lib) before <- .libPaths() hydrate() after <- .libPaths() expect_identical(before, after) }) test_that("hydrate(update = FALSE) does not update older packages", { renv_tests_scope("bread") init() # set up project with older version of bread install("bread@0.1.0") # add dependency on toast writeLines("library(toast)", con = "deps2.R") # set up library for hydration sourcelib <- renv_scope_tempfile("renv-source-") ensure_directory(sourcelib) install("toast", library = sourcelib) # try hydrating without update expect_false(renv_package_installed("toast")) hydrate(sources = sourcelib, update = FALSE) expect_true(renv_package_installed("toast")) expect_true(renv_package_version("toast") == "1.0.0") expect_true(renv_package_version("bread") == "0.1.0") # try hydrating with update hydrate(sources = sourcelib, update = TRUE) expect_true(renv_package_version("bread") == "1.0.0") }) renv/tests/testthat/test-envvars.R0000644000176200001440000000162114731330073017013 0ustar liggesuserstest_that("renv_envvars_save() is idempotent", { renv_scope_envvars(list = rep_named(renv_envvars_list(), list(NULL))) renv_scope_envvars(RENV_DEFAULT_R_LIBS_USER = "xyz") renv_envvars_restore() before <- Sys.getenv() userlib <- Sys.getenv("R_LIBS_USER") expect_true(renv_envvars_save()) expect_false(renv_envvars_save()) expect_equal(userlib, Sys.getenv("RENV_DEFAULT_R_LIBS_USER")) during <- Sys.getenv() expect_false(identical(before, during)) renv_envvars_restore() expect_equal(userlib, Sys.getenv("R_LIBS_USER")) after <- Sys.getenv() expect_equal(names(before), names(after)) nms <- union(names(before), names(after)) expect_equal(before[nms], after[nms]) }) test_that("RENV_PATHS_PREFIX is not normalized", { renv_scope_envvars(RENV_PATHS_PREFIX = ".", RENV_PATHS_ROOT = "") renv_envvars_normalize() expect_identical(Sys.getenv("RENV_PATHS_PREFIX"), ".") }) renv/tests/testthat/test-mran.R0000644000176200001440000000074014731330073016265 0ustar liggesusers test_that("older binaries are installed from MRAN on Windows / macOS", { skip_on_cran() skip_on_os("linux") renv_tests_scope() renv_scope_options(pkgType = "binary") version <- getRversion()[1, 1:2] if (version != "3.5") skip("only run on R 3.5") install("digest@0.6.17") path <- find.package("digest", lib.loc = .libPaths()) desc <- renv_description_read(path) expect_identical(desc$Package, "digest") expect_identical(desc$Version, "0.6.17") }) renv/tests/testthat/test-upgrade.R0000644000176200001440000000110414731330073016752 0ustar liggesusers test_that("the version of renv in a project can be changed (upgraded)", { skip_slow() renv_tests_scope() init() # with a version number upgrade(version = "0.5.0") expect_equal( renv_activate_version("."), structure("0.5.0", sha = "32f0f78d87150a8656a99223396f844e2fac7a17") ) # or with a sha expect_true(upgrade(version = "5049cef8a")) expect_equal( renv_activate_version("."), structure("0.17.3-62", sha = "5049cef8a94591b802f9766a0da092780f59f7e4") ) # second upgrade does nothing expect_false(upgrade(version = "5049cef8a")) }) renv/tests/testthat/test-http.R0000644000176200001440000000057114731330073016311 0ustar liggesusers test_that("renv handles unexpected user agent values", { # https://github.com/rstudio/renv/issues/1787 renv_scope_options(HTTPUserAgent = character()) agent <- renv_http_useragent() expect_equal(agent, renv_http_useragent_default()) renv_scope_options(HTTPUserAgent = NULL) agent <- renv_http_useragent() expect_equal(agent, renv_http_useragent_default()) }) renv/tests/testthat/test-sandbox.R0000644000176200001440000000755114731330073016775 0ustar liggesusers renv_scoped_sandbox <- function(scope = parent.frame()) { renv_scope_options(renv.config.sandbox.enabled = TRUE, scope = scope) sandbox <- renv_scope_tempfile(scope = scope) renv_scope_envvars(RENV_PATHS_SANDBOX = sandbox, scope = scope) old <- list(.Library.site, .Library, .libPaths()) defer(scope = scope, { unlink(sandbox, recursive = TRUE, force = TRUE) renv_binding_replace(base, ".Library.site", old[[1L]]) renv_binding_replace(base, ".Library", old[[2L]]) .libPaths(old[[3]]) }) } test_that("the sandbox can be activated and deactivated", { renv_scoped_sandbox() # save current library paths libpaths <- .libPaths() # after activating the sandbox, .Library should be changed syslib <- renv_libpaths_system() renv_sandbox_activate() expect_false(identical(syslib, .Library)) # after deactivating the sandbox, .Library should be restored renv_sandbox_deactivate() expect_equal(syslib, .Library) # the library paths should be restored as well expect_equal(libpaths, .libPaths()) }) test_that("multiple attempts to activate sandbox are handled", { renv_scoped_sandbox() libpaths <- .libPaths() syslib <- renv_libpaths_system() # calls to renv_sandbox_activate() should be idempotent renv_sandbox_activate() renv_sandbox_activate() renv_sandbox_activate() expect_false(identical(syslib, .Library)) # deactivate the sandbox and assert we've restored state renv_sandbox_deactivate() expect_equal(syslib, .Library) expect_equal(libpaths, .libPaths()) }) test_that(".Library.site isn't used even when sandbox is disabled", { skip_if(renv_platform_windows() || empty(.Library.site)) renv_scope_options(renv.config.sandbox.enabled = FALSE) sandbox <- renv_scope_tempfile("renv-sandbox-") renv_scope_envvars(RENV_PATHS_SANDBOX = sandbox) sitelib <- setdiff(.Library.site, .Library) renv_sandbox_activate() expect_false(any(sitelib %in% .libPaths())) renv_sandbox_deactivate() }) test_that("renv repairs library paths on load if sandbox is active", { renv_scoped_sandbox() libpaths <- .libPaths() syslib <- renv_libpaths_system() # initialize the sandbox renv_sandbox_activate() expect_false(identical(syslib, .Library)) # check for sandbox marker file marker <- file.path(.Library, ".renv-sandbox") expect_true(file.exists(marker)) # set up R_LIBS, and then run a child process that reports # the library paths after loading renv script <- renv_test_code({ renv:::summon() writeLines(.libPaths()) }) output <- local({ renv_scope_envvars(R_LIBS = paste(.libPaths(), collapse = .Platform$path.sep)) renv_system_exec( command = R(), args = c("--vanilla", "-s", "-f", renv_shell_path(script)), action = "testing sandbox" ) }) expect_equal(output, .libPaths()) # deactivate the sandbox and assert we've restored state renv_sandbox_deactivate() expect_equal(syslib, .Library) expect_equal(libpaths, .libPaths()) }) test_that("multiple processes can attempt to acquire the sandbox", { skip_on_cran() skip_on_ci() renv_scoped_sandbox() # number of processes n <- 20 server <- tryCatch(renv_socket_server(), error = skip) defer(close(server$socket)) project <- renv_tests_scope() script <- renv_test_code({ renv:::summon() renv_sandbox_activate(project) conn <- renv_socket_connect(port = port, open = "wb") defer(close(conn)) writeLines(paste(Sys.getpid()), con = conn) }, list(project = project, file = file, port = server$port)) for (i in seq_len(n)) { system2( command = R(), args = c("--vanilla", "-s", "-f", renv_shell_path(script)), wait = FALSE ) } stk <- stack() for (i in seq_len(n)) local({ conn <- renv_socket_accept(server$socket, open = "rb", timeout = 10) defer(close(conn)) stk$push(readLines(conn)) }) expect_length(stk$data(), n) }) renv/tests/testthat/test-record.R0000644000176200001440000000265414731330073016614 0ustar liggesusers test_that("an existing lockfile can be updated", { renv_tests_scope("bread") init() # change bread to 0.1.0 record(list(bread = "bread@0.1.0")) lockfile <- renv_lockfile_read("renv.lock") records <- renv_lockfile_records(lockfile) expect_length(records, 1L) expect_equal(records$bread$Version, "0.1.0") # add a new record record(list(toast = "toast@1.0.0")) lockfile <- renv_lockfile_read("renv.lock") records <- renv_lockfile_records(lockfile) expect_length(records, 2L) expect_equal(records$bread$Version, "0.1.0") expect_equal(records$toast$Version, "1.0.0") # use short-hand lockfile <- record("toast@1.0.1", lockfile = lockfile) records <- renv_lockfile_records(lockfile) expect_equal(records$toast$Version, "1.0.1") lockfile <- record(list(toast = "1.0.2"), lockfile = lockfile) records <- renv_lockfile_records(lockfile) expect_equal(records$toast$Version, "1.0.2") # remove a record lockfile <- record(list(toast = NULL), lockfile = lockfile) records <- renv_lockfile_records(lockfile) expect_true(is.null(records$toast)) }) test_that("record() also records version", { renv_tests_scope() # create empty lockfile snapshot() # add a record record("breakfast") # check that the version of breakfast was recorded lockfile <- renv_lockfile_load(project = getwd()) breakfast <- lockfile$Packages$breakfast expect_identical(breakfast$Version, "1.0.0") }) renv/tests/testthat/test-version.R0000644000176200001440000000234214731330073017015 0ustar liggesusers test_that("various versions can be compared", { expect_equal(renv_version_compare("3.5", "3.5.7"), -1L) expect_equal(renv_version_compare("3.5.0", "3.5.7"), -1L) expect_equal(renv_version_compare("3.5.7", "3.5.7"), +0L) expect_equal(renv_version_compare("3.5.8", "3.5.7"), +1L) expect_equal(renv_version_compare("3.10.1", "4"), -1L) }) test_that("version matching works as expected", { versions <- c("2.7.10", "3.5.1", "3.5.10", "3.6", "3.6.5") expect_equal(renv_version_match(versions, "3.5.6"), "3.5.10") expect_equal(renv_version_match(versions, "3.5.1"), "3.5.1") expect_equal(renv_version_match(versions, "3"), "3.6.5") expect_equal(renv_version_match(versions, "2"), "2.7.10") }) test_that("renv_version_length works as expected", { expect_equal(renv_version_length("1"), 1) expect_equal(renv_version_length("1.2"), 2) expect_equal(renv_version_length("1.2-3"), 3) expect_equal(renv_version_length("1.2.3-4"), 4) }) test_that("renv_version_parts works as expected", { expect_equal(renv_version_parts("1.0", 1L), c(1L)) expect_equal(renv_version_parts("1.0", 2L), c(1L, 0L)) expect_equal(renv_version_parts("1.0", 3L), c(1L, 0L, 0L)) expect_equal(renv_version_parts("1.1-4", 3L), c(1L, 1L, 4L)) }) renv/tests/testthat/helper-watchdog.R0000644000176200001440000000022214731330073017423 0ustar liggesusers # start the watchdog eagerly, so that our tests around potentially # modified connections don't pick up on the new process renv_watchdog_check() renv/tests/testthat/resources/0000755000176200001440000000000014761167256016257 5ustar liggesusersrenv/tests/testthat/resources/modules.R0000644000176200001440000000045714731330073020042 0ustar liggesusersmodule({ import("A") import(B) import(from = "C") import(symbol, from = D) }) # NOTE: these should be ignored as they are not # called within a module block import("e") import(f) # NOTE: fully scoped modules::import calls should # be added to dependencies modules::import("G") modules::import(H) renv/tests/testthat/resources/quarto-empty.qmd0000644000176200001440000000010614731330073021410 0ustar liggesusers--- title: Quarto document --- ```{python} # we don't use R here ``` renv/tests/testthat/resources/knitr-reused-chunks.Rmd0000644000176200001440000000016314731330073022612 0ustar liggesusers ```{r chunk1} x <- 1 ``` ```{r chunk2} <> library(A) ``` ```{r chunk3} <> library(B) ``` renv/tests/testthat/resources/pacman.R0000644000176200001440000000032514731330073017623 0ustar liggesusers # capture these pacman::p_load(a) pacman::p_load("b") p_load(c, "d", e) p_load(char = c("f", "g", "h")) pacman::p_load(char = "i") # don't capture these pacman::p_load(A, character.only = TRUE) p_load(char = B) renv/tests/testthat/resources/bioconductor.lock0000644000176200001440000000247414731330073021614 0ustar liggesusers{ "R": { "Version": "4.1.2", "Repositories": [ { "Name": "CRAN", "URL": "https://cloud.r-project.org" } ] }, "Bioconductor": { "Version": "3.13" }, "Packages": { "BiocGenerics": { "Package": "BiocGenerics", "Version": "0.38.0", "Source": "Bioconductor", "git_url": "https://git.bioconductor.org/packages/BiocGenerics", "git_branch": "RELEASE_3_13", "git_last_commit": "1db849a", "git_last_commit_date": "2021-05-19", "Hash": "de5e346fed0fc44a0424a0531cf5d12d" }, "limma": { "Package": "limma", "Version": "3.50.0", "Source": "Bioconductor", "git_url": "https://git.bioconductor.org/packages/limma", "git_branch": "RELEASE_3_14", "git_last_commit": "657b19b", "git_last_commit_date": "2021-10-26", "Hash": "25679c54c2737e06835c5484682990b5" }, "renv": { "Package": "renv", "Version": "0.14.0-69", "Source": "Local", "RemoteType": "local", "RemoteUrl": "~/r/pkg/renv", "Hash": "41fed5e791a5f7ea1eb290eda0531722" } } } renv/tests/testthat/resources/chunk-yaml.Rmd0000644000176200001440000000025514731330073020757 0ustar liggesusers ```{r} #| echo: false #| warning: false #| fig.cap: > #| Here is my really long caption. It'd be nice to #| split this and other portions across lines library(A) ``` renv/tests/testthat/resources/bslib.Rmd0000644000176200001440000000010314731330073017772 0ustar liggesusers--- output: html_document: theme: version: 4 --- renv/tests/testthat/resources/learnr-exercise.Rmd0000644000176200001440000000011314731330073021770 0ustar liggesusers ```{r exercise=TRUE} x <- _____ ``` ```{r exercise=FALSE} library(A) ``` renv/tests/testthat/resources/magrittr.R0000644000176200001440000000006614731330073020217 0ustar liggesusers'A' %>% library 'B' %>% library() 'C' %>% library(.) renv/tests/testthat/resources/import.R0000644000176200001440000000172414731330073017702 0ustar liggesusers # Capture packages (a, b, ...), not functions or other objects (f1, f2, ...) # Do not capture packages in invalid calls (x1, x2, ...) import::from(a, f1, f2, f3) import::from(f1, .from = b) # valid uses of import::here import::here(f1, f2, f3, .from = c) # invalid uses of import::here – should not infer a dependency import::here(f1, x2) # no .from argument import::here(f1, f2, f3, x3) # as above import::here(f1) # no package is specified # valid uses of import::into import::into(f1, f2, f3, .into = "imports::pkg", .from = d) import::into("imports::pkg", f1, f2, .from = e) # invalid uses of import::into import::into(f1, f2, x4, .into = "imports::pkg") # no .from argument import::into(f1, x5) # no .from or .into import::into(f1) # no package specified at all # ignore usages that aren't namespace-prefixed from(A) # ignore .character_only with symbol import::from(B, .character_only = TRUE) # ignore things that look like scripts import::from("./module.R") renv/tests/testthat/resources/shiny-prerendered.Rmd0000644000176200001440000000012514731330073022332 0ustar liggesusers--- title: "shiny_prerendered" output: html_document runtime: shiny_prerendered --- renv/tests/testthat/resources/notebook.ipynb0000644000176200001440000000154614731330073021132 0ustar liggesusers{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "Some markdown text" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false } }, "outputs": [], "source": [ "library(MASS)" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false } }, "outputs": [], "source": [ "stats::setNames()" ] } ], "metadata": { "kernelspec": { "display_name": "R", "language": "R", "name": "ir" }, "language_info": { "codemirror_mode": "r", "file_extension": ".r", "mimetype": "text/x-r-source", "name": "R", "pygments_lexer": "r", "version": "4.0.3" } }, "nbformat": 4, "nbformat_minor": 4 } renv/tests/testthat/resources/inline-chunks.Rmd0000644000176200001440000000026414731330073021456 0ustar liggesusers ## Inline Chunks Users might request the use of packages with `r inline::chunks()`. Check that we handle `r multiple::calls()` on the same line. Also in `r separate::chunks()`. renv/tests/testthat/resources/chunk-eval.Rmd0000644000176200001440000000014014731330073020735 0ustar liggesusers ```{r} #| eval: !expr knitr::is_html_output() library(A) ``` ```{r} #| eval: F library(a) ``` renv/tests/testthat/resources/empty-chunk.Rmd0000644000176200001440000000003614731330073021150 0ustar liggesusers ## Empty Chunks ```{r} ``` renv/tests/testthat/resources/manifest.json0000644000176200001440000000422014731330073020740 0ustar liggesusers{ "version": 1, "locale": "de_DE", "platform": "4.2.1", "metadata": { "appmode": "shiny", "primary_rmd": null, "primary_html": null, "content_category": null, "has_parameters": false }, "packages": { "MASS": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "MASS", "Priority": "recommended", "Version": "7.3-57", "Date": "2022-04-05", "Revision": "$Rev: 3564 $", "Depends": "R (>= 3.3.0), grDevices, graphics, stats, utils", "Imports": "methods", "Suggests": "lattice, nlme, nnet, survival", "Authors@R": "c(person(\"Brian\", \"Ripley\", role = c(\"aut\", \"cre\", \"cph\"),\n email = \"ripley@stats.ox.ac.uk\"),\n\t person(\"Bill\", \"Venables\", role = \"ctb\"),\n\t person(c(\"Douglas\", \"M.\"), \"Bates\", role = \"ctb\"),\n\t person(\"Kurt\", \"Hornik\", role = \"trl\",\n comment = \"partial port ca 1998\"),\n\t person(\"Albrecht\", \"Gebhardt\", role = \"trl\",\n comment = \"partial port ca 1998\"),\n\t person(\"David\", \"Firth\", role = \"ctb\"))", "Description": "Functions and datasets to support Venables and Ripley,\n \"Modern Applied Statistics with S\" (4th edition, 2002).", "Title": "Support Functions and Datasets for Venables and Ripley's MASS", "LazyData": "yes", "ByteCompile": "yes", "License": "GPL-2 | GPL-3", "URL": "http://www.stats.ox.ac.uk/pub/MASS4/", "Contact": "", "NeedsCompilation": "yes", "Packaged": "2022-04-19 04:13:16 UTC; ripley", "Author": "Brian Ripley [aut, cre, cph],\n Bill Venables [ctb],\n Douglas M. Bates [ctb],\n Kurt Hornik [trl] (partial port ca 1998),\n Albrecht Gebhardt [trl] (partial port ca 1998),\n David Firth [ctb]", "Maintainer": "Brian Ripley ", "Repository": "CRAN", "Date/Publication": "2022-04-22 11:45:27 UTC", "Built": "R 4.2.0; x86_64-pc-linux-gnu; 'Fri, 22 Apr 2022 13:37:54 -0500'; unix" } } } } renv/tests/testthat/resources/parsnip.R0000644000176200001440000000012214731330073020033 0ustar liggesusersmod <- logistic_reg(mixture = 1/3) %>% set_engine("glmnet", nlambda = 10) renv/tests/testthat/resources/quarto-r-chunks.qmd0000644000176200001440000000007514731330073022011 0ustar liggesusers--- title: Quarto Document --- ```{r} # an R code chunk ``` renv/tests/testthat/resources/knitr-spin.R0000644000176200001440000000015314731330073020461 0ustar liggesusers#' --- #' title: Palmer Penguins #' author: Norah Jones #' date: 3/12/23 #' format: html #' --- print(42) renv/tests/testthat/resources/properties.txt0000644000176200001440000000013714731330073021177 0ustar liggesusersKey1="Value 1" Key2="Value \"2\"" Key3='Value \'3\'' Key4=This value spans multiple lines. renv/tests/testthat/resources/ignore.Rmd0000644000176200001440000000013714731330073020171 0ustar liggesusers ```{r, renv.ignore=TRUE} library(a) ``` ```{r, eval=FALSE, renv.ignore=FALSE} library(A) ``` renv/tests/testthat/resources/empty-label.Rmd0000644000176200001440000000006714731330073021123 0ustar liggesusers ```{r , echo=TRUE} # uh oh library(A) library(B) ``` renv/tests/testthat/resources/utility0000644000176200001440000000007114731330073017665 0ustar liggesusers#!/usr/bin/env Rscript library(A) B::C() # vim: ft=r renv/tests/testthat/resources/modules-empty.R0000644000176200001440000000004614731330073021170 0ustar liggesusers example <- function() { module() } renv/tests/testthat/resources/box.R0000644000176200001440000000016114731330073017152 0ustar liggesusersbox::use(.) box::use(A) box::use(b = B) box::use(C[...]) box::use(D[c, d]) box::use(E/abc) box::use(F/def[g, h]) renv/tests/testthat/resources/evil.Rmd0000644000176200001440000000007614731330073017647 0ustar liggesusers ```{r} # this is evil knitr library(a) ```{r} library(b) ``` renv/tests/testthat/resources/eval.Rmd0000644000176200001440000000007614731330073017637 0ustar liggesusers ```{r, eval=T} library(A) ``` ```{r, eval=F} library(a) ``` renv/tests/testthat/resources/rmd-base-format.Rmd0000644000176200001440000000015014731330073021661 0ustar liggesusers--- title: "Uses base_format" output: bookdown::pdf_book: base_format: rticles::plos_article --- renv/tests/testthat/resources/glue.R0000644000176200001440000000043714731330073017324 0ustar liggesusers glue( "{library(A)}", "{ library(B) }" ) glue( "<< <{{ library(C) }}> >>", .open = "<", .close = ">" ) glue( "{{ {library(D); { library(E) }; library(F)} }}" ) glue( "Include: [[ library(G) ]]; Not included: [[[[ library(h) ]]]]", .open = "[[", .close = "]]" ) renv/tests/testthat/resources/multiple-output-formats.Rmd0000644000176200001440000000025114731330073023545 0ustar liggesusers--- title: "Test" author: "Test" date: "3/9/2021" output: bookdown::pdf_document2: number_sections: false bookdown::html_document2: number_sections: false --- renv/tests/testthat/resources/targets.R0000644000176200001440000000010714731330073020033 0ustar liggesuserstar_option_set(packages = c("A", "B")) tar_option_set(packages = oops) renv/tests/testthat/resources/code.R0000644000176200001440000000066314731330073017303 0ustar liggesusers # should be parsed as dependencies (use only lower-case letters for package names) library(a) library("b") base::library(c) base::library("d", character.only = TRUE) requireNamespace("e") base::requireNamespace("f", quietly = TRUE) xfun::pkg_attach(c("g", "h")) pkg_attach2("i", "j") k::foo() l:::bar() "m"::baz() # should NOT be parsed as dependencies (use only upper-case names for package names) library(A, character.only = TRUE) renv/tests/testthat/resources/chunk-errors.Rmd0000644000176200001440000000036614731330073021334 0ustar liggesusers ## Parse Errors renv should still be able to recover dependencies from the chunks without parse errors. ```{r} library(dplyr) ``` ```{r} this chunk has parse errors ``` ```{r} and so does this chunk ``` ```{r} we forgot to close this chunk renv/tests/testthat/resources/params.Rmd0000644000176200001440000000013614731330073020170 0ustar liggesusers--- params: x: !r A::a() # should be parsed y: B::b() # should _not_ be parsed --- renv/tests/testthat/resources/DESCRIPTION0000644000176200001440000000025614731330073017752 0ustar liggesusersType: Package Description: This is a dummy package. The description has multiple lines. `dummy::dummy()`. Package: dummy Imports: , a Depends: b, , , c LinkingTo: renv/tests/testthat/resources/yaml-chunks.Rmd0000644000176200001440000000011114731330073021131 0ustar liggesusers ```{r} #| eval: false library(a) ``` ```{r} #| b=TRUE library(A) ``` renv/tests/testthat/test-ffi.R0000644000176200001440000000163214731330073016075 0ustar liggesusers test_that("all ffi methods have matching formal definitions", { methods <- ls(envir = renv_envir_self(), pattern = "^__ffi__") map(methods, function(method) { lhs <- get(method, envir = renv_envir_self()) rhs <- get(substring(method, 8L), envir = renv_envir_self()) expect_identical(formals(lhs), formals(rhs)) }) }) test_that("the renv extensions library was loaded if available", { # get path to shared library nspath <- renv_namespace_path("renv") soname <- if (renv_platform_windows()) "renv.dll" else "renv.so" libsdir <- renv_package_libsdir(nspath) sopath <- file.path(libsdir, soname) # if it exists, it should be loaded if (file.exists(sopath)) { dllinfo <- find(library.dynam(), function(dllinfo) { if (identical(dllinfo[["name"]], "renv")) return(dllinfo) }) expect_true(renv_path_same(sopath, dllinfo[["path"]])) } }) renv/tests/testthat/test-run.R0000644000176200001440000000127314731330073016136 0ustar liggesusers test_that("run() can be called with arguments", { project <- renv_tests_scope() dir.create("renv", recursive = TRUE, showWarnings = FALSE) writeLines("# stub", con = "renv/activate.R") output <- tempfile("renv-output-") script <- renv_test_code({ tmpfile <- tempfile(pattern = "renv-script-", tmpdir = dirname(output)) writeLines(commandArgs(trailingOnly = TRUE), con = tmpfile) file.rename(tmpfile, output) }, list(output = output)) args <- c("--apple", "--banana", "--cherry") run( script = script, args = args, project = getwd() ) wait(file.exists, output) Sys.sleep(0.1) contents <- readLines(output) expect_equal(contents, args) }) renv/tests/testthat/test-acls.R0000644000176200001440000000176514731330073016262 0ustar liggesusers test_that("renv_acls_reset() can reset ACLs", { skip_on_cran() skip_if(!renv_platform_linux()) skip_if(!nzchar(Sys.which("getfacl"))) skip_if(!nzchar(Sys.which("setfacl"))) renv_scope_tempdir() # create some directories unlink("source", recursive = TRUE) unlink("target", recursive = TRUE) # in this scenario, we'll move 'source/directory' to 'target/directory', # and check that the ACLs from 'target' are applied to 'target/directory' # when all is said and done dir.create("source") dir.create("target") # try setting some ACLs on the source directory; # this basically disallows any 'other' access system("setfacl -d -m o::- source") dir.create("source/directory") # try copying the directory; the ACLs should be copied system("cp -R source/directory target/directory") # try resetting its ACL renv_acls_reset("target/directory") # check that the permissions (via ACLs) were preserved expect_equal(file.mode("target"), file.mode("target/directory")) }) renv/tests/testthat/helper-testthat.R0000644000176200001440000000422114731330073017466 0ustar liggesusers # a wrapper around testthat::test_that(), which also tries # to confirm that the test hasn't mutated any global state test_that <- function(desc, code) { # skip tests when run on CRAN's macOS machine cran <- !interactive() && !identical(Sys.getenv("NOT_CRAN"), "true") testthat::skip_if(cran && renv_platform_macos()) # record global state before test execution before <- renv_test_state(cran) # run the test call <- sys.call() call[[1L]] <- quote(testthat::test_that) withCallingHandlers( eval(call, envir = parent.frame()), condition = function(cnd) { message <- conditionMessage(cnd) if (any(grepl("SSL connect error", message))) skip(message) } ) # record global state after test execution after <- renv_test_state(cran) # check for unexpected changes diffs <- waldo::compare(before, after) if (length(diffs)) { fdiffs <- paste(format(diffs), collapse = "\n\n") stopf("Test '%s' has modified global state:\n%s\n", desc, fdiffs) } } renv_test_state <- function(cran) { repopath <- renv_tests_repopath() userpath <- file.path(renv_bootstrap_user_dir(), "library") opts <- options() opts <- opts[grep("^diffobj", names(opts), invert = TRUE)] opts$ambiguousMethodSelection <- NULL opts$restart <- NULL opts$repos[opts$repos == "@CRAN@"] <- "https://cloud.r-project.org" opts$mc.cores <- NULL opts <- opts[csort(names(opts))] envvars <- as.list(Sys.getenv()) envvars <- envvars[grep("^RENV_DEFAULT_", names(envvars), invert = TRUE)] envvars <- envvars[grep("^R_PACKRAT_", names(envvars), invert = TRUE)] envvars <- envvars[grep("^_R_", names(envvars), invert = TRUE)] envvars <- envvars[grep("^CALLR_", names(envvars), invert = TRUE)] envvars$RETICULATE_MINICONDA_PYTHON_ENVPATH <- NULL envvars$OMP_NUM_THREADS <- NULL envvars$OPENBLAS <- NULL envvars <- envvars[csort(names(envvars))] list( libpaths = if (!cran) .libPaths(), connections = getAllConnections(), options = opts, repofiles = list.files(repopath, all.files = TRUE, no.. = TRUE), userfiles = list.files(userpath, all.files = TRUE, no.. = TRUE), envvars = envvars ) } renv/tests/testthat/test-deactivate.R0000644000176200001440000000035614731330073017444 0ustar liggesuserstest_that("deactivate(clean = TRUE) removes all files", { renv_tests_scope() init() deactivate(clean = TRUE) expect_false(file.exists("renv.lock")) expect_false(file.exists("renv")) expect_false(file.exists(".Rprofile")) }) renv/tests/testthat/test-enumerate.R0000644000176200001440000000346614731330073017325 0ustar liggesusers test_that("enumerate() handles identity maps", { data <- list( as.integer(1:10), as.numeric(1:10), as.logical(1:10), letters[1:10] ) for (i in seq_along(data)) { expect_identical( enumerate(data[[i]], function(key, value) value, FUN.VALUE = data[[i]][[1L]]), data[[i]] ) } }) test_that("enumerate() works as expected", { zip <- function(key, value) list(key, value) data <- list(a = 1, b = 2, c = 3) actual <- enumerate(data, zip) expected <- list(a = list("a", 1), b = list("b", 2), c = list("c", 3)) expect_identical(actual, expected) data <- list(a = "1", b = "2", c = "3") actual <- enumerate(data, zip) expected <- list(a = list("a", "1"), b = list("b", "2"), c = list("c", "3")) expect_identical(actual, expected) data <- list2env(list(a = "1", b = "2", c = "3")) actual <- enumerate(data, zip) expected <- list(a = list("a", "1"), b = list("b", "2"), c = list("c", "3")) actual <- actual[order(names(actual))] expected <- expected[order(names(expected))] expect_identical(actual, expected) }) test_that("enumerate() handles dots", { values <- list() data <- list(a = 1, b = 2, c = 3) enumerate(data, function(key, value, extra) { values[[length(values) + 1L]] <<- list(key, value, extra) }, extra = TRUE) expect_identical(values, list( list("a", 1, TRUE), list("b", 2, TRUE), list("c", 3, TRUE) )) }) test_that("enum_chr() does what it should", { actual <- enum_chr( list(a = "1", b = "2", c = "3"), function(key, value) value ) expected <- c(a = "1", b = "2", c = "3") expect_identical(actual, expected) actual <- enum_chr( list(1, 2, 3), function(key, value) as.character(value) ) expected <- c("1", "2", "3") expect_identical(actual, expected) }) renv/tests/testthat/test-expr.R0000644000176200001440000000106414731330073016306 0ustar liggesusers test_that("expr() works as expected", { value <- 42 actual <- expr({ x <- !!value }) expected <- quote({ x <- 42 }) expect_identical(actual, expected) }) test_that("expr() also supports expressions", { actual <- expr({ pid <- !!Sys.getpid() }) expected <- substitute({ pid <- id }, list(id = Sys.getpid())) expect_identical(actual, expected) }) test_that("expr() repairs parse trees", { a <- TRUE; b <- FALSE actual <- expr(!!a + !!b) expected <- quote(TRUE + FALSE) expect_identical(actual, expected) }) renv/tests/testthat/test-renvignore.R0000644000176200001440000001000614731330073017502 0ustar liggesusers test_that(".renvignore ignores files, directories", { renv_tests_scope("oatmeal") writeLines(c("internal/", "data.R"), con = ".renvignore") # data file at root writeLines("library(breakfast)", con = "data.R") # internal folder at root dir.create("internal") writeLines("library(breakfast)", con = "internal/script.R") # internal subfolder dir.create("scripts/internal", recursive = TRUE) writeLines("library(breakfast)", con = "scripts/internal/script.R") deps <- dependencies(root = getwd()) expect_setequal(deps$Package, "oatmeal") deps <- dependencies("scripts", root = getwd()) expect_true(NROW(deps) == 0L) }) test_that("ignore patterns are constructed correctly", { # ignore all data directories in project expect_equal( renv_renvignore_parse_impl("data", "/project"), "^\\Q/project/\\E(?:.*/)?\\Qdata\\E(?:/)?$" ) # ignore data directory at root of project expect_equal( renv_renvignore_parse_impl("/data", "/project"), "^\\Q/project/data\\E(?:/)?$" ) # multiple ignores are parsed separately expect_equal( renv_renvignore_parse_impl(c("/data1", "/data2"), "/project"), c( "^\\Q/project/data1\\E(?:/)?$", "^\\Q/project/data2\\E(?:/)?$" ) ) # sub-directory ignores are handled expect_equal( renv_renvignore_parse_impl("data/internal", "/project"), "^\\Q/project/data/internal\\E(?:/)?$" ) # negations are handled expect_equal( renv_renvignore_parse(c("abc.R", "!def.R")), list( exclude = "^\\Q/\\E(?:.*/)?\\Qabc.R\\E(?:/)?$", include = "^\\Q/\\E(?:.*/)?\\Qdef.R\\E(?:/)?$" ) ) }) test_that("empty .renvignore does not ignore anything", { renv_tests_scope("oatmeal") file.create(".renvignore") deps <- dependencies() expect_true("oatmeal" %in% deps$Package) }) test_that("negated .renvignore patterns are handled", { renv_tests_scope() writeLines(c("script.R", "!script.R"), con = ".renvignore") writeLines("library(foo)", con = "script.R") deps <- dependencies() expect_true("foo" %in% deps$Package) }) test_that("ignores with a trailing slash are handled", { renv_tests_scope() writeLines("dir.R/", con = ".renvignore") # dir.R is a file, not a directory, so include it writeLines("library(foo)", con = "dir.R") deps <- dependencies() expect_true("foo" %in% deps$Package) # dir.R is, in fact, a directory, so ignore it unlink("dir.R") dir.create("dir.R") writeLines("library(bar)", con = "dir.R/deps.R") deps <- dependencies() expect_false("bar" %in% deps$Package) # dotfile exclusions can be overridden writeLines(c("dir.R/", "!dir.R/"), con = ".renvignore") deps <- dependencies() expect_true("bar" %in% deps$Package) }) test_that(".renvignore can be used to ignore all but certain files", { renv_tests_scope() writeLines(c("*", "!dependencies.R"), con = ".renvignore") writeLines("library(oatmeal)", con = "script.R") writeLines("library(bread)", con = "dependencies.R") deps <- dependencies() expect_true("bread" %in% deps$Package) expect_false("oatmeal" %in% deps$Package) }) test_that("ignores can be set via option if required", { renv_tests_scope() dir.create("data") writeLines("library(A)", con = "data/script.R") dir.create("inst") writeLines("library(B)", con = "inst/script.R") dir.create("ok") writeLines("library(C)", con = "ok/script.R") exclude <- structure(c("/data/", "/inst/"), asis = TRUE) renv_scope_options(renv.renvignore.exclude = exclude) deps <- dependencies() expect_setequal(deps$Package, "C") }) test_that("sub-directory inclusion rules are handled properly", { renv_tests_scope() dir.create("dir/ignored", recursive = TRUE) writeLines("library(A)", con = "dir/ignored/script.R") dir.create("dir/matched", recursive = TRUE) writeLines("library(B)", con = "dir/matched/script.R") rules <- heredoc(" dir/* !dir/matched ") writeLines(rules, con = ".gitignore") # system("git init && git add -A && git status") deps <- dependencies() expect_setequal(deps$Package, "B") }) renv/tests/testthat/test-dots.R0000644000176200001440000000043014731330073016275 0ustar liggesusers test_that("renv_dots_check only sets bioconductor from bioc is not already set", { f <- function(..., bioconductor = NULL) { renv_dots_check(...) bioconductor } expect_true(f(bioc = TRUE)) expect_snapshot(f(bioconductor = FALSE, bioc = TRUE), error = TRUE) }) renv/tests/testthat/test-retrieve.R0000644000176200001440000001771614731330073017170 0ustar liggesusers test_that("we can retrieve packages from CRAN", { skip_slow() renv_tests_scope() record <- list( Package = "oatmeal", Version = "1.0.0", Source = "CRAN" ) renv_test_retrieve(record) }) test_that("we can retrieve packages from the CRAN archive", { skip_slow() renv_tests_scope() record <- list( Package = "bread", Version = "0.1.0", Source = "CRAN" ) renv_test_retrieve(record) }) test_that("packages with an unknown source are retrieved from CRAN", { skip_slow() renv_tests_scope() record <- list( Package = "bread", Version = "0.1.0", Source = "unknown" ) renv_test_retrieve(record) }) test_that("we can retrieve packages from Bitbucket", { skip_on_cran() skip("unreliable test") record <- list( Package = "skeleton", Source = "bitbucket", RemoteRepo = "skeleton", RemoteUsername = "kevinushey", RemoteSha = "958296dbbbf7f1d82f7f5dd1b121c7558604809f" ) renv_test_retrieve(record) }) test_that("we can retrieve packages from git", { skip_on_cran() skip("unreliable test") record <- list( Package = "skeleton", Source = "git", RemoteUrl = "git://github.com/kevinushey/skeleton.git", RemoteSha = "958296dbbbf7f1d82f7f5dd1b121c7558604809f" ) renv_test_retrieve(record) }) test_that("we can retrieve packages with git dependencies", { skip_on_cran() skip_slow() # GitHub doesn't like ssh (used as remote field in renv.git1) skip_on_ci() record <- list( Package = "renv.git1", Source = "git", RemoteUrl = "https://github.com/kevinushey/renv.git1", RemoteRef = "main" ) renv_test_retrieve(record) }) test_that("we can retrieve packages from GitHub", { skip_slow() record <- list( Package = "skeleton", Source = "github", RemoteUsername = "kevinushey", RemoteRepo = "skeleton", RemoteSha = "958296dbbbf7f1d82f7f5dd1b121c7558604809f" ) renv_test_retrieve(record) }) test_that("we can retrieve packages from GitHub (in a sub-directory)", { skip_slow() record <- list( Package = "subdir", Source = "github", RemoteUsername = "kevinushey", RemoteRepo = "subdir", RemoteSubdir = "subdir", RemoteSha = "100373b23c8adae1da4e4d6995402d40e9227cfb" ) renv_test_retrieve(record) }) test_that("we can retrieve packages from GitLab", { skip_slow() record <- list( Package = "skeleton", Source = "gitlab", RemoteUsername = "kevinushey", RemoteRepo = "skeleton", RemoteSha = "958296dbbbf7f1d82f7f5dd1b121c7558604809f" ) renv_test_retrieve(record) }) test_that("we can retrieve packages with URLs", { skip_slow() url <- "https://api.github.com/repos/kevinushey/skeleton/tarball" record <- renv_remotes_resolve(url) renv_test_retrieve(record) }) test_that("we can retrieve packages from URL sources", { skip_slow() renv_tests_scope() renv_scope_local() record <- list( Package = "skeleton", Version = "1.0.1", Source = "URL", RemoteType = "url", RemoteUrl = "https://api.github.com/repos/kevinushey/skeleton/tarball" ) renv_test_retrieve(record) }) test_that("we can retrieve packages from local sources", { renv_tests_scope() renv_scope_local() record <- list( Package = "skeleton", Version = "1.0.1", Source = "local" ) renv_test_retrieve(record) }) test_that("compatible local sources are preferred when available", { renv_tests_scope() renv_scope_local() record <- list( Package = "skeleton", Version = "1.0.1", Source = "CRAN" ) renv_test_retrieve(record) record <- list( Package = "skeleton", Version = "1.0.1", Source = "unknown" ) renv_test_retrieve(record) }) test_that("an explicitly-provided local source path can be used", { renv_tests_scope() renv_scope_local() source <- renv_tests_path("local/skeleton/skeleton_1.0.1.tar.gz") renv_scope_wd(tempdir()) record <- list( Package = "skeleton", Version = "1.0.1", Source = source ) renv_test_retrieve(record) }) test_that("explicit path to binary packages work", { skip_if_not(renv_platform_macos()) renv_tests_scope() renv_scope_local() # make sure we have a binary package in the cellar to test with srcpath <- renv_tests_path("local/skeleton/skeleton_1.0.1.tar.gz") binpath <- renv_tests_path("local/skeleton/skeleton_1.0.1.tgz") defer(unlink(binpath)) # create the binary local({ renv_scope_wd(dirname(srcpath)) args <- c("CMD", "INSTALL", "--build", basename(srcpath)) renv_system_exec(R(), args) }) record <- list( Package = "skeleton", Version = "1.0.1", Source = binpath ) renv_test_retrieve(record) }) test_that("remotes::install_local() records are handled", { renv_scope_envvars(RENV_PATHS_LOCAL = NULL) record <- list( Package = "skeleton", Version = "1.0.1", Source = "local", RemoteUrl = renv_tests_path("local/skeleton/skeleton_1.0.1.tar.gz") ) renv_test_retrieve(record) }) test_that("we can retrieve packages from GitHub", { skip_slow() record <- list( Package = "skeleton", Source = "github", RemoteHost = "https://api.github.com", RemoteUsername = "kevinushey", RemoteRepo = "skeleton", RemoteSha = "958296dbbbf7f1d82f7f5dd1b121c7558604809f" ) renv_test_retrieve(record) }) test_that("we can retrieve packages from R repositories", { skip_on_cran() renv_tests_scope() record <- list( Package = "oatmeal", Version = "1.0.0", Source = "Repository", Repository = "CRAN" ) renv_test_retrieve(record) }) test_that("we can retrieve files using file URIs", { skip_on_cran() renv_tests_scope() source <- file.path(getwd(), "source") target <- file.path(getwd(), "target") writeLines("Hello, world!", con = source) # plain 'file:' URI with no authority uri <- paste("file:", source, sep = "") download(uri, destfile = target) expect_equal(readLines(target), "Hello, world!") unlink(target) # file URI using empty authority prefix <- if (renv_platform_windows()) "file:///" else "file://" uri <- paste(prefix, source, sep = "") download(uri, destfile = target) expect_equal(readLines(target), "Hello, world!") unlink(target) }) test_that("records with RemoteSha successfully retrieved from archives", { renv_tests_scope() record <- list( Package = "bread", Version = "0.1.0", Source = "Repository", RemoteSha = "oops" ) renv_test_retrieve(record) }) test_that("we respect the default branch for gitlab repositories", { skip_on_cran() remote <- renv_remotes_resolve("gitlab::kevinushey/main") expect_equal(remote$RemoteRef, "main") }) test_that("renv can retrieve the latest release associated with a project", { skip_if_no_github_auth() remote <- renv_remotes_resolve("rstudio/keras@*release") expect_true(is.list(remote)) }) test_that("retrieve handles local sources", { skip_on_cran() renv_tests_scope() record <- list( Package = "bread", Version = "1.0.0", Source = "bread_1.0.0.tar.gz" ) expect_error(renv_test_retrieve(record)) # call download.packages once to get URL url <- download.packages("bread", destdir = getwd()) if (!file.exists(record$Source)) file.copy(url[1, 2], record$Source) renv_test_retrieve(record) }) test_that("we can use retrieve() to download packages without installing", { project <- renv_tests_scope() init() result <- retrieve(packages = "breakfast") expect_false(renv_package_installed("breakfast")) expect_contains(names(result), "breakfast") expect_contains(names(result), "bread") result <- retrieve(packages = "bread", destdir = ".") expect_equal(result, c(bread = "./bread_1.0.0.tar.gz")) install("bread") result <- retrieve(packages = "bread", destdir = ".") expect_equal(result, c(bread = "./bread_1.0.0.tar.gz")) }) renv/tests/testthat/test-bind.R0000644000176200001440000000302614731330073016244 0ustar liggesusers test_that("bind() handles named lists", { data <- list( alpha = list(A = 1, B = 2), beta = list(A = 3, B = 4), gamma = list(A = 5, B = 6) ) actual <- bind(data) expected <- data.frame( Index = names(data), A = c(1, 3, 5), B = c(2, 4, 6), stringsAsFactors = FALSE ) expect_equal(actual, expected) }) test_that("bind() warns on name collision", { data <- list(alpha = list(Index = 1), beta = list(Index = 2)) expect_error(bind(data)) }) test_that("bind() handles data.frames with potentially different names", { data <- list( a = data.frame(A = 1, B = 2), b = data.frame(A = 1, C = 3) ) bound <- bind(data) expected <- data.frame( Index = c("a", "b"), A = c(1, 1), B = c(2, NA), C = c(NA, 3), stringsAsFactors = FALSE ) expect_identical(bound, expected) }) test_that("bind() preserves order where possible", { data <- list( a = data.frame(A = 1, C = 3), b = data.frame(A = 1, B = 2, C = 3) ) bound <- bind(data) expect_equal(names(bound), c("Index", "A", "B", "C")) }) test_that("bind() handles unnamed lists with explicit name", { data <- list( list(1, 2, 3), list(4, 5, 6), list(7, 8, 9) ) actual <- bind(data, names = c("A", "B", "C")) expected <- data.frame( A = c(1, 4, 7), B = c(2, 5, 8), C = c(3, 6, 9) ) expect_equal(actual, expected) }) renv/tests/testthat/test-lockfile.R0000644000176200001440000000672614753210661017136 0ustar liggesusers test_that("lockfiles can be diffed", { lhs <- list(A = 1, B = 2, C = "a", D = list(E = 1, F = 2)) rhs <- list(A = 1, B = 3, C = "b", D = list(E = 1, F = 3)) diff <- renv_lockfile_diff(lhs, rhs) expected <- list( B = list(before = 2, after = 3), C = list(before = "a", after = "b"), D = list( F = list(before = 2, after = 3) ) ) expect_identical(diff, expected) }) test_that("we can serialize lockfiles using unnamed repositories", { # no repositories set local({ renv_scope_options(repos = list()) actual <- renv_lockfile_init(project = NULL) json <- renv_lockfile_write(actual, file = NULL) expected <- renv_lockfile_read(text = json) expect_equal(actual, expected) }) # unnamed repositories set local({ renv_scope_options(repos = c("alpha", "beta")) actual <- renv_lockfile_init(project = NULL) json <- renv_lockfile_write(actual, file = NULL) expected <- renv_lockfile_read(text = json) expect_equal(actual, expected) }) }) test_that("we can create lockfiles from manifests", { skip_on_cran() lock <- renv_lockfile_from_manifest("resources/manifest.json") expect_equal(lock$R$Version, "4.2.1") expect_equal(lock$R$Repositories, list(CRAN = "https://cloud.r-project.org")) }) test_that("we create lockfile from a manifest automatically when no lockfile found", { skip_on_cran() project <- tempfile() dir.create(project) path <- renv_tests_path("resources/manifest.json") expected <- renv_lockfile_from_manifest(path) file.copy(path, file.path(project, "manifest.json")) # when called with `strict = TRUE` does not create manifest expect_error(renv_lockfile_load(project, strict = TRUE)) # creates and reads lockfile actual <- renv_lockfile_load(project) expect_identical(expected, actual) expect_true(file.exists(file.path(project, "renv.lock"))) unlink(project, recursive = TRUE) }) test_that("the Requirements field is read as character", { lockfile <- renv_lockfile_read(text = ' { "R": { "Version": "2.15.2", "Repositories": [] }, "Packages": { "morning": { "Package": "morning", "Version": "0.1.0", "Requirements": [ "coffee", "toast" ] } } } ') actual <- lockfile$Packages$morning$Requirements expected <- c("coffee", "toast") expect_identical(actual, expected) }) test_that("lockfile APIs can be used", { renv_tests_scope("breakfast") init() lockfile <- lockfile_read() expect_s3_class(lockfile, "renv_lockfile") # try writing some repositories repos <- list(CRAN = "https://cloud.r-project.org") lockfile <- lockfile_modify(lockfile, repos = repos) expect_equal(repos, lockfile$R$Repositories) # try updating a record lockfile <- lockfile_modify(lockfile, remotes = list(bread = "bread@0.1.0")) bread <- lockfile$Packages$bread expect_equal(bread$Version, "0.1.0") # try writing to file lockfile_write(lockfile) # check that it's the same expect_equal(lockfile, lockfile_read()) }) test_that("lockfiles with UTF-8 contents can be written, read", { author <- enc2utf8("cr\u00e8me br\u00fbl\u00e9e") Encoding(author) <- "UTF-8" lockfile <- list( Packages = list( example = list( Package = "example", Version = "1.0.0", Author = author ) ) ) path <- renv_scope_tempfile("renv-lockfile-") renv_lockfile_write(lockfile, file = path) actual <- renv_lockfile_read(path) expect_identical(unclass(actual), lockfile) }) renv/tests/testthat/test-archive.R0000644000176200001440000000513514731330073016754 0ustar liggesusers test_that("renv reports errors when decompressing invalid archives", { badtar <- renv_scope_tempfile(fileext = ".tar") writeLines("oh no", con = badtar) expect_error(renv_archive_decompress(badtar, verbose = TRUE)) badzip <- renv_scope_tempfile(fileext = ".zip") writeLines("oh no", con = badzip) expect_error(renv_archive_decompress(badzip)) }) test_that("we can successfully compress / decompress some sample files", { renv_scope_tempdir() for (letter in letters) writeLines(letter, con = letter) tarfile <- renv_scope_tempfile(fileext = ".tar.gz") tar(tarfile, files = ".") actual <- list.files() expected <- setdiff(basename(renv_archive_list(tarfile)), ".") expect_setequal(actual, expected) exdir <- renv_scope_tempfile() renv_archive_decompress(tarfile, exdir = exdir) expect_setequal(list.files(exdir), list.files()) zipper <- Sys.getenv("R_ZIPCMD", unset = "zip") if (nzchar(Sys.which(zipper))) { zipfile <- renv_scope_tempfile(fileext = ".zip") zip(zipfile, files = ".", extras = "-q") actual <- list.files() expected <- basename(renv_archive_list(zipfile)) expect_setequal(actual, expected) exdir <- renv_scope_tempfile() renv_archive_decompress(zipfile, exdir = exdir) expect_setequal(list.files(exdir), list.files()) } }) test_that("we can decompress an archive with a tilde path", { skip_on_cran() skip_on_windows() renv_tests_scope() renv_scope_envvars(HOME = getwd()) renv_scope_envvars(tar = Sys.which("tar")) # check that we actually set home correctly? if (!identical(path.expand("~"), getwd())) skip("couldn't override home path") # NOTE: in older versions of R, only paths to directory were accepted, # so we run out test by attempting to tar up a directory rather than file dir.create("subdir") writeLines("hello", con = "subdir/a.txt") writeLines("goodbye", con = "subdir/b.txt") tar("files.tar.gz", files = "subdir", compression = "gzip") unlink("subdir", recursive = TRUE) # double check the files we have in the archive # (renv_archive_list might report the folder itself so test files individually) files <- renv_archive_list("files.tar.gz") expect_true("subdir/a.txt" %in% files) expect_true("subdir/b.txt" %in% files) expect_false(file.exists("subdir/a.txt")) expect_false(file.exists("subdir/b.txt")) archive <- "~/files.tar.gz" if (!file.exists(archive)) skip("archive does not exist in home directory") renv_archive_decompress( archive = archive, exdir = getwd() ) expect_true(file.exists("subdir/a.txt")) expect_true(file.exists("subdir/b.txt")) }) renv/tests/testthat/test-lockfile-read.R0000644000176200001440000000026014731330073020026 0ustar liggesuserstest_that("renv_lockfile_read gives informative error", { file <- renv_scope_tempfile() writeLines("{", file) expect_snapshot(renv_lockfile_read(file), error = TRUE) }) renv/tests/testthat/test-internal.R0000644000176200001440000000211014761163114017140 0ustar liggesusers test_that("R files have balanced covr exclusions", { renv_scope_wd("../..") errors <- stack() files <- list.files("R", pattern = "[.][rR]$", full.names = TRUE) lapply(files, function(file) { nocov <- FALSE contents <- catch(readLines(file)) if (inherits(contents, "error")) { bulletin(values = "[%s]: %s", file, conditionMessage(contents)) return() } for (i in seq_along(contents)) { line <- contents[[i]] if (grepl("#\\s+nocov\\s+start\\s*$", line)) { if (nocov) { errors$push(list(file, i, "# nocov start")) } else { nocov <- TRUE } } if (grepl("#\\s+nocov\\s+end\\s*$", line)) { if (nocov) { nocov <- FALSE } else { errors$push(list(file, i, "# nocov end")) } } } }) invalid <- errors$data() if (length(invalid)) { lines <- map_chr(invalid, function(loc) { fmt <- "[%s:%i]: unexpected '%s'" sprintf(fmt, loc[[1]], loc[[2]], loc[[3]]) }) warning(lines) } expect_length(invalid, 0L) }) renv/tests/testthat/test-metadata.R0000644000176200001440000000076514731330073017117 0ustar liggesusers test_that("renv_metadata_version_friendly gives user friendly output", { expect_equal( renv_metadata_version_friendly(list(version = "1.0.0")), "1.0.0" ) expect_equal( renv_metadata_version_friendly(list(version = structure("1.0.0", sha = "abcd1234"))), "1.0.0 [sha: abcd123]" ) expect_equal( renv_metadata_version_friendly( metadata = list(version = structure("1.0.0", sha = "abcd1234")), shafmt = "; sha: %s" ), "1.0.0; sha: abcd123" ) }) renv/tests/testthat/test-filebacked.R0000644000176200001440000000141014731330073017374 0ustar liggesusers test_that("filebacked entries are discarded after the file is modified", { file <- renv_scope_tempfile("renv-test-") contents <- "Hello, world!" writeLines(contents, con = file) renv_filebacked_set("test", file, contents) expect_equal(renv_filebacked_get("test", file), contents) writeLines("Goodbye, world!", con = file) expect_identical(renv_filebacked_get("test", file), NULL) }) test_that("filebacked entries are discarded after the file is deleted", { file <- renv_scope_tempfile("renv-test-") contents <- "Hello, world!" writeLines(contents, con = file) renv_filebacked_set("test", file, contents) expect_equal(renv_filebacked_get("test", file), contents) unlink(file) expect_identical(renv_filebacked_get("test", file), NULL) }) renv/tests/testthat/test-repair.R0000644000176200001440000000376614731330073016625 0ustar liggesusers test_that("we can repair a broken project library", { skip_on_cran() renv_tests_scope("breakfast") init() # find breakfast in the cache, and delete it record <- list(Package = "breakfast", Version = "1.0.0") cachepath <- renv_cache_find(record) unlink(cachepath, recursive = TRUE) # check that the package no longer exists expect_false(renv_package_installed("breakfast")) # try to repair repair() # validate that we reinstalled it expect_true(renv_package_installed("breakfast")) }) test_that("repair() uses the package version recorded in the lockfile", { skip_on_cran() renv_tests_scope("breakfast") init() # install older breakfast from archive install("breakfast@0.1.0") snapshot() # find breakfast in the cache, and delete it record <- list(Package = "breakfast", Version = "0.1.0") cachepath <- renv_cache_find(record) unlink(cachepath, recursive = TRUE) # check that the package no longer exists expect_false(renv_package_installed("breakfast")) # try to repair repair() # validate that we reinstalled it expect_true(renv_package_installed("breakfast")) expect_true(renv_package_version("breakfast") == "0.1.0") }) test_that("repair() can update DESCRIPTION files for GitHub packages", { skip_on_cran() renv_tests_scope("skeleton") renv_scope_local() renv_scope_options(renv.config.cache.symlinks = FALSE) init() # mutate the installed package DESCRIPTION file, so that it appears # to be from GitHub descpath <- system.file("DESCRIPTION", package = "skeleton") desc <- renv_description_read(descpath) desc$Repository <- NULL desc$BugReports <- "https://github.com/kevinushey/skeleton/issues" renv_dcf_write(desc, file = descpath) metapath <- system.file("Meta/package.rds", package = "skeleton") meta <- readRDS(metapath) meta$DESCRIPTION <- desc saveRDS(meta, file = metapath) # try to repair, but cancel the request renv_scope_options(renv.menu.choice = 2L) expect_snapshot(. <- repair(), error = TRUE) }) renv/tests/testthat/test-activate.R0000644000176200001440000000300214731330073017122 0ustar liggesusers test_that("renv can bootstrap itself if not installed", { skip_on_cran() skip_on_os("windows") # initialize bare project renv_tests_scope() init(bare = TRUE) # simulate bootstrap of renv from mock library renv_infrastructure_write_activate(version = "1.0.0") renv_scope_envvars( RENV_BOOTSTRAP_INSTALL_ONLY = "1", RENV_CONFIG_REPOS_OVERRIDE = getOption("repos") ) # perform bootstrap args <- c("-s", "-e", shQuote("library(renv)")) stdout <- stderr <- if (interactive()) "" else FALSE status <- system2(R(), args, stdout = stdout, stderr = stderr) expect_equal(status, 0L) }) test_that("renv can bootstrap a dev version", { skip_slow() renv_tests_scope() init(bare = TRUE, restart = FALSE) version <- "1.0.0" attr(version, "sha") <- "5049cef8a" renv_infrastructure_write_activate(version = version) args <- c("-s", "-e", shQuote("requireNamespace(\"renv\")")) stdout <- stderr <- if (interactive()) "" else FALSE status <- system2(R(), args, stdout = stdout, stderr = stderr) expect_equal(status, 0L) # And should be fine if we run it again status <- system2(R(), args, stdout = stdout, stderr = stderr) expect_equal(status, 0L) }) test_that("activate_prompt behaves as expected", { renv_scope_options(renv.menu.choice = 2) expect_snapshot(val <- renv_activate_prompt_impl("snapshot")) expect_false(val) renv_tests_scope() renv_scope_options(renv.menu.choice = 1) expect_snapshot(val <- renv_activate_prompt_impl("snapshot")) expect_true(val) }) renv/tests/testthat/test-available-packages.R0000644000176200001440000001051514731330073021025 0ustar liggesusers test_that("available_packages() returns NULL when no repos set", { skip_on_cran() local({ renv_scope_options(repos = character()) expect_null(available_packages(type = "source")) }) local({ renv_scope_options(repos = list()) expect_null(available_packages(type = "source")) }) local({ renv_scope_options(repos = NULL) expect_null(available_packages(type = "source")) }) }) test_that("renv handles multiple available source packages", { skip_on_cran() renv_tests_scope() dbs <- available_packages(type = "source") cran <- dbs[["CRAN"]] entries <- cran[cran$Package == "breakfast", ] expect_true(nrow(entries) == 3) entry <- renv_available_packages_entry( package = "breakfast", type = "source" ) expect_true(nrow(entry) == 1) expect_true(entry$Package == "breakfast") expect_true(entry$Version == "1.0.0") }) test_that("available_packages() succeeds with unnamed repositories", { skip_on_cran() renv_tests_scope() entry <- renv_available_packages_entry( package = "breakfast", type = "source", filter = "1.0.0" ) expect_identical(entry$Package, "breakfast") expect_identical(entry$Version, "1.0.0") }) test_that("renv_available_packages_latest() respects pkgType option", { skip_on_cran() skip_if(.Platform$pkgType == "source") renv_tests_scope() renv_scope_options(pkgType = "source") record <- renv_available_packages_latest("breakfast") expect_identical(attr(record, "type"), "source") # NOTE: this fails because we don't populate binary repositories during tests renv_scope_options(renv.config.ppm.enabled = FALSE) renv_scope_options(pkgType = "binary") expect_error(renv_available_packages_latest("breakfast")) }) test_that("local sources are preferred when available", { skip_on_cran() renv_tests_scope() renv_scope_envvars(RENV_PATHS_LOCAL = renv_tests_path("local")) record <- renv_available_packages_latest(package = "skeleton", type = "source") expect_identical(record$Source, "Cellar") }) test_that("available packages database refreshed on http_proxy change", { skip_on_cran() skip_on_os("windows") renv_tests_scope_repos() renv_scope_envvars("https_proxy" = "123") available_packages(type = "source") count <- 0L renv_scope_trace( what = renv:::renv_available_packages_query, tracer = function() { count <<- count + 1L } ) renv_scope_envvars("https_proxy" = "") available_packages(type = "source") expect_identical(count, 1L) }) test_that("available packages prefer tagged repository", { skip_on_cran() skip_on_os("windows") renv_tests_scope() repos <- getOption("repos")[[1L]] renv_scope_options(repos = c(CRAN = repos, ALT = repos)) entry <- renv_available_packages_entry( package = "breakfast", type = "source", prefer = "ALT", quiet = TRUE ) expect_equal(entry$Name, "ALT") }) test_that("we're compatible with R", { skip_on_cran() renv_tests_scope() repos <- getOption("repos")[1L] lhs <- as.data.frame( available.packages( type = "source", repos = repos, filters = c("R_version", "OS_type") ), row.names = FALSE, stringsAsFactors = FALSE ) rhs <- available_packages( type = "source", repos = repos )[[1L]] row.names(lhs) <- row.names(rhs) <- NULL fields <- c("Package", "Version") expect_equal(lhs[fields], rhs[fields]) }) test_that("we can query the R universe", { skip_on_cran() lhs <- as.data.frame( available.packages( type = "source", repos = "https://rstudio.r-universe.dev" ), stringsAsFactors = FALSE ) rhs <- available_packages( type = "source", repos = "https://rstudio.r-universe.dev/" )[[1L]] # skip renv, since we just updated it lhs <- lhs[lhs$Package != "renv", ] rhs <- rhs[rhs$Package != "renv", ] # reduce risk of false positive test failures rownames(lhs) <- rownames(rhs) <- NULL lhs$MD5sum <- rhs$MD5sum <- NULL # otherwise, check they're identical expect_identical(lhs, rhs) }) test_that("available_packages() tolerates missing repositories", { renv_tests_scope() repos <- getOption("repos") repos[["NARC"]] <- file.path(repos[["CRAN"]], "missing") renv_scope_options(repos = repos) dbs <- available_packages(type = "source") expect_false(is.null(dbs[["CRAN"]])) expect_true(is.null(dbs[["NARC"]])) }) renv/tests/testthat/test-files.R0000644000176200001440000001206114740260564016440 0ustar liggesusers test_that("directories can be copied", { source <- renv_scope_tempfile("renv-source-") target <- renv_scope_tempfile("renv-target-") ensure_directory(source) range <- 1:10 files <- sprintf("%02i.txt", range) for (i in range) file.create(file.path(source, sprintf("%02i.txt", i))) expect_setequal(files, list.files(source)) renv_file_copy(source, target) expect_setequal(list.files(source), list.files(target)) }) test_that("directories can be moved", { source <- renv_scope_tempfile("renv-source-") target <- renv_scope_tempfile("renv-target-") ensure_directory(source) range <- 1:10 files <- sprintf("%02i.txt", range) for (i in range) file.create(file.path(source, sprintf("%02i.txt", i))) renv_file_move(source, target) expect_true(!file.exists(source)) expect_true(file.exists(target)) expect_setequal(files, list.files(target)) }) test_that("attempts to link files work", { source <- renv_scope_tempfile("renv-source-") target <- renv_scope_tempfile("renv-target-") dir.create(source) renv_file_link(source, target) expect_true(renv_file_same(source, target)) }) test_that("scoped backups are cleared as necessary", { source <- renv_scope_tempfile("renv-source-") target <- renv_scope_tempfile("renv-target-") writeLines("source", source) writeLines("target", target) local({ callback <- renv_file_backup(target) defer(callback()) expect_true(!file.exists(target)) }) expect_true(file.exists(target)) expect_equal(readLines(target), "target") local({ callback <- renv_file_backup(target) defer(callback()) writeLines("mutate", target) }) expect_true(file.exists(target)) expect_equal(readLines(target), "mutate") list.files(tempdir()) files <- list.files(tempdir()) backup <- grep("^\\.renv-backup-", files) expect_length(backup, 0) }) test_that("renv tempfiles are deleted at end of scope", { path <- NULL path2 <- NULL local({ path <<- renv_scope_tempfile() path2 <<- renv_scope_tempfile() file.create(path, path2) expect_true(file.exists(path)) expect_true(file.exists(path2)) }) expect_false(file.exists(path)) expect_false(file.exists(path2)) }) test_that("renv_file_find finds parent files", { base <- renv_scope_tempfile("renv-files-") rest <- c("alpha/beta/gamma") tip <- file.path(base, rest) ensure_directory(tip) found <- renv_file_find(tip, function(path) { if (basename(path) == "alpha") return(path) }) expect_true(renv_file_same(found, file.path(base, "alpha"))) }) test_that("attempts to overwrite existing files are handled appropriately", { source <- renv_scope_tempfile("renv-source-") target <- renv_scope_tempfile("renv-target-") writeLines("alpha", con = source) writeLines("beta", con = target) expect_error(renv_file_copy(source, target)) expect_true(renv_file_copy(source, target, overwrite = TRUE)) }) test_that("permissions, timestamps are preserved", { skip("failing test; mtime sometimes differs?") source <- renv_scope_tempfile("renv-source-") target <- renv_scope_tempfile("renv-target-") ensure_directory(source) range <- 1:10 files <- sprintf("%02i.txt", range) for (i in range) { Sys.sleep(0.01) file.create(file.path(source, sprintf("%02i.txt", i))) } renv_file_copy(source, target) srcfiles <- list.files(source, full.names = TRUE) tgtfiles <- list.files(target, full.names = TRUE) srcinfo <- renv_file_info(srcfiles) tgtinfo <- renv_file_info(tgtfiles) rownames(srcinfo) <- rownames(tgtinfo) <- basename(srcfiles) fields <- setdiff(names(srcinfo), c("ctime", "atime")) expect_equal(srcinfo[fields], tgtinfo[fields]) }) test_that("renv can list files not representable in the native encoding", { skip_if(renv_platform_unix() && !renv_l10n_utf8()) renv_scope_tempdir() evil <- "\u9b3c" file.create(evil) defer(unlink(evil)) files <- renv_file_list(getwd(), full.names = FALSE) expect_true(evil %in% files) }) test_that("renv can detect broken junctions / symlinks", { renv_scope_tempdir() if (renv_platform_windows()) { file.create("file") dir.create("dir") dir.create("nowhere") Sys.junction("dir", "junction") Sys.junction("nowhere", "broken") unlink("nowhere", recursive = TRUE) } else { file.create("file") dir.create("dir") file.symlink("file", "filelink") file.symlink("dir", "dirlink") file.symlink("oops", "broken") } paths <- list.files() broken <- renv_file_broken(paths) expect_equal(paths[broken], "broken") }) test_that("renv can detect junction points", { skip_on_cran() skip_if(!renv_platform_windows()) renv_scope_tempdir() dir.create("source") defer(unlink("source", recursive = TRUE)) # create some files -- should give the directory a size, # but this seems unreliable? files <- sprintf("source/%05i.txt", 1:100) file.create(files) # create a junction point Sys.junction("source", "junction") defer(unlink("junction", recursive = TRUE)) # check that they're the same expect_true(renv_file_same("source", "junction")) }) renv/tests/testthat/test-pak.R0000644000176200001440000000242714731330073016107 0ustar liggesusers test_that("renv::install() works in projects using pak", { skip_on_cran() skip_on_windows() skip_if_not_installed("pak") pak <- renv_namespace_load("pak") renv_scope_options(renv.config.pak.enabled = TRUE) project <- renv_tests_scope() # try installing an older version of 'breakfast' quietly(install("breakfast@0.1.0")) expect_true(renv_package_installed("breakfast")) expect_equal(renv_package_version("breakfast"), "0.1.0") # try using 'install()' to update all installed packages quietly(install()) expect_true(renv_package_installed("breakfast")) expect_equal(renv_package_version("breakfast"), "1.0.0") }) test_that("renv::update() works in projects using pak", { skip_on_cran() skip_on_windows() skip_if_not_installed("pak") pak <- renv_namespace_load("pak") renv_scope_options(renv.config.pak.enabled = TRUE) project <- renv_tests_scope() # try installing an older version of 'breakfast' quietly(install("breakfast@0.1.0")) expect_true(renv_package_installed("breakfast")) expect_equal(renv_package_version("breakfast"), "0.1.0") # try using 'update()' to update all installed packages quietly(update()) expect_true(renv_package_installed("breakfast")) expect_equal(renv_package_version("breakfast"), "1.0.0") }) renv/tests/testthat/test-settings.R0000644000176200001440000000463114731330073017173 0ustar liggesusers test_that("renv.settings can be used to provide defaults", { renv_tests_scope() expect_equal(settings$snapshot.type(), "implicit") # project is not yet initialized, so defaults can be used local({ renv_scope_options(renv.settings.snapshot.type = "all") expect_equal(settings$snapshot.type(), "all") }) local({ renv_scope_options(renv.settings = list(snapshot.type = "all")) expect_equal(settings$snapshot.type(), "all") }) }) test_that("non-persistent settings exist in R session; not in file", { renv_tests_scope() expect_equal(settings$snapshot.type(), "implicit") project <- getwd() path <- "renv/settings.json" before <- renv_settings_read_impl(path) settings$snapshot.type("all", persist = FALSE) after <- renv_settings_read_impl(path) expect_equal(before, after) expect_equal(settings$snapshot.type(), "all") settings$ignored.packages("dplyr", persist = TRUE) settings <- renv_settings_get(project) persisted <- renv_settings_read_impl(path) # TODO settings$ppm.ignored.urls <- persisted$ppm.ignored.urls <- NULL expect_mapequal(settings, persisted) }) test_that("users can request specific versions of R for lockfile", { renv_tests_scope() renv_scope_options(renv.settings.r.version = "4.0") init() lockfile <- renv_lockfile_load(getwd()) expect_identical(lockfile$R$Version, "4.0") }) test_that("project settings are migrated from dcf to json", { project <- renv_tests_scope() init() settings <- heredoc(" bioconductor.version: 3.16 external.libraries: ignored.packages: package.dependency.fields: Imports, Depends, LinkingTo r.version: snapshot.type: implicit use.cache: TRUE vcs.ignore.cellar: TRUE vcs.ignore.library: TRUE vcs.ignore.local: TRUE ") writeLines(settings, con = "renv/settings.dcf") old <- renv_settings_read(file.path(project, "renv/settings.dcf")) unlink("renv/settings.json") renv_settings_migrate(project) expect_true(file.exists("renv/settings.json")) new <- renv_settings_read(file.path(project, "renv/settings.json")) # TODO old$ppm.ignored.urls <- new$ppm.ignored.urls <- NULL expect_mapequal(old, new) }) test_that("a settings global option is handled correctly", { renv_tests_scope() renv_scope_options(renv.settings = list(ignored.packages = "odbc")) expect_equal(settings$ignored.packages(), "odbc") expect_null(settings$bioconductor.version()) }) renv/tests/testthat/test-restore.R0000644000176200001440000002006314731330073017013 0ustar liggesusers test_that("library permissions are validated before restore", { skip_on_os("windows") inaccessible <- renv_scope_tempfile() dir.create(inaccessible, mode = "0100") expect_false(renv_install_preflight_permissions(inaccessible)) }) test_that("restore() gives an error if no lockfile exists", { renv_tests_scope() expect_false(file.exists("renv.lock")) expect_error(restore()) }) test_that("we can restore packages after init", { skip_on_cran() renv_tests_scope("breakfast") init() libpath <- renv_paths_library() before <- list.files(libpath) unlink(renv_paths_library(), recursive = TRUE) restore() after <- list.files(libpath) expect_setequal(before, after) }) test_that("restore can recover when required packages are missing", { skip_on_cran() renv_tests_scope("breakfast") init() remove("oatmeal") snapshot(force = TRUE) unlink(renv_paths_library(), recursive = TRUE) restore() expect_true(renv_package_installed("oatmeal")) }) test_that("restore(clean = TRUE) removes packages not in the lockfile", { renv_tests_scope("oatmeal") init() renv_scope_options(renv.config.auto.snapshot = FALSE) install("bread") expect_true(renv_package_installed("bread")) restore(clean = TRUE) expect_false(renv_package_installed("bread")) }) test_that("renv.records can be used to override records during restore", { renv_tests_scope("bread") init() install("bread@0.1.0") snapshot() expect_equal(renv_package_version("bread"), "0.1.0") bread <- list(Package = "bread", Version = "1.0.0", Source = "CRAN") overrides <- list(bread = bread) renv_scope_options(renv.records = overrides) restore() expect_equal(renv_package_version("bread"), "1.0.0") }) test_that("install.staged works as expected", { renv_tests_scope("breakfast") init() library <- renv_paths_library(project = getwd()) install.opts <- list(breakfast = "--version") local({ renv_scope_options( renv.config.install.staged = TRUE, renv.config.install.transactional = TRUE, install.opts = install.opts ) renv_scope_envvars(RENV_PATHS_CACHE = renv_scope_tempfile()) unlink(renv_paths_library(), recursive = TRUE) expect_error(restore()) files <- list.files(library) expect_true(length(files) == 0L) }) local({ renv_scope_options( renv.config.install.staged = FALSE, renv.config.install.transactional = FALSE, install.opts = install.opts ) renv_scope_envvars(RENV_PATHS_CACHE = renv_scope_tempfile()) unlink(renv_paths_library(), recursive = TRUE) expect_error(restore()) files <- list.files(library) expect_true(length(files) != 0L) }) }) test_that("restore(lockfile = '/path/to/lockfile') works", { renv_tests_scope("bread") init() unlink(paths$library(), recursive = TRUE) restore(lockfile = "renv.lock") expect_true(renv_package_installed("bread")) unlink(paths$library(), recursive = TRUE) lockfile <- renv_lockfile_load(project = getwd()) restore(lockfile = "renv.lock") expect_true(renv_package_installed("bread")) }) test_that("restore(packages = <...>) works", { renv_tests_scope("breakfast") init() unlink(paths$library(), recursive = TRUE) restore(packages = "toast") expect_length(list.files(paths$library()), 2L) expect_true(renv_package_installed("bread")) expect_true(renv_package_installed("toast")) }) test_that("restore ignores packages of incompatible architecture", { renv_tests_scope(c("unixonly", "windowsonly")) init() if (renv_platform_unix()) { expect_true(renv_package_installed("unixonly")) expect_false(renv_package_installed("windowsonly")) lockfile <- renv_lockfile_read("renv.lock") package <- lockfile$Packages$unixonly expect_identical(package$OS_type, "unix") remove("unixonly") restore() expect_true(renv_package_installed("unixonly")) } else { expect_true(renv_package_installed("windowsonly")) expect_false(renv_package_installed("unixonly")) lockfile <- renv_lockfile_read("renv.lock") package <- lockfile$Packages$windowsonly expect_identical(package$OS_type, "windows") remove("windowsonly") restore() expect_true(renv_package_installed("windowsonly")) } }) test_that("restore handled records without version set", { renv_tests_scope() # create dummy lockfile snapshot() # read lockfile and add record without version lockfile <- renv_lockfile_load(project = getwd()) lockfile$Packages$bread <- list(Package = "bread", Source = "Repository") renv_lockfile_save(lockfile, project = getwd()) # try to restore restore() # check for success expect_true(renv_package_installed("bread")) expect_equal(renv_package_version("bread"), "1.0.0") }) test_that("restore doesn't re-use active library paths", { renv_tests_scope() renv_scope_options(renv.settings.snapshot.type = "all") lib1 <- renv_scope_tempfile("lib1") lib2 <- renv_scope_tempfile("lib2") ensure_directory(c(lib1, lib2)) .libPaths(c(lib2, .libPaths())) install("bread", library = lib2) expect_true(renv_package_installed("bread", lib.loc = lib2)) lockfile <- snapshot(library = lib2, lockfile = NULL) restore(library = lib1, lockfile = lockfile) expect_true(renv_package_installed("bread", lib.loc = lib1)) }) test_that("restore(exclude = <...>) excludes as expected", { renv_tests_scope("breakfast") init() remove(c("bread", "breakfast", "oatmeal", "toast")) restore(exclude = "breakfast") expect_false(renv_package_installed("breakfast")) }) test_that("restore works with explicit Source", { renv_tests_scope("breakfast") init() renv_scope_envvars( RENV_PATHS_LOCAL = NULL, RENV_PATHS_CACHE = NULL ) record <- list( Package = "skeleton", Version = "1.0.0", Source = renv_tests_path("local/skeleton/skeleton_1.0.0.tar.gz") ) renv_test_retrieve(record) lockfile <- renv_lockfile_init(project = getwd()) lockfile$Packages <- list(skeleton = record) renv_lockfile_write(lockfile, file = "renv.lock") remove("skeleton") restore() expect_true(renv_package_installed("skeleton")) expect_true(renv_package_version("skeleton") == "1.0.0") }) test_that("restore() restores packages with broken symlinks", { skip_on_cran() renv_scope_options(renv.settings.cache.enabled = TRUE) renv_tests_scope("breakfast") init() # check it's installed pkgpath <- renv_package_find("breakfast") expect_true(renv_file_exists(pkgpath)) # break the cache record <- list(Package = "breakfast", Version = "1.0.0") cachepath <- renv_cache_find(record) unlink(cachepath, recursive = TRUE) # check that it's broken expect_true(renv_file_broken(pkgpath)) # try to restore restore() # check that we're happy again expect_false(renv_file_broken(pkgpath)) expect_true(renv_file_exists(pkgpath)) expect_true(renv_package_installed("breakfast")) }) test_that("restore() also installs packages with broken symlinks", { skip_on_cran() skip_on_os("windows") renv_scope_options(renv.settings.cache.enabled = TRUE) project <- renv_tests_scope("breakfast") init() pkgpaths <- list.files( path = renv_paths_library(project = project), full.names = TRUE ) links <- Sys.readlink(pkgpaths) expect_true(all(nzchar(links))) unlink(links, recursive = TRUE) expect_false(renv_package_installed("breakfast")) restore() expect_true(renv_package_installed("breakfast")) }) test_that("we can restore a package installed with a custom repository", { project <- renv_tests_scope(isolated = TRUE) init() url <- unname(getOption("repos")) local({ renv_scope_options(repos = character()) install("bread", repos = c(TEST = url), rebuild = TRUE) }) dcf <- renv_description_read(package = "bread") expect_equal(!!dcf$RemoteRepos, !!url) expect_equal(!!dcf$RemoteReposName, "TEST") writeLines("library(bread)", con = "_deps.R") local({ renv_scope_options(repos = character()) snapshot() }) remove.packages("bread") local({ renv_scope_options(repos = character()) restore(rebuild = TRUE) }) expect_true(renv_package_installed("bread")) }) renv/tests/testthat/test-id.R0000644000176200001440000000024714731330073015726 0ustar liggesusers test_that("we can generate a unique ID", { skip_on_cran() id <- renv_id_generate() expect_true(nchar(id) == 36L) expect_true(grepl("^[a-fA-F0-9-]+$", id)) }) renv/tests/testthat/test-errors.R0000644000176200001440000000034514731330073016645 0ustar liggesusers test_that("tracebacks are captured by catch", { skip_if(getRversion() < "3.3.0") output <- catch(stop("ouch")) traceback <- output$traceback expect_true(is.character(traceback)) expect_true(length(traceback) > 0) }) renv/tests/testthat/local/0000755000176200001440000000000014761167256015337 5ustar liggesusersrenv/tests/testthat/local/skeleton/0000755000176200001440000000000014761167256017163 5ustar liggesusersrenv/tests/testthat/local/skeleton/skeleton_1.0.1.tar.gz0000644000176200001440000000064114731330073022636 0ustar liggesusersO0s_;MiCDP!ykk5#ed8isnDdz98$1MH}(`x!K} ;[Tl ;2o 9؇@ҕ>=T{UHXҐέp\\5d67rlѪ TRB/P_#3~'٬zdr]5L'  ;JbVipCDqhq:>9 5|4}^f{q?Ko8AF8,W F+T zE ;4thg:$x_i2uxp&y{w 2 renv/tests/testthat/local/skeleton/skeleton_1.0.0.tar.gz0000644000176200001440000000064614731330073022642 0ustar liggesusersAo0sx+R1IXU%vd駟]&RU՚Ų{{V>4zbh/nLH~E4bI҄%eh lW6\ew޸%6|wd*_JpJ_PU1֕FK`1)lX Ńm]nd#N`QUU,"ud$!"Ivgd;dsS3gdJ$S ͺ.+O y|X!SNw9ڗgDM>.W`8~?$mo3h~4`}@% !75jvܡiK\Yݪ LyV~u^ZZZZ^8i renv/tests/testthat/test-r.R0000644000176200001440000000333614731330073015575 0ustar liggesusers test_that("we can use R CMD build to build a package", { skip_on_cran() if (renv_platform_windows()) { zip <- Sys.which("zip") if (!nzchar(zip)) skip("test requires 'zip'") } testdir <- renv_scope_tempfile("renv-r-tests-") ensure_directory(testdir) # R CMD install creates file in working directory renv_scope_wd(testdir) package <- "sample.package" pkgdir <- file.path(testdir, package) ensure_directory(pkgdir) data <- list(Package = package, Type = "Package", Version = "0.1.0") renv_dcf_write(data, file = file.path(pkgdir, "DESCRIPTION")) expect_equal(renv_project_type(pkgdir), "package") tarball <- r_cmd_build(package, pkgdir) ensure_existing_path(tarball) ensure_existing_file(tarball) expect_true(file.exists(tarball)) files <- renv_archive_list(tarball) expect_true(all(c("DESCRIPTION", "NAMESPACE", "MD5") %in% basename(files))) # NOTE: R 3.6 seems to check the permissions of the target library, # even when you just want to build a binary? before <- list.files(testdir) args <- c( "CMD", "INSTALL", "-l", renv_shell_path(tempdir()), "--no-multiarch", "--build", package ) output <- r(args, stdout = TRUE, stderr = TRUE) after <- list.files(testdir) binball <- renv_vector_diff(after, c("NULL", before)) expect_true(length(binball) == 1L) expect_equal(renv_package_type(binball), "binary") }) test_that("we can supply custom options to R CMD INSTALL", { skip_on_cran() renv_tests_scope() # work in new renv context (don't re-use cache) renv_scope_envvars(RENV_PATHS_ROOT = renv_scope_tempfile()) # make install 'fail' with bad option renv_scope_options(install.opts = list(oatmeal = "--version")) expect_error(install("oatmeal")) }) renv/tests/testthat/test-miscellaneous.R0000644000176200001440000000072614731330073020177 0ustar liggesusers # base::sprintf will fail if you pass a format string that's too # long even if no arguments are used, so renv provides its own # override that handles this case and then delegates to base::sprintf # if it's otherwise safe to do so test_that("long lines and be printed", { long <- paste(sample(letters, 8192 * 2, TRUE), collapse = "") expect_equal(sprintf(long), long) }) test_that("unmatched dots cause an error", { expect_error(snapshot(packaeg = getwd())) }) renv/tests/testthat/test-platform.R0000644000176200001440000000131514731330073017153 0ustar liggesusers test_that("/etc/os-release files can be parsed", { contents <- ' # This is a comment. NAME="Ubuntu" VERSION="18.04.5 LTS (Bionic Beaver)" ID=ubuntu ID_LIKE=debian PRETTY_NAME="Ubuntu 18.04.5 LTS" VERSION_ID="18.04" HOME_URL="https://www.ubuntu.com/" SUPPORT_URL="https://help.ubuntu.com/" BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/" PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy" VERSION_CODENAME=bionic UBUNTU_CODENAME=bionic ' file <- renv_scope_tempfile("os-release-") writeLines(contents, con = file) sysinfo <- list(sysname = "linux") prefix <- renv_bootstrap_platform_os_via_os_release(file, sysinfo) expect_equal(prefix, "linux-ubuntu-bionic") }) renv/tests/testthat/test-history.R0000644000176200001440000000142214731330073017027 0ustar liggesusers test_that("history() on an example git repository works", { skip_on_cran() skip_if(!nzchar(Sys.which("git")), "git is not installed") renv_tests_scope() snapshot() # try initializing a simple git repository renv_system_exec("git", c("init", "--quiet"), action = "git init") renv_system_exec("git", c("config", "user.name", shQuote("User Name")), action = "git config") renv_system_exec("git", c("config", "user.email", shQuote("user@example.com")), action = "git config") renv_system_exec("git", c("add", "-A"), action = "git add files") renv_system_exec("git", c("commit", "-m", shQuote("initial commit"))) # retrieve history hist <- history() # check that it worked expect_true(nrow(hist) == 1L) expect_equal(hist$subject, "initial commit") }) renv/tests/testthat/helper-scope.R0000644000176200001440000000503414731330073016742 0ustar liggesusers renv_tests_scope <- function(packages = character(), project = NULL, isolated = FALSE, scope = parent.frame()) { # use private repositories in this scope renv_tests_scope_repos(scope = scope) # use sandbox in this scope, to guard against packages like 'bread' # being installed into the system library renv_scope_sandbox(scope = scope) # most tests will call init() which changes `R_LIBS_USER`; # this ensures we reset to the original value when the test is done renv_scope_envvars(R_LIBS_USER = NULL, scope = scope) # also ensure we 'exit' the current project after completion renv_scope_envvars(RENV_PROJECT = "", scope = scope) # if we're trying to be isolated from other test infrastructure if (isolated) { root <- tempfile("renv-root-") ensure_directory(root) renv_scope_envvars(RENV_PATHS_ROOT = root, scope = scope) } # move to own test directory project <- project %||% renv_scope_tempfile("renv-test-", scope = scope) ensure_directory(project) renv_scope_wd(project, scope = scope) # scope project here so that it's unset after load, just in case defer(renv_project_clear(), scope = scope) # create empty renv directory dir.create("renv") # create file with dependencies code <- sprintf("library(%s)", packages) writeLines(code, "dependencies.R") # use temporary library library <- renv_scope_tempfile("renv-library-", scope = scope) ensure_directory(library) renv_scope_libpaths(library, scope = scope) # return path to project directory invisible(renv_path_normalize(project)) } renv_tests_scope_repos <- function(scope = parent.frame()) { # get path to on-disk repository repopath <- renv_tests_repopath() # update our repos option fmt <- if (renv_platform_windows()) "file:///%s" else "file://%s" repos <- c(CRAN = sprintf(fmt, repopath)) renv_scope_options( pkgType = "source", repos = repos, scope = scope ) } renv_tests_scope_system_cache <- function(scope = parent.frame()) { skip_on_cran() renv_scope_options(repos = c(CRAN = "https://cloud.r-project.org"), scope = scope) renv_scope_envvars(RENV_PATHS_ROOT = renv_paths_root_default_impl(), scope = scope) } renv_scope_local <- function() { path <- renv_tests_path("local") renv_scope_envvars(RENV_PATHS_LOCAL = path, scope = parent.frame()) } renv_tests_dependencies <- function(packages) { code <- sprintf("library(%s)", packages) cat(code, file = "dependencies.R", sep = "\n", append = TRUE) } renv/tests/testthat/test-repos.R0000644000176200001440000000417414740070207016465 0ustar liggesusers test_that("we can query our local repository during tests", { expected <- list.files("packages") drop <- c("today", if (.Platform$OS.type == "unix") "windowsonly" else "unixonly") expected <- setdiff(expected, drop) renv_tests_scope() ap <- available_packages(type = "source")[[1]] expect_setequal(ap$Package, expected) }) test_that("repository names are not lost in the lockfile", { url <- "https://cloud.r-project.org" renv_scope_options(repos = c(Example = url)) lockfile <- renv_lockfile_init(project = getwd()) expect_equal(lockfile$R$Repositories, list(Example = url)) }) test_that("trailing slashes are removed from repositories on load", { renv_scope_options(repos = NULL) renv_load_r_repos(list(CRAN = "https://cloud.r-project.org/")) expect_equal(getOption("repos"), c(CRAN = "https://cloud.r-project.org")) }) test_that("renv emits an error if repos option is malformed", { renv_tests_scope() # disallow malformed repos expect_error(snapshot(repos = 1)) # disallow unnamed repositories expect_error(snapshot(repos = c("a", "b"))) # allow zero-length repos lockfile <- snapshot(repos = NULL, lockfile = NULL) expect_true(empty(lockfile$R$Repositories)) # explicitly allow single unnamed repository lockfile <- snapshot(repos = "https://cloud.r-project.org", lockfile = NULL) expect_equal(lockfile$R$Repositories, list(CRAN = "https://cloud.r-project.org")) # allow custom named repositories repos <- list( CRAN = "https://cran.rstudio.com", RSPM = "https://packagemanager.rstudio.com/all/latest" ) lockfile <- snapshot(repos = repos, lockfile = NULL) expect_equal(lockfile$R$Repositories, repos) }) test_that("repository names are always set", { skip_on_cran() renv_tests_scope() repos <- c( CRAN = "https://cran.r-project.org", PPM = "https://packagemanager.posit.co", "https://renv.github.io/drat" ) expected <- c( CRAN = "https://cran.r-project.org", PPM = "https://packagemanager.posit.co", "https://renv.github.io/drat" = "https://renv.github.io/drat" ) actual <- renv_repos_normalize(repos) expect_equal(actual, expected) }) renv/tests/testthat/test-actions.R0000644000176200001440000000314314731330073016770 0ustar liggesusers test_that("we can query actions for a sample project", { project <- renv_tests_scope("breakfast") renv_scope_options(renv.config.auto.snapshot = FALSE) init(bare = TRUE) install("breakfast") # project depends on 'breakfast' and 'breakfast' is installed # ergo, need to snapshot 4 packages acts <- actions("snapshot", project = getwd()) expect_equal(nrow(acts), 4L) expect_setequal(acts$Package, c("bread", "breakfast", "oatmeal", "toast")) expect_true(all(acts$Action == "install")) # note: empty for non-clean restore as we don't remove packages acts <- actions("restore", library = renv_paths_library(), project = getwd()) expect_equal(nrow(acts), 0L) # now non-empty acts <- actions("restore", library = renv_paths_library(), project = getwd(), clean = TRUE) expect_equal(nrow(acts), 4L) expect_setequal(acts$Package, c("bread", "breakfast", "oatmeal", "toast")) expect_true(all(acts$Action == "remove")) }) test_that("we can query actions when no lockfile has yet been generated", { renv_tests_scope("bread") init(bare = TRUE) install("bread") actions <- actions("snapshot") expect_equal(nrow(actions), 1L) }) test_that("bare usages of actions work as expected", { # set up project with 3 packages that need to be snapshotted renv_tests_scope("bread") renv_scope_options(renv.config.auto.snapshot = FALSE) init() install("breakfast") writeLines("library(breakfast)", con = "deps.R") acts <- actions("snapshot") expect_equal(nrow(acts), 3L) expect_setequal(acts$Package, c("breakfast", "oatmeal", "toast")) expect_true(all(acts$Action == "install")) }) renv/tests/testthat/test-profiles.R0000644000176200001440000000307114731330073017153 0ustar liggesusers test_that("library paths set in a user profile are overridden after load", { skip_on_cran() skip_on_os("windows") renv_tests_scope() renv_scope_envvars(R_LIBS = NULL) init() renv_imbue_impl(project = getwd(), force = TRUE) profile <- c( ".libPaths('.')", "source('renv/activate.R')" ) writeLines(profile, con = ".Rprofile") # ensure profile is executed renv_scope_envvars(R_PROFILE_USER = NULL) # invoke R args <- c("-s", "-e", shQuote("writeLines(.libPaths(), 'libpaths.txt')")) output <- system2(R(), args, stdout = FALSE, stderr = FALSE) actual <- readLines("libpaths.txt") expected <- renv_libpaths_all() expect_equal(actual[[1]], expected[[1]]) }) test_that(".First is executed; library paths are restored after", { skip_on_cran() skip_on_os("windows") renv_tests_scope() renv_scope_envvars(R_LIBS = NULL) init() renv_imbue_impl(project = getwd(), force = TRUE) # add a .First to the profile profile <- quote({ .First <- function() { writeLines("Hello from .First") .libPaths(".") } source("renv/activate.R") }) # ensure profile is executed renv_scope_envvars(R_PROFILE_USER = NULL) writeLines(deparse(profile), con = ".Rprofile") # invoke R script <- renv_test_code({ print(.libPaths()) writeLines(.libPaths(), con = "libpaths.txt") }) args <- c("-f", shQuote(script)) output <- renv_system_exec(R(), args, action = "writing libpaths") actual <- readLines("libpaths.txt") expected <- renv_libpaths_all() expect_equal(actual[[1L]], expected[[1L]]) }) renv/tests/testthat/test-cellar.R0000644000176200001440000000133714731330073016575 0ustar liggesusers test_that("renv can find packages located in the cellar", { skip_on_cran() renv_tests_scope() # copy some packages into the cellar cellar <- renv_paths_cellar() ensure_directory(cellar) repopath <- renv_tests_repopath() packages <- list.files( path = file.path(repopath, "src/contrib"), pattern = ".tar.gz$", full.names = TRUE ) file.copy(packages, to = cellar) # turn off repositories renv_scope_options(repos = character()) # check for latest available package latest <- renv_available_packages_latest("bread", type = "source") expect_equal( latest[c("Package", "Version")], list(Package = "bread", Version = "1.0.0") ) # check that we can install it install("bread") }) renv/tests/testthat/test-envvar.R0000644000176200001440000000241714737271144016645 0ustar liggesusers test_that("can add before or after", { path_string <- function(...) { paste(c(...), collapse = .Platform$path.sep) } renv_scope_envvars(TESTPATH = "a") renv_envvar_path_add("TESTPATH", "b", prepend = TRUE) expect_equal(Sys.getenv("TESTPATH"), path_string(c("b", "a"))) renv_scope_envvars(TESTPATH = "a") renv_envvar_path_add("TESTPATH", "b", prepend = FALSE) expect_equal(Sys.getenv("TESTPATH"), path_string(c("a", "b"))) }) test_that("renv_envvar_path_modify doesn't duplicate paths", { path_string <- function(...) { paste(c(...), collapse = .Platform$path.sep) } renv_scope_envvars(TESTPATH = path_string("a", "b", "c")) renv_envvar_path_add("TESTPATH", "a") expect_equal(Sys.getenv("TESTPATH"), path_string("a", "b", "c")) renv_envvar_path_add("TESTPATH", "b") expect_equal(Sys.getenv("TESTPATH"), path_string("b", "a", "c")) }) test_that("renv_envvar_path_modify works if var isn't set", { renv_scope_envvars(TESTPATH = NULL) renv_envvar_path_add("TESTPATH", "a") expect_equal(Sys.getenv("TESTPATH"), "a") }) test_that("renv_envvar_exists behaves as expected", { renv_scope_envvars(RENV_TEST = NULL) expect_false(renv_envvar_exists("RENV_TEST")) renv_scope_envvars(RENV_TEST = 1) expect_true(renv_envvar_exists("RENV_TEST")) }) renv/tests/testthat/test-snapshot.R0000644000176200001440000004066114761163114017200 0ustar liggesusers test_that("snapshot is idempotent", { renv_tests_scope("oatmeal") init(bare = TRUE) install("oatmeal") snapshot() before <- renv_lockfile_read("renv.lock") snapshot() after <- renv_lockfile_read("renv.lock") expect_equal(before, after) }) test_that("snapshot failures are reported", { renv_tests_scope("oatmeal") init() descpath <- system.file("DESCRIPTION", package = "oatmeal") unlink(descpath) expect_snapshot(snapshot()) }) test_that("broken symlinks are reported", { skip_on_os("windows") renv_scope_envvars(RENV_PATHS_ROOT = renv_scope_tempfile()) renv_tests_scope("oatmeal") init() oatmeal <- renv_path_normalize(system.file(package = "oatmeal")) unlink(oatmeal, recursive = TRUE) expect_snapshot(snapshot()) }) test_that("multiple libraries can be used when snapshotting", { renv_scope_envvars(RENV_PATHS_ROOT = renv_scope_tempfile()) renv_tests_scope() init() lib1 <- renv_scope_tempfile("renv-lib1-") lib2 <- renv_scope_tempfile("renv-lib2-") ensure_directory(c(lib1, lib2)) oldlibpaths <- .libPaths() .libPaths(c(lib1, lib2)) install("bread", library = lib1) breadloc <- find.package("bread") expect_true(renv_file_same(dirname(breadloc), lib1)) install("toast", library = lib2) toastloc <- find.package("toast") expect_true(renv_file_same(dirname(toastloc), lib2)) libs <- c(lib1, lib2) lockfile <- snapshot(lockfile = NULL, library = libs, type = "all") records <- renv_lockfile_records(lockfile) expect_length(records, 2L) expect_setequal(names(records), c("bread", "toast")) .libPaths(oldlibpaths) }) test_that("implicit snapshots only include packages currently used", { renv_tests_scope("oatmeal") init() # install toast, but don't declare that we use it install("toast") lockfile <- snapshot(type = "implicit", lockfile = NULL) records <- renv_lockfile_records(lockfile) expect_length(records, 1L) expect_setequal(names(records), "oatmeal") # use toast writeLines("library(toast)", con = "toast.R") lockfile <- snapshot(type = "packrat", lockfile = NULL) records <- renv_lockfile_records(lockfile) expect_length(records, 3L) expect_setequal(names(records), c("oatmeal", "bread", "toast")) }) test_that("explicit snapshots only capture packages in DESCRIPTION", { renv_tests_scope("breakfast") init() desc <- list(Type = "Project", Depends = "toast") write.dcf(desc, file = "DESCRIPTION") lockfile <- snapshot(type = "explicit", lockfile = NULL) records <- renv_lockfile_records(lockfile) expect_true(length(records) == 2L) expect_true(!is.null(records[["bread"]])) expect_true(!is.null(records[["toast"]])) }) test_that("a custom snapshot filter can be used", { skip_on_cran() renv_tests_scope("breakfast") settings$snapshot.type("custom") filter <- function(project) c("bread", "toast") renv_scope_options(renv.snapshot.filter = filter) init() lockfile <- renv_lockfile_load(project = getwd()) expect_setequal(names(renv_lockfile_records(lockfile)), c("bread", "toast")) }) test_that("snapshotted packages from CRAN include the Repository field", { renv_tests_scope("bread") init() lockfile <- renv_lockfile_read("renv.lock") records <- renv_lockfile_records(lockfile) expect_true(records$bread$Repository == "CRAN") }) test_that("snapshot failures due to bad library / packages are reported", { project <- renv_tests_scope("oatmeal") init() isolate() writeLines("invalid", con = system.file("DESCRIPTION", package = "oatmeal")) expect_error(snapshot()) }) test_that("snapshot ignores own package in package development scenarios", { renv_tests_scope() ensure_directory("bread") renv_scope_wd("bread") writeLines(c("Type: Package", "Package: bread"), con = "DESCRIPTION") ensure_directory("R") writeLines("function() { library(bread) }", con = "R/deps.R") lockfile <- snapshot(lockfile = NULL) records <- renv_lockfile_records(lockfile) expect_true(is.null(records[["bread"]])) }) test_that("snapshot warns about unsatisfied dependencies", { renv_tests_scope("toast") init(settings = list(use.cache = FALSE)) descpath <- system.file("DESCRIPTION", package = "toast") toast <- renv_description_read(descpath) toast$Depends <- "bread (> 1.0.0)" renv_dcf_write(toast, file = descpath) expect_snapshot(snapshot(), error = TRUE) }) test_that("snapshot records packages discovered in cellar", { renv_tests_scope("skeleton") renv_scope_envvars( RENV_PATHS_CACHE = renv_scope_tempfile(), RENV_PATHS_LOCAL = renv_tests_path("local") ) init(bare = TRUE) record <- list(Package = "skeleton", Version = "1.0.1") records <- install(list(record)) # validate the record in the lockfile lockfile <- snapshot(lockfile = NULL) records <- renv_lockfile_records(lockfile) skeleton <- records[["skeleton"]] expect_equal(skeleton$Package, "skeleton") expect_equal(skeleton$Version, "1.0.1") expect_equal(skeleton$Source, "Cellar") }) test_that("snapshot prefers RemoteType to biocViews", { desc <- list( Package = "test", Version = "1.0", RemoteType = "github", biocViews = "Biology" ) descfile <- renv_scope_tempfile() renv_dcf_write(desc, file = descfile) record <- renv_snapshot_description(descfile) expect_identical(record$Source, "GitHub") }) test_that("parse errors cause snapshot to abort", { renv_tests_scope() # write invalid code to an R file writeLines("parse error", con = "parse-error.R") # init should succeed even with parse errors init(bare = TRUE) # snapshots should fail when configured to do so renv_scope_options(renv.config.dependency.errors = "fatal") expect_error(snapshot()) }) test_that("records for packages available on other OSes are preserved", { skip_on_os("windows") renv_tests_scope("unixonly") init() # fake a windows-only record lockfile <- renv_lockfile_read("renv.lock") lockfile$Packages$windowsonly <- lockfile$Packages$unixonly lockfile$Packages$windowsonly$Package <- "windowsonly" lockfile$Packages$windowsonly$Hash <- NULL lockfile$Packages$windowsonly$OS_type <- "windows" renv_lockfile_write(lockfile, "renv.lock") # call snapshot to update lockfile snapshot() # ensure that 'windowsonly' is still preserved lockfile <- renv_lockfile_read("renv.lock") expect_true(!is.null(lockfile$Packages$windowsonly)) }) test_that(".renvignore works during snapshot without an explicit root", { renv_tests_scope() # install bread install("bread") # create sub-directory that should be ignored dir.create("ignored") writeLines("library(bread)", con = "ignored/script.R") lockfile <- snapshot(project = ".", lockfile = NULL) expect_false(is.null(lockfile$Packages$bread)) writeLines("*", con = "ignored/.renvignore") lockfile <- snapshot(project = ".", lockfile = NULL) expect_true(is.null(lockfile$Packages$bread)) }) test_that("snapshot(packages = ...) captures package dependencies", { renv_tests_scope("breakfast") # init to install required packages init() # remove old lockfile unlink("renv.lock") # create lockfile snapshot(packages = "breakfast") # check for expected records lockfile <- renv_lockfile_load(project = getwd()) records <- renv_lockfile_records(lockfile) expect_true(!is.null(records$breakfast)) expect_true(!is.null(records$bread)) expect_true(!is.null(records$toast)) expect_true(!is.null(records$oatmeal)) }) test_that("snapshot() accepts relative library paths", { renv_tests_scope("breakfast") # initialize project init() # remove lockfile unlink("renv.lock") # form relative path to library library <- substring(.libPaths()[1], nchar(getwd()) + 2) # try to snapshot with relative library path snapshot(library = library) # test that snapshot succeeded expect_true(file.exists("renv.lock")) }) test_that("snapshot(update = TRUE) preserves old records", { skip_on_cran() skip_if_no_github_auth() renv_tests_scope("breakfast") init() # remove breakfast, then try to snapshot again old <- renv_lockfile_read("renv.lock") remove("breakfast") snapshot(update = TRUE) new <- renv_lockfile_read("renv.lock") expect_identical(names(old$Packages), names(new$Packages)) # try installing a package old <- renv_lockfile_read("renv.lock") writeLines("library(halloween)", con = "halloween.R") install("halloween") snapshot(update = TRUE) new <- renv_lockfile_read("renv.lock") # check that we have our old record names expect_true(all(old$Packages %in% new$Packages)) # now try removing 'breakfast' snapshot(update = FALSE) new <- renv_lockfile_read("renv.lock") expect_false("breakfast" %in% names(new$Packages)) }) test_that("renv reports missing packages in explicit snapshots", { renv_tests_scope() init() writeLines("Depends: breakfast", con = "DESCRIPTION") expect_snapshot(snapshot(type = "explicit")) }) test_that("a project using explicit snapshots is marked in sync appropriately", { skip_on_cran() renv_tests_scope() renv_scope_options(renv.config.snapshot.type = "explicit") init() writeLines("Depends: breakfast", con = "DESCRIPTION") expect_false(status()$synchronized) install("breakfast") expect_false(status()$synchronized) snapshot() expect_true(status()$synchronized) }) test_that("snapshot() warns when required package is not installed", { renv_tests_scope("breakfast") init() remove("breakfast") expect_snapshot(snapshot()) install("breakfast") remove("toast") expect_snapshot(snapshot(), error = TRUE) }) test_that("packages installed from CRAN using pak are handled", { skip_on_cran() skip_if_not_installed("pak") renv_tests_scope() library <- renv_paths_library() ensure_directory(library) pak <- renv_namespace_load("pak") quietly(pak$pkg_install("toast")) record <- renv_snapshot_description(package = "toast") expected <- c("Package", "Version", "Source", "Repository") expect_contains(names(record), expected) expect_identical(record$Source, "Repository") expect_identical(record$Repository, "CRAN") }) test_that("packages installed from Bioconductor using pak are handled", { skip_on_cran() skip_if_not_installed("pak") skip_if(devel()) renv_tests_scope() library <- renv_paths_library() ensure_directory(library) pak <- renv_namespace_load("pak") suppressMessages(pak$pkg_install("bioc::Biobase")) record <- renv_snapshot_description(package = "Biobase") expect_identical(record$Source, "Bioconductor") }) test_that("snapshot always reports on R version changes", { renv_scope_options(renv.verbose = TRUE) R4.1 <- list(R = list(Version = 4.1)) R4.2 <- list(R = list(Version = 4.2)) expect_snapshot({ renv_snapshot_report_actions(list(), R4.1, R4.2) }) }) test_that("user can choose to install missing packages", { # use a temporary cache to guarantee packages are fully installed # regardless of order other tests are run in renv_scope_envvars(RENV_PATHS_CACHE = renv_scope_tempfile("renv-tempcache-")) renv_tests_scope("egg") renv_scope_options(renv.menu.choice = 2) expect_snapshot(snapshot()) }) test_that("exclude handles uninstalled packages", { project <- renv_tests_scope("bread") init() snapshot(exclude = "bread") lockfile <- renv_lockfile_load(project) expect_null(lockfile$Packages$bread) }) test_that("snapshot doesn't include development dependencies", { renv_tests_scope() writeLines(c("Imports: egg", "Suggests: bread"), "DESCRIPTION") inst <- install() expect_named(inst, c("bread", "egg"), ignore.order = TRUE) snap <- snapshot() expect_named(snap$Packages, "egg") }) test_that("automatic snapshot works as expected", { renv_scope_binding(the, "auto_snapshot_forced", TRUE) defer(the$library_info <- NULL) project <- renv_tests_scope("oatmeal") init() expect_silent(renv_snapshot_task()) # bread isn't used so snapshot shouldn't change install("bread") expect_silent(renv_snapshot_task()) writeLines("library(egg)", "dependencies.R") install("egg") expect_snapshot(renv_snapshot_task()) }) # test_that("we can infer github remotes from packages installed from sources", { # skip_on_cran() # # desc <- heredoc(" # Package: renv # Version: 0.1.0-9000 # BugReports: https://github.com/rstudio/renv/issues # ") # # descfile <- renv_scope_tempfile("description-") # writeLines(desc, con = descfile) # # remote <- local({ # renv_scope_options(renv.verbose = FALSE) # renv_snapshot_description(path = descfile) # }) # # expect_equal(remote$RemoteType, "github") # # expect_snapshot(. <- renv_snapshot_description(path = descfile)) # # }) test_that("we report if dependency discover during snapshot() is slow", { renv_tests_scope() init() renv_scope_options(renv.dependencies.elapsed_time_threshold = -1) expect_snapshot(. <- snapshot()) }) test_that("failures in automatic snapshots disable automatic snapshots", { renv_scope_binding(the, "auto_snapshot_forced", TRUE) defer(the$library_info <- NULL) project <- renv_tests_scope("bread") init() count <- 0L renv_scope_trace(renv:::renv_snapshot_auto, function() { count <<- count + 1L stop("simulated failure in snapshot task") }) the$library_info <- list() expect_false(the$auto_snapshot_failed) expect_snapshot(renv_snapshot_task()) expect_true(the$auto_snapshot_failed) the$library_info <- list() expect_silent(renv_snapshot_task()) expect_equal(count, 1L) }) # https://github.com/rstudio/renv/issues/1607 test_that("snapshot() reports missing packages even if renv.verbose is FALSE", { project <- renv_tests_scope() init() renv_scope_options(renv.verbose = FALSE) writeLines("library(bread)", con = "deps.R") expect_snapshot(. <- snapshot(force = TRUE)) }) test_that("packages installed from r-universe preserve remote metadata", { text <- heredoc(" Package: skeleton Type: Package Version: 1.1.0 Remotes: kevinushey/skeleton Repository: https://kevinushey.r-universe.dev RemoteUrl: https://github.com/kevinushey/skeleton RemoteSha: e4aafb92b86ba7eba3b7036d9d96fdfb6c32761a ") path <- renv_scope_tempfile() writeLines(text, con = path) record <- renv_snapshot_description(path = path) expect_identical(record[["RemoteSha"]], "e4aafb92b86ba7eba3b7036d9d96fdfb6c32761a") }) test_that("standard remotes preserve RemoteSha if it's a hash", { text <- heredoc(" Package: skeleton Type: Package Version: 1.1.0 Remotes: kevinushey/skeleton Repository: https://kevinushey.r-universe.dev RemoteType: standard RemoteUrl: https://github.com/kevinushey/skeleton RemoteSha: e4aafb92b86ba7eba3b7036d9d96fdfb6c32761a ") path <- renv_scope_tempfile() writeLines(text, con = path) record <- renv_snapshot_description(path = path) expect_identical(record[["RemoteSha"]], "e4aafb92b86ba7eba3b7036d9d96fdfb6c32761a") }) test_that("standard remotes drop RemoteSha if it's a version", { text <- heredoc(" Package: skeleton Type: Package Version: 1.1.0 Remotes: kevinushey/skeleton Repository: https://kevinushey.r-universe.dev RemoteType: standard RemoteSha: 1.1.0 ") path <- renv_scope_tempfile() writeLines(text, con = path) record <- renv_snapshot_description(path = path) expect_null(record[["RemoteSha"]]) }) test_that("a package's hash can be re-generated from lockfile", { project <- renv_tests_scope("breakfast") init() lockfile <- snapshot(lockfile = NULL) records <- renv_lockfile_records(lockfile) enumerate(records, function(package, record) { path <- system.file("DESCRIPTION", package = package) actual <- renv_hash_description(path) expected <- renv_hash_record(record) expect_equal(actual, expected) }) }) test_that("lockfiles are stable (v1)", { renv_scope_options(renv.lockfile.version = 1L) project <- renv_tests_scope("breakfast") init() expect_snapshot(. <- writeLines(readLines("renv.lock"))) }) test_that("lockfiles are stable (v2)", { renv_scope_options(renv.lockfile.version = 2L) project <- renv_tests_scope("breakfast") init() expect_snapshot(. <- writeLines(readLines("renv.lock"))) }) # # https://github.com/rstudio/renv/issues/2073 test_that("empty .ipynb files are handled gracefully", { skip_on_cran() project <- renv_tests_scope("bread") init() writeLines("", con = "example.ipynb") snapshot() }) test_that("invalid lockfiles don't prevent calls to snapshot", { skip_on_cran() project <- renv_tests_scope("bread") init() writeLines("whoops!", con = "renv.lock") suppressWarnings(snapshot()) expect_true(TRUE) }) renv/tests/testthat/test-ppm.R0000644000176200001440000002562014731330073016130 0ustar liggesusers test_that("we can transform binary URLs into source URLs", { skip_on_cran() skip_on_os("windows") url <- "https://packagemanager.rstudio.com/cran/__linux__/centos7/latest" actual <- renv_ppm_normalize(url) expected <- "https://packagemanager.rstudio.com/cran/latest" expect_identical(actual, expected) }) test_that("repository URLs are properly transformed for different platforms", { skip_on_cran() skip_on_os("windows") renv_scope_envvars(RENV_RSPM_OS = "__linux__", RENV_RSPM_PLATFORM = "bionic") repos <- c(RSPM = "https://cluster.rstudiopm.com/cran/latest") expected <- c(RSPM = "https://cluster.rstudiopm.com/cran/__linux__/bionic/latest") actual <- renv_ppm_transform(repos) expect_identical(expected, actual) }) test_that("a binary-specific URL is transformed before writing a lockfile", { skip_on_cran() renv_tests_scope() renv_scope_options(repos = c(RSPM = "https://cluster.rstudiopm.com/cran/__os__/platform/latest")) lockfile <- snapshot(lockfile = NULL) actual <- convert(lockfile$R$Repositories, "character") expected <- c(RSPM = "https://cluster.rstudiopm.com/cran/latest") expect_identical(actual, expected) }) test_that("RSPM bits are preserved when writing lockfile", { skip_on_cran() renv_tests_scope() renv_scope_options(repos = c(RSPM = "https://cluster.rstudiopm.com/curated/__linux__/bionic/12")) lockfile <- snapshot(lockfile = NULL) actual <- convert(lockfile$R$Repositories, "character") expected <- c(RSPM = "https://cluster.rstudiopm.com/curated/12") expect_identical(actual, expected) }) test_that("renv correctly detects RHEL < 9 as CentOS for RSPM", { skip_on_cran() skip_on_os("windows") release <- heredoc(' NAME="Red Hat Enterprise Linux Server" VERSION="7.9 (Maipo)" ID="rhel" ID_LIKE="fedora" VARIANT="Server" VARIANT_ID="server" VERSION_ID="7.9" PRETTY_NAME="Red Hat Enterprise Linux Server 7.9 (Maipo)" ANSI_COLOR="0;31" CPE_NAME="cpe:/o:redhat:enterprise_linux:7.9:GA:server" HOME_URL="https://www.redhat.com/" BUG_REPORT_URL="https://bugzilla.redhat.com/" ') file <- renv_scope_tempfile() writeLines(release, con = file) platform <- renv_ppm_platform_impl(file = file) expect_equal(platform, "centos7") }) test_that("renv correctly detects RHEL9 for PPM", { skip_on_cran() skip_on_os("windows") release <- heredoc(' NAME="Red Hat Enterprise Linux" VERSION="9.0 (Plow)" ID="rhel" ID_LIKE="fedora" VERSION_ID="9.0" PLATFORM_ID="platform:el9" PRETTY_NAME="Red Hat Enterprise Linux 9.0 (Plow)" ANSI_COLOR="0;31" LOGO="fedora-logo-icon" CPE_NAME="cpe:/o:redhat:enterprise_linux:9::baseos" HOME_URL="https://www.redhat.com/" DOCUMENTATION_URL="https://access.redhat.com/documentation/red_hat_enterprise_linux/9/" BUG_REPORT_URL="https://bugzilla.redhat.com/" REDHAT_BUGZILLA_PRODUCT="Red Hat Enterprise Linux 9" REDHAT_BUGZILLA_PRODUCT_VERSION=9.0 REDHAT_SUPPORT_PRODUCT="Red Hat Enterprise Linux" REDHAT_SUPPORT_PRODUCT_VERSION="9.0" ') file <- renv_scope_tempfile() writeLines(release, con = file) platform <- renv_ppm_platform_impl(file = file) expect_equal(platform, "rhel9") }) test_that("renv correctly detects Rocky Linux 8 as centos8 for PPM", { skip_on_cran() skip_on_os("windows") release <- heredoc(' NAME="Rocky Linux" VERSION="8.8 (Green Obsidian)" ID="rocky" ID_LIKE="rhel centos fedora" VERSION_ID="8.8" PLATFORM_ID="platform:el8" PRETTY_NAME="Rocky Linux 8.8 (Green Obsidian)" ANSI_COLOR="0;32" LOGO="fedora-logo-icon" CPE_NAME="cpe:/o:rocky:rocky:8:GA" HOME_URL="https://rockylinux.org/" BUG_REPORT_URL="https://bugs.rockylinux.org/" SUPPORT_END="2029-05-31" ROCKY_SUPPORT_PRODUCT="Rocky-Linux-8" ROCKY_SUPPORT_PRODUCT_VERSION="8.8" REDHAT_SUPPORT_PRODUCT="Rocky Linux" REDHAT_SUPPORT_PRODUCT_VERSION="8.8" ') file <- renv_scope_tempfile() writeLines(release, con = file) platform <- renv_ppm_platform_impl(file = file) expect_equal(platform, "centos8") }) test_that("renv correctly detects Rocky Linux 9 as rhel9 for PPM", { skip_on_cran() skip_on_os("windows") release <- heredoc(' NAME="Rocky Linux" VERSION="9.2 (Blue Onyx)" ID="rocky" ID_LIKE="rhel centos fedora" VERSION_ID="9.2" PLATFORM_ID="platform:el9" PRETTY_NAME="Rocky Linux 9.2 (Blue Onyx)" ANSI_COLOR="0;32" LOGO="fedora-logo-icon" CPE_NAME="cpe:/o:rocky:rocky:9::baseos" HOME_URL="https://rockylinux.org/" BUG_REPORT_URL="https://bugs.rockylinux.org/" SUPPORT_END="2032-05-31" ROCKY_SUPPORT_PRODUCT="Rocky-Linux-9" ROCKY_SUPPORT_PRODUCT_VERSION="9.2" REDHAT_SUPPORT_PRODUCT="Rocky Linux" REDHAT_SUPPORT_PRODUCT_VERSION="9.2" ') file <- renv_scope_tempfile() writeLines(release, con = file) platform <- renv_ppm_platform_impl(file = file) expect_equal(platform, "rhel9") }) test_that("renv correctly detects AlmaLinux 9 as rhel9 for PPM", { skip_on_cran() skip_on_os("windows") release <- heredoc(' NAME="AlmaLinux" VERSION="9.2 (Turquoise Kodkod)" ID="almalinux" ID_LIKE="rhel centos fedora" VERSION_ID="9.2" PLATFORM_ID="platform:el9" PRETTY_NAME="AlmaLinux 9.2 (Turquoise Kodkod)" ANSI_COLOR="0;34" LOGO="fedora-logo-icon" CPE_NAME="cpe:/o:almalinux:almalinux:9::baseos" HOME_URL="https://almalinux.org/" DOCUMENTATION_URL="https://wiki.almalinux.org/" BUG_REPORT_URL="https://bugs.almalinux.org/" ALMALINUX_MANTISBT_PROJECT="AlmaLinux-9" ALMALINUX_MANTISBT_PROJECT_VERSION="9.2" REDHAT_SUPPORT_PRODUCT="AlmaLinux" REDHAT_SUPPORT_PRODUCT_VERSION="9.2" ') file <- renv_scope_tempfile() writeLines(release, con = file) platform <- renv_ppm_platform_impl(file = file) expect_equal(platform, "rhel9") }) test_that("renv correctly detects OpenSUSE for PPM", { skip_on_cran() skip_on_os("windows") release <- heredoc(' NAME="openSUSE Leap" VERSION="15.4" ID="opensuse-leap" ID_LIKE="suse opensuse" VERSION_ID="15.4" PRETTY_NAME="openSUSE Leap 15.4" ANSI_COLOR="0;32" CPE_NAME="cpe:/o:opensuse:leap:15.4" BUG_REPORT_URL="https://bugs.opensuse.org" HOME_URL="https://www.opensuse.org/" DOCUMENTATION_URL="https://en.opensuse.org/Portal:Leap" LOGO="distributor-logo-Leap" ') file <- renv_scope_tempfile() writeLines(release, con = file) platform <- renv_ppm_platform_impl(file = file) expect_equal(platform, "opensuse154") }) test_that("renv correctly detects SLES as OpenSUSE for PPM", { skip_on_cran() skip_on_os("windows") # `docker run -it registry.suse.com/suse/sle15:15.5` release <- heredoc(' NAME="SLES" VERSION="15-SP5" VERSION_ID="15.5" PRETTY_NAME="SUSE Linux Enterprise Server 15 SP5" ID="sles" ID_LIKE="suse" ANSI_COLOR="0;32" CPE_NAME="cpe:/o:suse:sles:15:sp5" DOCUMENTATION_URL="https://documentation.suse.com/" ') file <- renv_scope_tempfile() writeLines(release, con = file) platform <- renv_ppm_platform_impl(file = file) expect_equal(platform, "opensuse155") }) test_that("renv correctly detects Debian for PPM", { skip_on_cran() skip_on_os("windows") release <- heredoc(' PRETTY_NAME="Debian GNU/Linux 11 (bullseye)" NAME="Debian GNU/Linux" VERSION_ID="11" VERSION="11 (bullseye)" VERSION_CODENAME=bullseye ID=debian HOME_URL="https://www.debian.org/" SUPPORT_URL="https://www.debian.org/support" BUG_REPORT_URL="https://bugs.debian.org/" ') file <- renv_scope_tempfile() writeLines(release, con = file) platform <- renv_ppm_platform_impl(file = file) expect_equal(platform, "bullseye") }) test_that("renv correctly detects Ubuntu for PPM", { skip_on_cran() skip_on_os("windows") release <- heredoc(' NAME="Ubuntu" VERSION="20.04.6 LTS (Focal Fossa)" ID=ubuntu ID_LIKE=debian PRETTY_NAME="Ubuntu 20.04.6 LTS" VERSION_ID="20.04" HOME_URL="https://www.ubuntu.com/" SUPPORT_URL="https://help.ubuntu.com/" BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/" PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy" VERSION_CODENAME=focal UBUNTU_CODENAME=focal ') file <- renv_scope_tempfile() writeLines(release, con = file) platform <- renv_ppm_platform_impl(file = file) expect_equal(platform, "focal") }) test_that("renv correctly detects Amazon Linux 2 as centos7 for PPM", { skip_on_cran() skip_on_os("windows") release <- heredoc(' NAME="Amazon Linux" VERSION="2" ID="amzn" ID_LIKE="centos rhel fedora" VERSION_ID="2" PRETTY_NAME="Amazon Linux 2" ANSI_COLOR="0;33" CPE_NAME="cpe:2.3:o:amazon:amazon_linux:2" HOME_URL="https://amazonlinux.com/" SUPPORT_END="2025-06-30" ') file <- renv_scope_tempfile() writeLines(release, con = file) platform <- renv_ppm_platform_impl(file = file) expect_equal(platform, "centos7") }) test_that("renv detects no supported PPM platform for Amazon Linux 2023", { skip_on_cran() skip_on_os("windows") release <- heredoc(' NAME="Amazon Linux" VERSION="2023" ID="amzn" ID_LIKE="fedora" VERSION_ID="2023" PLATFORM_ID="platform:al2023" PRETTY_NAME="Amazon Linux 2023" ANSI_COLOR="0;33" CPE_NAME="cpe:2.3:o:amazon:amazon_linux:2023" HOME_URL="https://aws.amazon.com/linux/" BUG_REPORT_URL="https://github.com/amazonlinux/amazon-linux-2023" SUPPORT_END="2028-03-01" ') file <- renv_scope_tempfile() writeLines(release, con = file) platform <- renv_ppm_platform_impl(file = file) expect_null(platform) }) test_that("URLs like http://foo/bar aren't queried", { skip_on_cran() skip_on_os("windows") # pretend to be Ubuntu renv_scope_envvars( RENV_PPM_OS = "__linux__", RENV_PPM_PLATFORM = "ubuntu" ) # emit a condition that we can catch on renv_scope_trace(renv:::renv_ppm_status, quote( renv_condition_signal("bail") )) # should exit early status <- expect_no_condition(renv_ppm_transform_impl("http://foo/bar"), class = "bail") expect_equal(status, "http://foo/bar") # should emit a condition now expect_condition(renv_ppm_transform_impl("http://foo/bar/baz"), class = "bail") }) test_that("renv_ppm_transform() uses source URLs when appropriate", { skip_on_cran() skip_on_os("windows") # pretend to be Ubuntu renv_scope_envvars( RENV_PPM_OS = "__linux__", RENV_PPM_PLATFORM = "jammy" ) binurl <- "https://packagemanager.rstudio.com/cran/__linux__/jammy/latest" srcurl <- "https://packagemanager.rstudio.com/cran/latest" # prefer source URLs renv_scope_binding(the, "install_pkg_type", "source") expect_equal(unname(renv_ppm_transform(binurl)), srcurl) expect_equal(unname(renv_ppm_transform(srcurl)), srcurl) # prefer binary URLs renv_scope_binding(the, "install_pkg_type", "binary") expect_equal(unname(renv_ppm_transform(binurl)), binurl) expect_equal(unname(renv_ppm_transform(srcurl)), binurl) }) renv/tests/testthat/test-lock.R0000644000176200001440000001250614731330073016263 0ustar liggesusers test_that("locks can be acquired, released", { renv_scope_options(renv.config.locking.enabled = TRUE) path <- renv_lock_path(renv_scope_tempfile()) renv_lock_acquire(path) expect_true(file.exists(path)) renv_lock_release(path) expect_false(file.exists(path)) }) test_that("scoped locks are released appropriately", { renv_scope_options(renv.config.locking.enabled = TRUE) path <- renv_lock_path(renv_scope_tempfile()) local({ renv_scope_lock(path) expect_true(file.exists(path)) }) expect_false(file.exists(path)) }) test_that("we can recursively acquire locks", { renv_scope_options(renv.config.locking.enabled = TRUE) path <- renv_lock_path(renv_scope_tempfile()) local({ renv_scope_lock(path) expect_true(file.exists(path)) local({ renv_scope_lock(path) expect_true(file.exists(path)) }) expect_true(file.exists(path)) }) expect_false(file.exists(path)) }) test_that("other processes cannot lock our owned locks", { skip_if( is.null(formals(system2)[["timeout"]]), "system2() lacks the timeout argument" ) renv_scope_options(renv.config.locking.enabled = TRUE) path <- renv_lock_path(renv_scope_tempfile()) renv_lock_acquire(path) script <- renv_test_code( print(renv:::renv_lock_acquire(path)), list(path = path) ) args <- c("--vanilla", "-s", "-f", shQuote(script)) output <- suppressWarnings( system2(R(), args, stdout = FALSE, stderr = FALSE, timeout = 1L) ) expect_equal(output, 124L) }) test_that("locks are released on process exit", { renv_scope_options(renv.config.locking.enabled = TRUE) path <- renv_lock_path(renv_scope_tempfile()) code <- expr({ renv_lock_acquire(!!path) stopifnot(file.exists(!!path)) }) args <- c("--vanilla", "-s", "-e", shQuote(stringify(code))) status <- suppressWarnings( system2(R(), args, stdout = FALSE, stderr = FALSE, timeout = 1L) ) expect_equal(status, 1L) expect_false(file.exists(path)) }) test_that("we can refresh locks", { # create a file path <- renv_scope_tempfile("renv-lock-") file.create(path) # get current info old <- file.info(path, extra_cols = FALSE) # wait a bit Sys.sleep(2) # refresh the 'lock' renv_lock_refresh(path) new <- file.info(path, extra_cols = FALSE) # check for updated time expect_gt(new$mtime, old$mtime) }) test_that("old locks are considered 'orphaned'", { renv_scope_options(renv.config.locking.enabled = TRUE) renv_scope_envvars(RENV_WATCHDOG_ENABLED = "FALSE") path <- renv_lock_path(renv_scope_tempfile()) renv_scope_options(renv.lock.timeout = -1L) renv_lock_acquire(path) expect_true(renv_lock_orphaned(path)) expect_true(file.exists(path)) script <- renv_test_code({ options(renv.config.locking.enabled = TRUE) options(renv.lock.timeout = -1L) stopifnot(renv:::renv_lock_acquire(path)) stopifnot(file.exists(path)) }, list(path = path)) output <- renv_system_exec( command = R(), args = c("--vanilla", "-s", "-f", shQuote(script)), action = "checking for orphaned locks", ) expect_false(file.exists(path)) }) test_that("multiple renv processes successfully acquire, release locks", { skip_on_cran() skip_if(getRversion() < "4.0.0") skip_on_os("windows") skip_on_ci() renv_scope_options(renv.config.locking.enabled = TRUE) renv_scope_envvars(RENV_WATCHDOG_ENABLED = "FALSE") # initialize server server <- tryCatch(renv_socket_server(), error = skip) defer(close(server$socket)) # initialize state n <- 100 start <- tempfile("renv-start-") lockfile <- renv_lock_path(tempfile("renv-lock-")) # initialize shared file shared <- renv_scope_tempfile("renv-file-") writeLines("0", con = shared) # generate runner script script <- renv_test_code( code = { renv:::summon() # wait for start file wait_until(file.exists, start) # helper function increment <- function() { renv_lock_acquire(lockfile) stopifnot(file.exists(lockfile)) number <- as.integer(readLines(shared)) writeLines(as.character(number + 1L), con = shared) renv_lock_release(lockfile) number } # update shared file with lock acquired number <- catch(increment()) if (inherits(number, "error")) number <- -1L # notify parent conn <- renv_socket_connect(port = port, open = "wb") defer(close(conn)) serialize(number, connection = conn) # we're done invisible() }, data = list( start = start, lockfile = lockfile, shared = shared, port = server$port ) ) # create start file file.create(start) # create a bunch of processes that try to update the shared file for (i in 1:n) { system2( command = R(), args = c("--vanilla", "-s", "-f", renv_shell_path(script)), wait = FALSE ) } # wait for all the processes to communicate responses <- stack() for (i in 1:n) local({ conn <- renv_socket_accept(server$socket, open = "rb", timeout = 60) defer(close(conn)) responses$push(unserialize(conn)) }) # check that the count is correct contents <- readLines(shared) expect_equal(contents, as.character(n)) # check that each process saw a unique value numbers <- unlist(responses$data()) expect_equal(sort(numbers), 0:(n - 1)) }) renv/tests/testthat/test-purge.R0000644000176200001440000000036314731330073016453 0ustar liggesusers test_that("we can purge packages from the cache", { renv_tests_scope("breakfast") init() expect_true("breakfast" %in% basename(renv_cache_list())) purge("breakfast") expect_false("breakfast" %in% basename(renv_cache_list())) }) renv/tests/testthat/helper-slow.R0000644000176200001440000000051714731330073016616 0ustar liggesusers skip_slow <- function() { skip_on_cran() skip_if_not(is_slow(), "Skipping slow test") } is_slow <- function() { truthy(Sys.getenv("RENV_TESTTHAT_SLOW", unset = "false")) } slow_test_disable <- function() { Sys.unsetenv("RENV_TESTTHAT_SLOW") } slow_test_enable <- function() { Sys.setenv("RENV_TESTTHAT_SLOW" = "true") } renv/tests/testthat/test-socket.R0000644000176200001440000000151714731330073016623 0ustar liggesusers test_that("we can communicate with a large number of child processes", { skip_on_cran() skip_on_ci() n <- 20L server <- tryCatch(renv_socket_server(), error = skip) defer(close(server$socket)) port <- server$port script <- renv_test_code({ renv:::summon() conn <- renv_socket_connect(port, open = "wb") defer(close(conn)) serialize(Sys.getpid(), connection = conn) }, list(port = port)) for (i in seq_len(n)) { system2( command = R(), args = c("--vanilla", "-s", "-f", renv_shell_path(script)), stdout = FALSE, stderr = FALSE, wait = FALSE ) } stk <- stack() for (i in seq_len(n)) local({ conn <- renv_socket_accept(server$socket, open = "rb", timeout = 10) defer(close(conn)) stk$push(unserialize(conn)) }) expect_length(stk$data(), n) }) renv/tests/testthat/test-bootstrap.R0000644000176200001440000001646714731331230017356 0ustar liggesusers test_that("we can bootstrap the current version of renv", { skip_on_cran() skip_on_ci() renv_tests_scope() library <- renv_libpaths_active() bootstrap(version = "1.0.0", library = library) expect_true(renv_package_installed("renv", library)) expect_true(renv_package_version("renv") == "1.0.0") }) test_that("we can bootstrap an archived version of renv", { skip_on_cran() skip_on_ci() renv_tests_scope() library <- renv_libpaths_active() bootstrap(version = "0.1.0", library = library) expect_true(renv_package_installed("renv", library)) expect_true(renv_package_version("renv") == "0.1.0") }) test_that("we can install a version of renv with a sha", { skip_on_cran() skip_on_ci() skip_if_no_github_auth() renv_tests_scope() version <- structure("0.17.3-62", sha = "5049cef8a") library <- renv_libpaths_active() bootstrap(version = version, library = library) expect_true(renv_package_installed("renv", library)) desc <- utils::packageDescription("renv", library) expect_equal(desc$RemoteType, "github") expect_equal(desc$RemotePkgRef, "rstudio/renv") expect_equal(desc$RemoteSha, "5049cef8a94591b802f9766a0da092780f59f7e4") }) test_that("bootstrap functions don't depend on non-bootstrap APIs", { # pattern matching things that are bootstrapped for renv pattern <- "^renv_(?:bootstrap|json|options|remote)_" # get all of the bootstrap functions defined in renv renv <- asNamespace("renv") keys <- grep(pattern, ls(envir = renv), value = TRUE) fns <- mget(keys, envir = renv, mode = "function") bodies <- map(fns, body) # iterate over those functions and look for the called functions calls <- stack(mode = "character") recurse(bodies, function(node) { if (is.call(node) && is.symbol(node[[1L]])) calls$push(as.character(node[[1L]])) }) calls <- calls$data() # check what renv APIs are used apis <- grep("^renv_", calls, value = TRUE) # validate they're all available bad <- grep(pattern, apis, value = TRUE, invert = TRUE) expect_true(length(bad) == 0, info = paste("Functions:", bad, collapse = ", ")) }) test_that("bootstrapping functions standalone", { renv_tests_scope() # get all bootstrap APIs in package renv <- asNamespace("renv") keys <- ls(envir = renv, pattern = "^renv_bootstrap_", all.names = TRUE) vals <- mget(c("catf", "%||%", "header", "bootstrap", keys), envir = renv) # put those into a separate environment inheriting only from base, and # re-mark those as inheriting from base (so they only 'see' each-other) envir <- new.env(parent = baseenv()) for (i in seq_along(vals)) environment(vals[[i]]) <- envir list2env(vals, envir = envir) # set up library path library <- renv_scope_tempfile("renv-library-") ensure_directory(library) # try running 'sandboxed' version of bootstrap run <- function(version) { # construct bootstrap call code <- call("bootstrap", version = version, library = library) # run that call eval(code, envir = envir) # validate that renv was successfully installed expect_true(renv_package_installed("renv", lib.loc = library)) # validate that the correct version was installed descpath <- file.path(library, "renv/DESCRIPTION") desc <- renv_description_read(path = descpath) expect_identical(desc$Package, "renv") expect_identical(desc$Version, version) } # current on CRAN (test repository version) run("1.0.0") # in the CRAN archive (test repository version) run("0.1.0") }) test_that("bootstrapping gives informative output when succesful", { env <- environment() local_mocked_bindings( renv_bootstrap_download_impl = function(url, destfile) { file.create(destfile) defer(unlink(destfile), env) 0L }, renv_bootstrap_install_impl = function(cmd, args, ...) { structure("", status = 0L) }, renv_bootstrap_download_cran_latest_find = function(version) { if (package_version(version) < "1.0.0") { list(type = "binary", repos = "https://cran.rstudio.com") } }, load = function(...) invisible() ) renv_scope_options(renv.bootstrap.quiet = FALSE) library <- renv_scope_tempfile() expect_snapshot({ bootstrap(version = "0.9.0", library = library) bootstrap(version = "1.0.0", library = library) bootstrap(version = "1.0.0.1", library = library) }) }) test_that("bootstrapping gives informative output when download fails", { local_mocked_bindings( renv_bootstrap_download_impl = function(...) { stop("test failure") }, renv_bootstrap_download_cran_latest_find = function(version) { if (package_version(version) < "1.0.0") { list(type = "binary", repos = "https://cran.rstudio.com") } } ) renv_scope_options(renv.bootstrap.quiet = FALSE) library <- renv_scope_tempfile() expect_snapshot(error = TRUE, { bootstrap(version = "0.9.0", library = library) bootstrap(version = "1.0.0", library = library) bootstrap(version = "1.0.0.1", library = library) }) }) test_that("bootstrapping gives informative output when install fails", { local_mocked_bindings( renv_bootstrap_download_impl = function(url, destfile) { file.create(destfile) 0L }, renv_bootstrap_install_impl = function(...) { structure("test failure", status = 123L) } ) renv_scope_options(renv.bootstrap.quiet = FALSE) library <- renv_scope_tempfile() expect_snapshot(bootstrap(version = "1.0.0.1", library = library), error = TRUE) }) # helpers ----------------------------------------------------------------- test_that("renv_boostrap_version_validate() recognises when versions are the same", { expect_true( renv_bootstrap_validate_version( version = structure("1.2.3", sha = "abcd123"), description = list(RemoteSha = "abcd1234567") ) ) expect_true( renv_bootstrap_validate_version( version = "1.2.3", description = list(Version = "1.2.3") ) ) }) test_that("renv_boostrap_version_validate() gives good warnings", { renv_scope_options(renv.bootstrap.quiet = FALSE) expect_snapshot({ # out-of-sync (request release; have different release) . <- renv_bootstrap_validate_version( version = "1.2.3", description = list(Version = "2.3.4") ) # out-of-sync (request dev; have release) . <- renv_bootstrap_validate_version( version = "1.2.3-1", description = list(Version = "1.2.3") ) # out-of-sync (request dev; have different dev) . <- renv_bootstrap_validate_version( version = structure("1.2.3-1", sha = "22d015905828c3398728a5ff9e381e0433976263"), description = list( Version = "1.2.3-1", RemoteType = "github", RemoteSha = "6b09befaaba3f55e0e2c141cb45c5d247b61ef1e" ) ) # out-of-sync (request dev; have release) . <- renv_bootstrap_validate_version( version = structure("1.2.3-1", sha = "22d015905828c3398728a5ff9e381e0433976263"), description = list( Version = "1.2.3" ) ) }) }) test_that("bootstrap version validation handles 'standard' remote types", { renv_scope_options(renv.bootstrap.quiet = FALSE) expect_snapshot( . <- renv_bootstrap_validate_version( version = "1.0.0", description = list( Version = "1.0.1", RemoteType = "standard", RemoteSha = "1.0.1" ) ) ) }) renv/tests/testthat/test-sysreqs.R0000644000176200001440000000065214761163114017046 0ustar liggesusers test_that("system requirements are reported", { skip_on_cran() renv_tests_scope() renv_scope_binding(the, "os", "linux") local({ renv_scope_binding(the, "distro", "ubuntu") syspkg <- renv_sysreqs_resolve("zlib") expect_equal(syspkg, "zlib1g-dev") }) local({ renv_scope_binding(the, "distro", "redhat") syspkg <- renv_sysreqs_resolve("zlib") expect_equal(syspkg, "zlib-devel") }) }) renv/tests/testthat/test-load.R0000644000176200001440000000353314731330073016252 0ustar liggesusers test_that("invalid lockfile entries are reported", { renv_tests_scope() renv_scope_options(repos = NULL) expect_warning(renv_load_r(getwd(), NULL)) expect_warning(renv_load_r(getwd(), list())) # this used to be a warning, but now we just write as an info message # expect_warning(renv_load_r(getwd(), list(Version = "1.0.0"))) }) test_that("renv/settings.R is sourced on load if available", { renv_tests_scope() ensure_directory("renv") writeLines("options(renv.test.dummy = 1)", con = "renv/settings.R") renv_load_settings(getwd()) expect_equal(getOption("renv.test.dummy"), 1) options(renv.test.dummy = NULL) }) test_that("errors when sourcing user profile are reported", { skip_on_cran() renv_tests_scope() renv_scope_options(renv.config.user.profile = TRUE) profile <- renv_scope_tempfile("renv-profile-", fileext = ".R") writeLines("stop(1)", con = profile) renv_scope_envvars(R_PROFILE_USER = profile) tryCatch(expect_warning(renv_load_rprofile(getwd())), error = identity) }) test_that("load() installs packages if needed", { renv_tests_scope("breakfast") renv_scope_envvars(RENV_CONFIG_STARTUP_QUIET = "FALSE") install("bread") init() unlink("renv/library", recursive = TRUE) expect_snapshot(load()) }) test_that("load() reports on problems", { renv_scope_libpaths() renv_tests_scope() renv_scope_envvars(RENV_CONFIG_STARTUP_QUIET = "FALSE") renv_tests_scope("egg") init() record("egg@2.0.0") expect_snapshot(load()) }) test_that("load() delegates to base::load() when appropriate", { renv_tests_scope() value <- 42 save(value, file = ".RData") rm(value) load(".RData") expect_equal(value, 42) rm(value) load(file = ".RData") expect_equal(value, 42) rm(value) envir <- new.env(parent = emptyenv()) load(".RData", envir = envir) expect_equal(envir$value, 42) }) renv/tests/testthat/test-bioconductor.R0000644000176200001440000001444614740260564020041 0ustar liggesusers # R 3.6 appears to have trouble if we try to load and unload # different versions of BiocManager in the same session, so # we need to use this hack to ensure that promises in the S3 # methods table are forced at opportune times if (getRversion() < "4.0") { setHook( packageEvent("BiocManager", "onLoad"), function(...) renv_patch_methods_table() ) setHook( packageEvent("BiocManager", "onUnload"), function(...) renv_patch_methods_table() ) } test_that("packages can be installed, restored from Bioconductor", { skip_slow() skip_on_os("windows") skip_if(getRversion() < "3.5.0") skip_if(R.version$nickname == "Unsuffered Consequences") renv_tests_scope("Biobase") local({ renv_tests_scope_system_cache() install("BiocManager") }) suppressMessages(BiocManager::install("Biobase", quiet = TRUE, update = FALSE, ask = FALSE)) expect_true(renv_package_installed("BiocManager")) expect_true(renv_package_installed("BiocVersion")) expect_true(renv_package_installed("Biobase")) snapshot(prompt = FALSE) lockfile <- snapshot(lockfile = NULL) expect_true("Bioconductor" %in% names(lockfile)) BiocManager <- renv_scope_biocmanager() defer(unloadNamespace("BiocManager")) expect_equal(lockfile$Bioconductor$Version, format(BiocManager$version())) records <- renv_lockfile_records(lockfile) expect_true("BiocManager" %in% names(records)) expect_true("BiocVersion" %in% names(records)) expect_true("Biobase" %in% names(records)) remove("Biobase") restore() expect_true(renv_package_installed("Biobase")) }) test_that("install(, rebuild = TRUE) works", { skip_on_cran() skip_on_os("windows") skip_if(getRversion() < "3.5.0") skip_if(R.version$nickname == "Unsuffered Consequences") skip_if_not_installed("BiocManager") renv_tests_scope() defer(unloadNamespace("BiocManager")) local({ renv_tests_scope_system_cache() install("bioc::Biobase", rebuild = TRUE) }) expect_true(renv_package_installed("Biobase")) }) test_that("bioconductor.version can be used to freeze version", { project <- renv_tests_scope() defer(unloadNamespace("BiocManager")) settings$bioconductor.version("3.14", project = project) expect_equal(renv_bioconductor_version(project = project), "3.14") }) test_that("we can restore a lockfile using multiple Bioconductor releases", { skip_on_cran() skip_on_windows() skip_if(getRversion()[1, 1:2] != "4.1") skip_if_not_installed("BiocManager") project <- renv_tests_scope() defer(unloadNamespace("BiocManager")) path <- renv_tests_path("resources/bioconductor.lock") lockfile <- renv_lockfile_read(path) status <- restore( lockfile = lockfile, packages = c("limma", "BiocGenerics"), rebuild = TRUE ) expect_true(renv_package_version("limma") == "3.50.0") expect_true(renv_package_version("BiocGenerics") == "0.38.0") }) test_that("Bioconductor packages add BiocManager as a dependency", { renv_tests_scope() defer(unloadNamespace("BiocManager")) init() local({ renv_tests_scope_system_cache() install("bioc::BiocGenerics") }) snapshot() writeLines("library(BiocGenerics)", "dependencies.R") lockfile <- snapshot() expect_contains(names(lockfile$Packages), "BiocManager") # And it goes away when we remove the dependency unlink("dependencies.R") lockfile <- snapshot() records <- renv_lockfile_records(lockfile) expect_length(records, 0L) }) test_that("remotes which depend on Bioconductor packages can be installed", { skip_on_cran() renv_tests_scope() renv_scope_options(repos = c(CRAN = "https://cloud.r-project.org")) defer(unloadNamespace("BiocManager")) # create a dummy package which has a Bioconductor dependency pkgdir <- file.path(tempdir(), "bioc.example") ensure_directory(pkgdir) desc <- heredoc(" Package: bioc.example Version: 1.0.0 Imports: Biobase biocViews: Biology ") writeLines(desc, con = file.path(pkgdir, "DESCRIPTION")) # try to install it local({ renv_tests_scope_system_cache() install(pkgdir) }) expect_true(renv_package_installed("Biobase")) expect_true(renv_package_installed("BiocGenerics")) }) test_that("auto-bioc install happens silently", { # https://github.com/rstudio/renv/actions/runs/5326472190/jobs/9648557761#step:6:295 skip_if(renv_platform_windows()) renv_tests_scope() renv_tests_scope_system_cache() defer(unloadNamespace("BiocManager")) install("bioc::BiocGenerics") expect_true(renv_package_installed("BiocManager")) }) test_that("standard bioc remotes are standardized appropriately", { contents <- heredoc(' Package: BiocVersion Version: 3.18.1 Title: Set the appropriate version of Bioconductor packages Description: This package provides repository information for the appropriate version of Bioconductor. Authors@R: c( person("Martin", "Morgan", email = "martin.morgan@roswellpark.org", role = "aut"), person("Marcel", "Ramos", email = "marcel.ramos@roswellpark.org", role = "ctb"), person("Bioconductor", "Package Maintainer", email = "maintainer@bioconductor.org", role = c("ctb", "cre"))) biocViews: Infrastructure Depends: R (>= 4.3.0) License: Artistic-2.0 Encoding: UTF-8 RoxygenNote: 6.0.1 git_url: https://git.bioconductor.org/packages/BiocVersion git_branch: RELEASE_3_18 git_last_commit: 70680b8 git_last_commit_date: 2023-11-15 Repository: Bioconductor 3.18 Date/Publication: 2023-11-18 NeedsCompilation: no Packaged: 2023-11-18 19:15:45 UTC; biocbuild Author: Martin Morgan [aut], Marcel Ramos [ctb], Bioconductor Package Maintainer [ctb, cre] Maintainer: Bioconductor Package Maintainer Built: R 4.3.2; ; 2023-11-20 12:36:26 UTC; unix RemoteType: standard RemotePkgRef: BiocVersion RemoteRef: BiocVersion RemoteRepos: https://bioconductor.org/packages/3.18/bioc RemotePkgPlatform: aarch64-apple-darwin20 RemoteSha: 3.18.1 ') descfile <- tempfile("biocversion-") writeLines(contents, con = descfile) actual <- renv_snapshot_description(path = descfile) expected <- list( Package = "BiocVersion", Version = "3.18.1", Source = "Bioconductor", Repository = "Bioconductor 3.18" ) expect_identical(actual[names(expected)], expected) }) renv/tests/testthat/test-scaffold.R0000644000176200001440000000052414731330073017111 0ustar liggesusers test_that("renv.lock is created when scaffold is called", { renv_tests_scope() scaffold() expect_true(file.exists("renv.lock")) }) test_that("scaffold() accepts project settings", { renv_tests_scope() scaffold(settings = list(ignored.packages = "A")) ignored <- settings$ignored.packages() expect_identical(ignored, "A") }) renv/tests/testthat/test-dependencies.R0000644000176200001440000004242514740257227017775 0ustar liggesusers test_that("can select fields", { renv_tests_scope() expect_equal(renv_dependencies_impl(field = "Package"), character()) writeLines("library(utils)", "deps.R") expect_equal(renv_dependencies_impl(field = "Package"), "utils") }) test_that(".Rproj files requesting devtools is handled", { renv_tests_scope() writeLines("PackageUseDevtools: Yes", "project.Rproj") deps <- dependencies(dev = TRUE) packages <- deps$Package expect_setequal(packages, c("devtools", "roxygen2")) }) test_that("usages of library, etc. are properly handled", { deps <- dependencies("resources/code.R") pkgs <- deps$Package expect_equal(pkgs, tolower(pkgs)) l <- pkgs[nchar(pkgs) == 1] expect_equal(sort(l), letters[seq_along(l)]) }) test_that("parse errors are okay in .Rmd documents", { deps <- dependencies("resources/chunk-errors.Rmd") pkgs <- deps$Package expect_setequal(pkgs, c("rmarkdown", "dplyr")) }) test_that("inline chunks are parsed for dependencies", { deps <- dependencies("resources/inline-chunks.Rmd") pkgs <- deps$Package expect_setequal(pkgs, c("rmarkdown", "inline", "multiple", "separate")) }) test_that("usages of S4 tools are discovered", { file <- renv_test_code({setClass("ClassSet")}) deps <- dependencies(file) expect_true(deps$Package == "methods") }) test_that("the package name is validated when inferring dependencies", { file <- renv_test_code({SomePackage::setClass("ClassSet")}) deps <- dependencies(file) expect_true("SomePackage" %in% deps$Package) expect_false("methods" %in% deps$Package) }) test_that("empty chunks don't cause issues during dependency resolution", { deps <- dependencies("resources/empty-chunk.Rmd") pkgs <- deps$Package expect_setequal(pkgs, c("rmarkdown")) }) test_that("pacman::p_load() usages are understood", { deps <- dependencies("resources/pacman.R") packages <- setdiff(deps$Package, "pacman") expect_setequal(packages, letters[1:length(packages)]) }) test_that("import:: usages are understood", { deps <- dependencies("resources/import.R") packages <- setdiff(deps$Package, "import") expect_setequal(packages, letters[1:length(packages)]) }) test_that("box::use() usages are handled", { deps <- dependencies(test_path("resources/box.R")) actual <- deps$Package expected <- c("A", "B", "C", "D", "box") expect_setequal(actual, expected) }) test_that("targets::tar_option_set() dependencies are handled", { deps <- dependencies("resources/targets.R") expect_setequal(deps$Package, c("A", "B", "targets")) }) test_that("renv warns when large number of files found in total", { renv_scope_options(renv.config.dependencies.limit = 5L) strip_dir <- function(x) gsub(basename(getwd()), "", x) renv_tests_scope() dir.create("a") dir.create("b") file.create(sprintf("a/%.3i.R", 1:3)) file.create(sprintf("b/%.3i.R", 1:3)) expect_snapshot(. <- dependencies()) }) test_that("renv warns when large number of files found in one directory", { renv_scope_options(renv.config.dependencies.limit = 5L) renv_tests_scope() file.create(sprintf("%.3i.R", 1:10)) expect_snapshot(. <- dependencies()) }) test_that("evil knitr chunks are handled", { deps <- dependencies("resources/evil.Rmd") packages <- deps$Package expect_setequal(packages, c("rmarkdown", "a", "b")) }) test_that("renv_dependencies_requires warns once", { expect_warning(renv_dependencies_require("nosuchpackage", "test")) expect_false(renv_dependencies_require("nosuchpackage", "test")) }) test_that("the presence of an rsconnect folder forces dependency on rsconnect", { renv_tests_scope() dir.create("rsconnect") deps <- dependencies() expect_true("rsconnect" %in% deps$Package) }) test_that("dependencies can accept multiple files", { deps <- dependencies( path = c("packages/bread", "packages/breakfast"), root = getwd() ) expect_setequal(deps$Package, c("oatmeal", "toast")) }) test_that("no warnings are produced when crawling dependencies", { expect_warning( regexp = NA, dependencies( "resources", root = file.path(getwd(), "resources") ) ) }) test_that("Suggests are dev. deps for all projects", { renv_tests_scope() expected <- data.frame( Package = "bread", Dev = TRUE, stringsAsFactors = FALSE ) writeLines(c("Type: Project", "Suggests: bread"), con = "DESCRIPTION") deps <- dependencies(dev = TRUE) expect_equal(deps[c("Package", "Dev")], expected) writeLines(c("Type: Package", "Suggests: bread"), con = "DESCRIPTION") deps <- dependencies(dev = TRUE) expect_equal(deps[c("Package", "Dev")], expected) }) test_that("packages referenced by modules::import() are discovered", { deps <- dependencies("resources/modules.R") expect_setequal(deps$Package, c("A", "B", "C", "D", "G", "H", "modules")) }) # https://github.com/rstudio/renv/issues/2007 test_that("module without parameter doesn't give an error", { deps <- dependencies("resources/modules-empty.R") expect_setequal(deps$Package, character()) }) test_that("dependencies specified in R Markdown site generators are found", { renv_tests_scope() writeLines( c("---", "site: blogdown:::blogdown_site", "---"), con = "index.Rmd") deps <- dependencies() expect_true("blogdown" %in% deps$Package) writeLines( c("---", "site: bookdown::bookdown_site", "---"), con = "index.Rmd") deps <- dependencies() expect_true("bookdown" %in% deps$Package) }) test_that("Suggest dependencies are ignored by default", { renv_tests_scope("breakfast") install("breakfast") expect_false(renv_package_installed("egg")) }) test_that("a call to geom_hex() implies a dependency on ggplot2", { file <- renv_test_code({ ggplot() + geom_hex() }) deps <- dependencies(file) expect_true("hexbin" %in% deps$Package) }) test_that("empty fields are handled in DESCRIPTION", { deps <- dependencies("resources/DESCRIPTION") expect_setequal(deps$Package, c("a", "b", "c")) }) test_that("recursive symlinks are handled", { skip_on_os("windows") project <- renv_scope_tempfile() ensure_directory(project) renv_scope_wd(project) symlink <- file.path(project, "symlink") file.symlink(dirname(symlink), symlink) dependencies() }) test_that("exercise chunks are ignored", { deps <- dependencies("resources/learnr-exercise.Rmd") expect_true("A" %in% deps$Package) }) test_that("dependencies in R functions can be found", { deps <- dependencies(function() renv::init) expect_true("renv" %in% deps$Package) }) test_that("dependencies in dotfiles are discovered", { renv_tests_scope() writeLines("library(A)", con = ".Rprofile") deps <- dependencies() expect_true(nrow(deps) == 1L) expect_true(basename(deps$Source) == ".Rprofile") expect_true(deps$Package == "A") }) test_that("reused knitr chunks are handled", { deps <- dependencies("resources/knitr-reused-chunks.Rmd") expect_true(all(c("A", "B") %in% deps$Package)) }) test_that("empty / missing labels are handled", { deps <- dependencies("resources/empty-label.Rmd") expect_true(all(c("A", "B") %in% deps$Package)) }) test_that("only dependencies in a top-level DESCRIPTION file are used", { renv_tests_scope() dir.create("a") writeLines("Depends: toast", con = "DESCRIPTION") writeLines("Depends: oatmeal", con = "a/DESCRIPTION") deps <- dependencies() expect_true("toast" %in% deps$Package) expect_false("oatmeal" %in% deps$Package) }) test_that("multiple output formats are handled", { deps <- dependencies("resources/multiple-output-formats.Rmd") expect_true("bookdown" %in% deps$Package) }) test_that("glue::glue() package usages are found", { deps <- dependencies("resources/glue.R") expect_true(all(c("A", "B", "C", "D", "E", "F", "G") %in% deps$Package)) expect_false(any(letters %in% deps$Package)) }) test_that("set_engine() package usages are found", { deps <- dependencies("resources/parsnip.R") expect_setequal(deps$Package, c("glmnet")) }) test_that("eval=F does not trip up dependencies", { deps <- dependencies("resources/eval.Rmd") expect_true("A" %in% deps$Package) expect_false("a" %in% deps$Package) }) test_that("renv.ignore=FALSE, eval=TRUE is handled", { deps <- dependencies("resources/ignore.Rmd") expect_true("A" %in% deps$Package) expect_false("a" %in% deps$Package) }) test_that("eval= is treated as truthy", { deps <- dependencies("resources/chunk-eval.Rmd", quiet = TRUE) expect_true("A" %in% deps$Package) expect_false("a" %in% deps$Package) }) test_that("piped expressions can be parsed for dependencies", { deps <- dependencies(renv_tests_path("resources/magrittr.R")) expect_setequal(deps$Package, c("A", "B", "C")) }) test_that("bslib dependencies are discovered", { deps <- dependencies("resources/bslib.Rmd") expect_true("bslib" %in% deps$Package) }) test_that("utility script dependencies are discovered", { deps <- dependencies("resources/utility") expect_false(is.null(deps)) expect_setequal(deps$Package, c("A", "B")) }) test_that("we handle shiny_prerendered documents", { deps <- dependencies("resources/shiny-prerendered.Rmd") expect_true("shiny" %in% deps$Package) }) test_that("we don't infer a dependency on rmarkdown for empty .qmd", { deps <- dependencies("resources/quarto-empty.qmd") expect_true(is.null(deps) || !"rmarkdown" %in% deps$Package) }) test_that("we do infer dependency on rmarkdown for .qmd with R chunks", { deps <- dependencies("resources/quarto-r-chunks.qmd") expect_true("rmarkdown" %in% deps$Package) }) test_that("we parse package references from arbitrary yaml fields", { deps <- dependencies("resources/rmd-base-format.Rmd") expect_true("bookdown" %in% deps$Package) expect_true("rticles" %in% deps$Package) }) test_that("dependencies in parameterized documents are discovered", { deps <- dependencies(test_path("resources/params.Rmd")) expect_true(all(c("shiny", "A") %in% deps$Package)) expect_false("B" %in% deps$Package) }) test_that("we ignore chunks with '#| eval: false'", { deps <- dependencies("resources/yaml-chunks.Rmd") expect_false("a" %in% deps$Package) expect_true("A" %in% deps$Package) }) test_that("dependencies in hidden folders are not scoured", { renv_tests_scope() dir.create(".hidden") writeLines("library(A)", con = ".hidden/deps.R") deps <- dependencies() expect_false("A" %in% deps$Package) writeLines("!.hidden", con = ".renvignore") deps <- dependencies() expect_true("A" %in% deps$Package) }) test_that("dependencies() doesn't barf on files without read permission", { skip_on_windows() renv_tests_scope() dir.create("secrets") writeLines("library(dplyr)", con = "secrets/secrets.R") Sys.chmod("secrets/secrets.R", mode = "0000") expect_error(renv_file_read("secrets/secrets.R")) deps <- dependencies() expect_true(NROW(deps) == 0L) }) test_that("dependencies() doesn't barf on malformed DESCRIPTION files", { skip_on_windows() renv_tests_scope() writeLines("Depends: A, B\n\nImports: C, D", con = "DESCRIPTION") deps <- dependencies() expect_setequal(deps$Package, c("A", "B", "C", "D")) }) test_that("dependencies() handles inline YAML comments", { deps <- dependencies("resources/chunk-yaml.Rmd") expect_true("A" %in% deps$Package) }) test_that("we can parse remotes from a DESCRIPTION file", { desc <- heredoc(' Remotes: r-dbi/DBItest ') descfile <- renv_scope_tempfile() writeLines(desc, con = descfile) deps <- renv_dependencies_discover_description(descfile, fields = "Remotes") expect_equal(deps$Package, "r-dbi/DBItest") }) test_that("dependencies ignore pseudo-code in YAML metadata", { path <- renv_scope_tempfile() writeLines(con = path, c( '---', 'title: "RStudio::conf reflections"', '---', '', 'Hello!' )) deps <- renv_dependencies_discover_rmd_yaml_header(path, "rmd") expect_equal(deps$Package, "rmarkdown") }) test_that("~/.Rprofile included in dev dependencies when config$user.profile()", { path <- renv_scope_tempfile("renv-profile", fileext = ".R") writeLines("library(utils)", path) renv_scope_envvars(R_PROFILE_USER = normalizePath(path, winslash = "/")) renv_scope_options(renv.config.user.profile = TRUE) renv_tests_scope() deps <- renv_dependencies_impl(dev = TRUE) expect_equal(deps$Package, "utils") expect_equal(deps$Dev, TRUE) }) test_that("captures dependencies from Jupyter notebooks", { path <- test_path("resources/notebook.ipynb") deps <- dependencies(path) expect_setequal(deps$Package, c("IRkernel", "MASS", "stats")) expect_equal(deps$Source, rep(renv_path_normalize(path), 3)) }) test_that("we tolerate calls when parsing dependencies", { document <- heredoc(' ```{r, renv.ignore=TRUE || TRUE} library(A) ``` ```{r, renv.ignore=FALSE && TRUE} library(B) ``` ') file <- renv_scope_tempfile("renv-test-", fileext = ".Rmd") writeLines(document, con = file) deps <- dependencies(file) expect_false("A" %in% deps$Package) expect_true("B" %in% deps$Package) }) test_that("dependencies() notifies the user if directories contain lots of files", { project <- renv_tests_scope() init() # create data directory with 'lots' of files dir.create("data") setwd("data") files <- sprintf("%03i.R", 1:200) file.create(files) setwd("..") # try to collect snapshot dependencies renv_scope_options(renv.dependencies.elapsed_time_threshold = 0) expect_snapshot(. <- renv_snapshot_dependencies(project)) }) test_that("dependencies() can parse NAMESPACE files", { project <- renv_tests_scope() desc <- heredoc(" Type: Package Package: test Version: 0.1.0 ") writeLines(desc, con = "DESCRIPTION") namespace <- heredoc(" import(utils) importFrom(tools, SIGQUIT) import(graphics, except = c(abline)) ") writeLines(namespace, con = "NAMESPACE") deps <- dependencies() expect_setequal(deps$Package, c("graphics", "tools", "utils")) }) test_that("dependencies() handles upper-case engine names", { document <- heredoc(" ```{R} library(A) ``` ") file <- renv_scope_tempfile(fileext = ".Rmd") writeLines(document, con = file) deps <- dependencies(file, quiet = TRUE) expect_true("A" %in% deps$Package) }) test_that("dependencies() ignores R when specified in a DESCRIPTION file", { project <- renv_tests_scope() desc <- heredoc(" Type: Package Package: test Version: 0.1.0 Depends: R (>= 4.0.0) ") writeLines(desc, con = "DESCRIPTION") deps <- dependencies(quiet = TRUE) expect_false("R" %in% deps$Package) }) test_that("dependencies() with different extensions", { project <- renv_tests_scope() writeLines("library(A)", con = "a.R") writeLines("```{r}\nlibrary(B)\n```", con = "a.Rmd") deps <- dependencies(quiet = TRUE) expect_true("A" %in% deps$Package) expect_true("B" %in% deps$Package) }) test_that("dependencies() can infer an svglite dependency from ggsave", { document <- heredoc(' library(ggplot2) ggsave(filename = "test.svg") ') file <- renv_scope_tempfile("renv-test-", fileext = ".R") writeLines(document, con = file) deps <- dependencies(file, quiet = TRUE) expect_contains(deps$Package, "svglite") }) test_that("dependencies() can handle calls", { document <- heredoc(' ```{r} #| eval = c(1, 2) ``` ') file <- renv_scope_tempfile("renv-test-", fileext = ".Rmd") writeLines(document, con = file) expect_no_warning( dependencies(file, quiet = TRUE) ) }) test_that("dependencies() detects usages of Junit test reporters", { check <- function(document) { file <- renv_scope_tempfile("renv-test-", fileext = ".R") writeLines(document, con = file) deps <- dependencies(file, quiet = TRUE) expect_contains(deps$Package, "xml2") } check("JunitReporterMock <- R6::R6Class(\"JunitReporterMock\", inherit = JunitReporter)") check("JunitReporter$new()") check("testthat::test_dir(reporter = JunitReporter)") check("testthat::test_dir(reporter = \"junit\")") }) test_that("dependencies() detects usage of ragg_png device", { check <- function(document) { file <- renv_scope_tempfile("renv-test-", fileext = ".R") writeLines(document, con = file) deps <- dependencies(file, quiet = TRUE) expect_contains(deps$Package, "ragg") } check("opts_chunk$set(dev = \"ragg_png\")") check("knitr::opts_chunk$set(dev = \"ragg_png\")") }) test_that("dependencies() does not create 'object' in parent environment", { result <- dependencies("resources/code.R", quiet = TRUE) expect_false(exists("object", envir = environment(), inherits = FALSE)) }) test_that("R scripts that appear destined for knitr::spin() are detected", { result <- dependencies("resources/knitr-spin.R", quiet = TRUE) expect_contains(result$Package, c("knitr", "rmarkdown")) }) test_that("renv infers a dev. dependency on lintr", { project <- renv_tests_scope() file.create(".lintr") deps <- dependencies(quiet = TRUE, dev = TRUE) expect_contains(deps$Package, "lintr") }) test_that("https://github.com/rstudio/renv/issues/2052", { renv_scope_tempdir() dir.create("subdir") writeLines("library(A)", con = "subdir/test.R") writeLines(c("*", "!/**/", "!*.*"), con = ".renvignore") deps <- dependencies(quiet = TRUE, root = getwd()) expect_contains(deps$Package, "A") }) test_that("https://github.com/rstudio/renv/issues/2047", { renv_tests_scope() writeLines("citation(\"breakfast\")", con = "_deps.R") init() expect_true(renv_package_installed("breakfast")) }) renv/tests/testthat/test-caution.R0000644000176200001440000000200314761163114016767 0ustar liggesusers test_that("bulletin() creates bulleted list with optional postamble", { expect_snapshot({ bulletin("preamble", letters[1:3]) bulletin("preamble", letters[1:3], postamble = "after") }) }) test_that("bulletin() doesn't show pre/post amble if no values", { expect_silent(bulletin("before", character(), "after")) }) test_that("options(renv.pretty.print.emitter) is respected", { skip_on_cran() project <- renv_tests_scope("bread") init() cls <- "renv.pretty.print.emitter" emitter <- function(text) renv_condition_signal(cls) renv_scope_options(renv.pretty.print.emitter = emitter) renv_scope_options(renv.verbose = TRUE) # regular pretty printer expect_condition(bulletin("preamble", 1), class = cls) # record printer lockfile <- renv_lockfile_create(project = getwd()) records <- renv_lockfile_records(lockfile) expect_condition(renv_pretty_print_records(NULL, records), class = cls) # diff printer expect_condition(renv_pretty_print_records_pair(NULL, records, records)) }) renv/tests/testthat/test-properties.R0000644000176200001440000000153014731330073017522 0ustar liggesusers test_that("properties can be read successfully", { data <- '# This is a comment.\nKey: Value' path <- renv_scope_tempfile("renv-properties-") writeLines(data, con = path) # trim whitespace by default props <- renv_properties_read(path = path) expect_identical(props, list(Key = "Value")) # without trimming whitespace props <- renv_properties_read(path = path, trim = FALSE) expect_identical(props, list(Key = " Value")) }) test_that("quoted properties are unquoted", { props <- renv_properties_read( path = "resources/properties.txt", delimiter = "=", dequote = TRUE, trim = TRUE ) expected <- list( Key1 = "Value 1", Key2 = 'Value "2"', Key3 = "Value '3'" ) expect_identical(props[names(expected)], expected) expect_equal(props$Key4, "This value spans multiple lines.") }) renv/tests/testthat/test-description.R0000644000176200001440000000257214731330073017660 0ustar liggesusers test_that("snapshotting broken DESCRIPTION files is an error", { # empty file file <- renv_scope_tempfile() file.create(file) expect_error(renv_snapshot_description(file)) # missing Version field fields <- c(Type = "Package", Package = "test") renv_dcf_write(fields, file = file) expect_error(renv_snapshot_description(file)) }) test_that("we read DESCRIPTION files correctly", { contents <- heredoc(" Package: example Description: This is a description. Indented fields might have colons: that's fine. Depends: apple URL: https://posit.co ") descfile <- renv_scope_tempfile() writeLines(contents, con = descfile) actual <- renv_description_read(path = descfile) expected <- list( Package = "example", Description = paste( "This is a description.", "Indented fields might have colons: that's fine." ), Depends = "apple", URL = "https://posit.co" ) expect_equal(actual, expected) }) test_that("we can read a DESCRIPTION file with empty lines", { contents <- heredoc(" Package: example Description: Oops! There's a blank line! ") descfile <- renv_scope_tempfile() writeLines(contents, con = descfile) actual <- renv_description_read(path = descfile) expected <- list( Package = "example", Description = "Oops! There's a blank line!" ) expect_equal(actual, expected) }) renv/tests/testthat/helper-zzz.R0000644000176200001440000000030214731330073016457 0ustar liggesusers # This is the moral equivalent of 'setup.R', but placed here so that it's # automatically run after both `devtools::load_all()` and `devtools::test()`. renv_tests_setup(scope = teardown_env()) renv/tests/testthat/test-migrate.R0000644000176200001440000000454714731330073016771 0ustar liggesusers skip_if_no_packrat <- function() { skip_on_cran() skip_on_windows() skip_if_not_installed("packrat") version <- packageVersion("packrat") if (renv_version_length(version) != 3) skip("cannot test with development version of Packrat") packrat <- renv_available_packages_latest(package = "packrat", type = "source") if (version != packrat$Version) skip("packrat is not current") TRUE } test_that("a sample Packrat project can be migrated", { skip_if_no_packrat() # use dummy caches for this test cache <- renv_scope_tempfile("packrat-cache-") root <- renv_scope_tempfile("renv-root-") renv_scope_envvars(R_PACKRAT_CACHE_DIR = cache, RENV_PATHS_ROOT = root) # set up requireNamespace("packrat") renv_tests_scope("breakfast") # initialize packrat quietly( expect_warning( packrat::init(enter = FALSE, options = list(use.cache = TRUE)) ) ) # try to migrate migrate() # packages we expect to find expected <- c("bread", "breakfast", "oatmeal", "toast", "packrat") # check the renv cache cachelist <- renv_cache_list() expect_setequal(expected, basename(cachelist)) # check the renv sources directory cransources <- renv_paths_source("cran") expect_setequal(expected, list.files(cransources)) # check the lockfile lockfile <- renv_lockfile_read("renv.lock") records <- renv_lockfile_records(lockfile) expect_setequal(expected, names(records)) }) test_that("a Packrat project with no library can be migrated", { skip_if_no_packrat() # TODO: skip tests if non-CRAN packrat is installed # use dummy caches for this test renv_scope_envvars( R_PACKRAT_CACHE_DIR = renv_scope_tempfile("packrat-cache-"), RENV_PATHS_ROOT = renv_scope_tempfile("renv-cache-") ) renv_tests_scope("breakfast") # initialize packrat quietly( expect_warning( packrat::init(enter = FALSE, options = list(use.cache = TRUE)) ) ) # remove library unlink("packrat/lib", recursive = TRUE) # try to migrate migrate() # packages we expect to find expected <- c("bread", "breakfast", "oatmeal", "toast", "packrat") # check the renv cache cachelist <- renv_cache_list() expect_setequal(expected, basename(cachelist)) # check the lockfile lockfile <- renv_lockfile_read("renv.lock") records <- renv_lockfile_records(lockfile) expect_setequal(expected, names(records)) }) renv/tests/testthat/test-parse.R0000644000176200001440000000130514731330073016440 0ustar liggesusers test_that("code containing multibyte characters can be parsed", { skip_if(renv_platform_unix() && !renv_l10n_utf8()) code <- intToUtf8(258L) # "Ă" parsed <- renv_parse_text(code) expect_true(inherits(parsed, "expression")) code <- intToUtf8(193L) # "Á" parsed <- renv_parse_text(code) expect_true(inherits(parsed, "expression")) code <- enc2utf8("Á # 鬼") parsed <- renv_parse_text(code) expect_true(inherits(parsed, "expression")) code <- enc2utf8("paste(Á, '鬼')") parsed <- renv_parse_text(code) expect_true(inherits(parsed, "expression")) code <- enc2utf8("paste(`Á`, `鬼`)") parsed <- renv_parse_text(code) expect_true(inherits(parsed, "expression")) }) renv/tests/testthat/test-download.R0000644000176200001440000001150314731330073017136 0ustar liggesusers test_that("we avoid downloading files twice", { skip_on_cran() if (!renv_download_method() %in% c("curl", "wget")) skip("required downloader not available") url <- "https://cloud.r-project.org/src/contrib/Archive/sourcetools/sourcetools_0.1.0.tar.gz" destfile <- renv_scope_tempfile("renv-download-", fileext = ".tar.gz") # download once and check file metadata download(url, destfile, quiet = TRUE) before <- renv_file_info(destfile)$mtime # download again and check the file info hasn't changed download(url, destfile, quiet = TRUE) after <- renv_file_info(destfile)$mtime # check that they're the same. expect_identical(before, after) }) test_that("we can successfully tweak the user agent string", { utils <- asNamespace("utils") ok <- is.function(utils$makeUserAgent) && identical(formals(utils$makeUserAgent), pairlist(format = TRUE)) if (!ok) return(NULL) headers <- c("Key" = "Value") before <- utils$makeUserAgent expect_true(renv_download_default_agent_scope_impl(headers = headers)) after <- utils$makeUserAgent expect_identical( paste0(before(format = TRUE), "Key: Value\r\n"), after(format = TRUE) ) expect_identical(before(format = FALSE), after(format = FALSE)) }) test_that("renv_download_default_agent_scope_impl resets itself", { before <- asNamespace("utils")$makeUserAgent local(renv_download_default_agent_scope_impl(c("Key" = "Value"))) expect_equal(asNamespace("utils")$makeUserAgent, before) }) test_that("we can successfully download files with different downloaders", { skip_on_cran() skip_on_os("windows") # download a small sample file url <- "https://cloud.r-project.org/src/base/THANKS" destfile <- renv_scope_tempfile("r-thanks-") method <- renv_download_method() download.file(url, destfile = destfile, quiet = TRUE, method = method) thanks <- readLines(destfile) if (nzchar(Sys.which("curl"))) local({ renv_scope_envvars(RENV_DOWNLOAD_FILE_METHOD = "curl") destfile <- renv_scope_tempfile("r-curl-thanks-") download(url, destfile, quiet = TRUE) expect_equal(readLines(destfile), thanks) }) if (renv_platform_windows()) local({ renv_scope_envvars(RENV_DOWNLOAD_FILE_METHOD = "wininet") destfile <- renv_scope_tempfile("r-wininet-thanks-") download(url, destfile, quiet = TRUE) expect_equal(readLines(destfile), thanks) }) if (capabilities("libcurl") %||% FALSE) local({ renv_scope_envvars(RENV_DOWNLOAD_FILE_METHOD = "libcurl") destfile <- renv_scope_tempfile("r-libcurl-thanks-") download(url, destfile, quiet = TRUE) expect_equal(readLines(destfile), thanks) }) # TODO: fails on winbuilder # if (nzchar(Sys.which("wget"))) local({ # renv_scope_envvars(RENV_DOWNLOAD_FILE_METHOD = "wget") # destfile <- renv_scope_tempfile("r-wget-thanks-") # download(url, destfile, quiet = TRUE) # expect_equal(readLines(destfile), thanks) # }) }) test_that("downloads work with file URIs", { renv_tests_scope() repos <- getOption("repos")[["CRAN"]] url <- file.path(repos, "src/contrib/PACKAGES") destfile <- renv_scope_tempfile("packages-") download(url, destfile = destfile) expect_true(file.exists(!!destfile)) }) test_that("downloads work with UNC paths on Windows", { skip_on_cran() skip_if_not(renv_platform_windows()) renv_tests_scope() # get path to repos PACKAGES file repos <- getOption("repos")[["CRAN"]] base <- sub("^file:/*", "", repos) url <- file.path(base, "src/contrib/PACKAGES") norm <- renv_path_normalize(url) # create server-style path to localhost unc <- sub("^([a-zA-Z]):", "//localhost/\\1$", norm) expect_true(file.exists(unc)) destfile <- renv_scope_tempfile("packages-") urls <- c(unc, paste0("file:", unc)) for (url in urls) { download(unc, destfile) expect_true(file.exists(destfile)) unlink(destfile) } }) test_that("we can check that a URL is available", { skip_on_cran() skip_if_not_installed("webfakes") app <- webfakes::new_app_process(webfakes::httpbin_app()) url <- paste0(app$url(), "/bytes/100") expect_true(renv_download_available(url)) # also test the different methods expect_true(renv_download_available_headers(url)) expect_true(renv_download_available_range(url)) expect_true(renv_download_available_fallback(url)) }) test_that("download failures are reported if destfile not writable", { skip_on_cran() skip_on_os("windows") tdir <- tempfile("renv-forbidden-") dir.create(tdir, mode = "0000") expect_true(file.exists(tdir)) tfile <- tempfile("renv-download-", tmpdir = tdir) expect_error( download(url = "https://cran.r-project.org/", destfile = tfile, quiet = TRUE) ) Sys.chmod(tdir, mode = "0755") download(url = "https://cran.r-project.org/", destfile = tfile, quiet = TRUE) expect_true(file.exists(tfile)) unlink(tdir, recursive = TRUE) }) renv/tests/testthat/test-rebuild.R0000644000176200001440000000053414731330073016757 0ustar liggesusers test_that("rebuild forces a package to rebuilt, bypassing cache", { renv_tests_scope("bread") init() records <- rebuild("bread") expect_length(records, 1L) }) test_that("rebuild installs latest-available package if not installed", { renv_tests_scope() init() rebuild("bread") expect_true(renv_package_installed("bread")) }) renv/tests/testthat/test-cleanse.R0000644000176200001440000000116214731330073016741 0ustar liggesusers test_that("we can clean up empty directory hierarchies", { root <- renv_scope_tempfile("renv-root-") ensure_directory(root) renv_scope_wd(root) dir.create("A/B", recursive = TRUE) dir.create("C", recursive = TRUE) dir.create("D") file.create("D/E") file.create("F") actual <- list.files(recursive = TRUE, include.dirs = TRUE) expected <- c("A", "A/B", "C", "D", "D/E", "F") expect_equal(sort(actual), sort(expected)) renv_cleanse_empty(path = getwd()) actual <- list.files(recursive = TRUE, include.dirs = TRUE) expected <- c("D", "D/E", "F") expect_equal(sort(actual), sort(expected)) }) renv/tests/testthat/test-init.R0000644000176200001440000001622114761163114016277 0ustar liggesusers test_that("init() automatically installs referenced packages", { skip_on_cran() renv_tests_scope("bread") init() expect_true(renv_package_installed("bread")) }) test_that("we can initialize a project using 'breakfast'", { skip_on_cran() skip_on_covr() renv_tests_scope("breakfast") init() expect_true(renv_project_initialized(getwd())) expected <- c("bread", "breakfast", "oatmeal", "toast") lockfile <- snapshot(lockfile = NULL) actual <- setdiff(names(renv_lockfile_records(lockfile)), "renv") expect_setequal(actual, expected) }) test_that("we can initialize a project using 'toast'", { skip_on_covr() renv_tests_scope("toast") init() expected <- c("bread", "toast") lockfile <- snapshot(lockfile = NULL) actual <- setdiff(names(renv_lockfile_records(lockfile)), "renv") expect_setequal(actual, expected) }) test_that("we cannot initialize a project using 'brunch'", { renv_tests_scope("brunch") # 'brunch' will fail to install init() expect_false(file.exists(renv_paths_library("brunch"))) }) test_that("attempts to initialize a project with a missing package is okay", { renv_tests_scope("missing") # package 'missing' does not exist and so cannot be installed init() expect_false(file.exists(renv_paths_library("missing"))) }) test_that("the remotes field in a DESCRIPTION is honored", { skip_on_cran() skip_if_no_github_auth() renv_tests_scope("halloween") install("halloween") ip <- installed_packages(lib.loc = renv_libpaths_active()) expect_true("halloween" %in% ip$Package) expect_true("skeleton" %in% ip$Package) }) test_that("init(bare = TRUE) initializes a project without packages", { renv_tests_scope("brunch") init(bare = TRUE) files <- list.files(renv_paths_library()) expect_length(files, 0) }) test_that("init succeeds even if there are parse errors in project", { renv_tests_scope() writeLines("oh no", con = "analysis.R") init() expect_true(file.exists("renv.lock")) }) test_that("init() works in path containing accented characters", { # ensure the project path can be represented in native encoding project <- enc2utf8("pr\u{00f8}ject") roundtrip <- tryCatch( enc2utf8(enc2native(project)), condition = identity ) if (!identical(project, roundtrip)) skip("project cannot be represented in native encoding") # TODO(Kevin): Windows tests on CI had the following NOTE # # Found the following files/directories: # 'RtmpKIUpGHprøject' # # so we should run this down. project <- paste(tempdir(), enc2native(project), sep = "/") renv_tests_scope(project = project) defer(unlink(project, recursive = TRUE)) init() install("toast") expect_true(renv_package_installed("bread")) expect_true(renv_package_installed("toast")) snapshot(library = paths$library(), type = "all") lockfile <- renv_lockfile_load(project = getwd()) expect_true(!is.null(lockfile$Packages$bread)) expect_true(!is.null(lockfile$Packages$toast)) }) test_that("we use an external library path for package projects", { skip_on_cran() renv_tests_scope() # use custom userdir userdir <- renv_scope_tempfile("renv-userdir-override") ensure_directory(userdir) userdir <- renv_path_normalize(userdir, mustWork = TRUE) renv_scope_options(renv.userdir.override = userdir) # sanity check userdir <- renv_bootstrap_user_dir() if (!identical(expect_true(renv_path_within(userdir, tempdir())), TRUE)) skip("userdir not in tempdir; cannot proceed") # initialize a package project writeLines("Type: Package", con = "DESCRIPTION") init() # check for external library path library <- renv_libpaths_active() expect_true( object = renv_path_within(library, userdir), info = sprintf("- %s\n- %s\n", library, userdir) ) }) test_that("a project with unnamed repositories can be initialized", { skip_on_cran() renv_tests_scope() repos <- c( CRAN = "https://cran.rstudio.com", "https://cloud.r-project.org" ) renv_scope_options(repos = repos) init() lockfile <- renv_lockfile_read("renv.lock") repos <- lockfile[["R"]][["Repositories"]] expect_equal( repos, list( CRAN = "https://cran.rstudio.com", "https://cloud.r-project.org" = "https://cloud.r-project.org" ) ) }) test_that("RENV_PATHS_RENV is respected on init", { skip_on_cran() renv_tests_scope() unlink("renv", recursive = TRUE) renv_scope_envvars( RENV_PATHS_LOCKFILE = ".renv/renv.lock", RENV_PATHS_RENV = ".renv", # don't execute user profile R_PROFILE_USER = "" ) # perform init in sub-process args <- c("-s", "-e", renv_shell_quote("renv::init()")) renv_system_exec(R(), args, action = "executing init()") # check that the requisite files were created expect_true(file.exists(".renv")) expect_true(file.exists(".renv/renv.lock")) script <- renv_test_code({ writeLines(Sys.getenv("RENV_PATHS_RENV")) }) renv_scope_envvars(R_PROFILE_USER = NULL, RENV_PROJECT = NULL) args <- c("-s", "-f", script) renv <- renv_system_exec(R(), args, action = "reading RENV_PATHS_RENV") expect_equal(tail(renv, n = 1L), ".renv") }) test_that("init() uses PPM by default", { skip_on_cran() # simulate "fresh" R session with unset repositories renv_scope_options(repos = c(CRAN = "@CRAN@")) repos <- renv_init_repos() expect_equal(repos[["CRAN"]], "https://packagemanager.posit.co/cran/latest") }) test_that("init() prompts the user for the snapshot type", { skip_on_cran() project <- renv_tests_scope("bread") writeLines("Depends: bread", con = "DESCRIPTION") expect_snapshot(init()) expect_true(renv_package_installed("bread")) }) test_that("a project can be initialized without loading it", { skip_on_cran() project <- renv_tests_scope() init() expect_equal(renv_project_get(), project) oldwd <- getwd() oldrepos <- getOption("repos") oldlibs <- .libPaths() other <- renv_scope_tempfile("renv-project-") ensure_directory(other) init(project = other, load = FALSE, restart = FALSE) expect_equal(renv_project_get(), project) expect_true(file.exists(file.path(other, "renv.lock"))) expect_equal(oldwd, getwd()) expect_equal(oldrepos, getOption("repos")) expect_equal(oldlibs, .libPaths()) }) test_that("init() respects user-requested snapshot type", { project <- renv_tests_scope() writeLines("Depends: bread", con = "DESCRIPTION") writeLines("library(toast)", con = "deps.R") init() expect_true(renv_package_installed("bread")) expect_false(renv_package_installed("toast")) expect_equal(settings$snapshot.type(), "explicit") }) test_that("init() respects Remotes in a project DESCRIPTION file", { skip_on_cran() skip_if_no_github_auth() project <- renv_tests_scope("skeleton") writeLines("Depends: skeleton\nRemotes: kevinushey/skeleton", con = "DESCRIPTION") init() expect_true(renv_package_installed("skeleton")) }) test_that("a project using named remotes can be initialized", { project <- renv_tests_scope() contents <- heredoc(' Depends: toast Remotes: toast=toast ') writeLines(contents, con = "DESCRIPTION") init(settings = list(snapshot.type = "explicit")) expect_true(renv_package_installed("toast")) }) renv/tests/testthat/test-defer.R0000644000176200001440000000346514731330073016424 0ustar liggesusers test_that("defer evaluates in appropriate environment", { foo <- function() { writeLines("+ foo") defer(writeLines("> foo"), environment()) defer(writeLines("> foo.parent"), parent.frame(1)) defer(writeLines("> foo.parent.parent"), parent.frame(2)) writeLines("- foo") } bar <- function() { writeLines("+ bar") foo() writeLines("- bar") } baz <- function() { writeLines("+ baz") bar() writeLines("- baz") } output <- capture.output(baz()) expected <- c( "+ baz", "+ bar", "+ foo", "- foo", "> foo", "- bar", "> foo.parent", "- baz", "> foo.parent.parent" ) expect_identical(output, expected) }) test_that("defer runs handles in LIFO order", { x <- double() local({ defer(x <<- c(x, 1)) defer(x <<- c(x, 2)) defer(x <<- c(x, 3)) }) expect_equal(x, c(3, 2, 1)) }) test_that("defer captures arguments properly", { foo <- function(x) { defer(writeLines(x), scope = parent.frame()) } bar <- function(y) { writeLines("+ bar") foo(y) writeLines("- bar") } output <- capture.output(bar("> foo")) expected <- c("+ bar", "- bar", "> foo") expect_identical(output, expected) }) test_that("defer works with arbitrary expressions", { foo <- function(x) { defer({ x + 1 writeLines("> foo") }, scope = parent.frame()) } bar <- function() { writeLines("+ bar") foo(1) writeLines("- bar") } output <- capture.output(bar()) expected <- c("+ bar", "- bar", "> foo") expect_identical(output, expected) }) test_that("renv_defer_execute can run handlers earlier", { x <- 1 defer(rm(list = "x")) expect_true(exists("x", inherits = FALSE)) renv_defer_execute(environment()) expect_false(exists("x", inherits = FALSE)) }) renv/tests/testthat/test-autoload.R0000644000176200001440000000233114731330073017136 0ustar liggesusers test_that("autoload() works", { skip_on_cran() # TODO: Failed on Windows CI, but works locally? skip_on_windows() project <- renv_tests_scope() # initialize renv project here init() # make sure we have renv installed for this test libpaths <- the$default_libpaths[[".libPaths()"]] source <- find.package("renv", lib.loc = libpaths) renv_imbue_self(project, source = source) # make sure autoloader is enabled in this scope renv_scope_envvars(RENV_AUTOLOAD_ENABLED = "TRUE") # make sure default library paths are visible to child process renv_scope_envvars( R_LIBS = NULL, R_LIBS_USER = paste(libpaths, collapse = .Platform$path.sep), R_LIBS_SITE = NULL ) # move to sub-directory dir.create("subdir") renv_scope_wd("subdir") # create a .Rprofile that calls renv::autoload() profile <- renv_test_code({ renv::autoload() }) renv_scope_envvars(R_PROFILE_USER = profile) # launch R and see what library paths we got output <- renv_system_exec( command = R(), args = c("-s", "-e", renv_shell_quote("writeLines(.libPaths()[1])")), action = "testing autoload" ) expected <- .libPaths()[1] expect_equal(tail(output, n = length(expected)), expected) }) renv/tests/testthat/test-modify.R0000644000176200001440000000105214731330073016614 0ustar liggesusers test_that("lockfiles can be modified non-interactively", { skip_on_cran() renv_tests_scope() init() modify(changes = list(R = list(Version = "1.0.0"))) lockfile <- renv_lockfile_load(project = getwd()) expect_equal(lockfile$R$Version, "1.0.0") }) test_that("modifications that 'break' the lockfile are not persisted", { skip_on_cran() project <- renv_tests_scope() init() old <- renv_lockfile_load(project) expect_error(modify(changes = list(Packages = 42))) new <- renv_lockfile_load(project) expect_equal(old, new) }) renv/tests/testthat/test-vendor.R0000644000176200001440000000441014740260564016632 0ustar liggesusers test_that("renv itself doesn't mark itself as embedded", { expect_false(renv_metadata_embedded()) expect_equal(renv_metadata_version(), renv_namespace_version("renv")) }) test_that("renv can be vendored into an R package", { skip_on_cran() skip_slow() # create a dummy R package project <- renv_tests_scope() desc <- heredoc(" Type: Package Package: test.renv.embedding Version: 0.1.0 ") writeLines(desc, con = "DESCRIPTION") file.create("NAMESPACE") # vendor renv vendor() # make sure renv is initializes in .onLoad() code <- heredoc(' .onLoad <- function(libname, pkgname) { renv$initialize(libname, pkgname) } ') ensure_directory("R") writeLines(code, con = "R/zzz.R") # try installing the package r_cmd_install("test.renv.embedding", getwd()) # test that we can load the package and initialize renv code <- substitute({ # make sure renv isn't visible on library paths base <- .BaseNamespaceEnv base$.libPaths(path) # load the package, and check that renv realizes it's embedded namespace <- base$asNamespace("test.renv.embedding") embedded <- namespace$renv$renv_metadata_embedded() if (!embedded) stop("internal error: renv is embedded but doesn't realize it") # let parent process know we succeeded writeLines(as.character(embedded)) }, list(path = .libPaths()[1])) script <- renv_scope_tempfile("renv-script-", fileext = ".R") writeLines(deparse(code), con = script) # attempt to run script output <- renv_system_exec(R(), c("--vanilla", "-s", "-f", renv_shell_path(script))) expect_equal(output, "TRUE") # test that we can use the embedded renv to run snapshot code <- substitute({ # make sure renv isn't visible on library paths base <- .BaseNamespaceEnv base$.libPaths(path) # try to list ns <- base$asNamespace("test.renv.embedding") deps <- ns$renv$dependencies() saveRDS(deps, file = "dependencies.rds") }, list(path = .libPaths()[1])) script <- renv_scope_tempfile("renv-script-", fileext = ".R") writeLines(deparse(code), con = script) # attempt to run script output <- renv_system_exec(R(), c("--vanilla", "-s", "-f", renv_shell_path(script)), quiet = FALSE) expect_true(file.exists("dependencies.rds")) }) renv/tests/testthat/_snaps/0000755000176200001440000000000014761163114015515 5ustar liggesusersrenv/tests/testthat/_snaps/preflight.md0000644000176200001440000000062014761160465020027 0ustar liggesusers# renv warns when snapshotting missing dependencies Code snapshot() Output The following required packages are not installed: - oatmeal [required by breakfast] Consider reinstalling these packages before snapshotting the lockfile. Condition Error in `renv_snapshot_validate_report()`: ! aborting snapshot due to pre-flight validation failure renv/tests/testthat/_snaps/lockfile-read.md0000644000176200001440000000042714761160453020546 0ustar liggesusers# renv_lockfile_read gives informative error Code renv_lockfile_read(file) Condition Error: ! Failed to parse 'renv.lock': parse error: premature EOF { (right here) ------^ renv/tests/testthat/_snaps/init.md0000644000176200001440000000157214761160454017013 0ustar liggesusers# init() prompts the user for the snapshot type Code init() Output This project contains a DESCRIPTION file. Which files should renv use for dependency discovery in this project? 1: Use only the DESCRIPTION file. (explicit mode) 2: Use all files in this project. (implicit mode) Selection: 1 - Using 'explicit' snapshot type. Please see `?renv::snapshot` for more details. - Resolving missing dependencies ... # Installing packages --- - Installing bread ... OK [linked from cache in XXs] The following package(s) will be updated in the lockfile: # CRAN --- - bread [* -> 1.0.0] The version of R recorded in the lockfile will be updated: - R [* -> ] - Lockfile written to "/renv.lock". renv/tests/testthat/_snaps/status.md0000644000176200001440000000455314761160505017372 0ustar liggesusers# reports if status not possible Code . <- status() Output This project does not appear to be using renv. Use `renv::init()` to initialize the project. --- Code . <- status() Output This project does not contain a lockfile. Use `renv::snapshot()` to create a lockfile. --- Code . <- status() Output There are no packages installed in the project library. Use `renv::restore()` to install the packages defined in lockfile. # reports when project is synchronised Code . <- status() Output No issues found -- the project is in a consistent state. # reports installation problems with non-installed packages Code . <- status() Output The following package(s) are in an inconsistent state: package installed recorded used bread n n y egg n y y oatmeal n y ? See `?renv::status` for advice on resolving these issues. # reports synchronisation problems with installed packages Code . <- status() Output The following package(s) are in an inconsistent state: package installed recorded used bread y n y egg y y n See `?renv::status` for advice on resolving these issues. # reports version differences Code . <- status() Output The following package(s) are out of sync [lockfile != library]: # CRAN --- - egg [repo: * != CRAN; ver: 2.0.0 != 1.0.0] - oatmeal [repo: * != CRAN; ver: 0.9.0 != 1.0.0] See `?renv::status` for advice on resolving these issues. # status() notifies user if R version does not match Code . <- status() Output The lockfile was generated with R 1.0.0, but you're using R . See `?renv::status` for advice on resolving these issues. # status() notifies user if packages are missing and inconsistent Code . <- status() Output The following package(s) are used in this project, but are not installed: - breakfast The following package(s) are out of sync [lockfile != library]: # CRAN --- - bread [1.0.0 != 0.1.0] See `?renv::status` for advice on resolving these issues. renv/tests/testthat/_snaps/dots.md0000644000176200001440000000030614761160440017006 0ustar liggesusers# renv_dots_check only sets bioconductor from bioc is not already set Code f(bioconductor = FALSE, bioc = TRUE) Condition Error in `f()`: ! unused argument (bioc = TRUE) renv/tests/testthat/_snaps/bootstrap.md0000644000176200001440000001037114761160432020056 0ustar liggesusers# bootstrapping gives informative output when succesful Code bootstrap(version = "0.9.0", library = library) Output # Bootstrapping renv 0.9.0 --- - Downloading renv ... OK - Installing renv ... OK Code bootstrap(version = "1.0.0", library = library) Output # Bootstrapping renv 1.0.0 --- - Downloading renv ... OK - Installing renv ... OK Code bootstrap(version = "1.0.0.1", library = library) Output # Bootstrapping renv 1.0.0.1 --- - Downloading renv ... OK - Installing renv ... OK # bootstrapping gives informative output when download fails Code bootstrap(version = "0.9.0", library = library) Output # Bootstrapping renv 0.9.0 --- - Downloading renv ... FAILED Condition Error in `h()`: ! failed to download: All download methods failed Code bootstrap(version = "1.0.0", library = library) Output # Bootstrapping renv 1.0.0 --- - Downloading renv ... FAILED Condition Error in `h()`: ! failed to download: All download methods failed Code bootstrap(version = "1.0.0.1", library = library) Output # Bootstrapping renv 1.0.0.1 --- - Downloading renv ... FAILED Condition Error in `h()`: ! failed to download: All download methods failed # bootstrapping gives informative output when install fails Code bootstrap(version = "1.0.0.1", library = library) Output # Bootstrapping renv 1.0.0.1 --- - Downloading renv ... OK - Installing renv ... FAILED Condition Error in `h()`: ! failed to install: installation of renv failed =========================== test failure # renv_boostrap_version_validate() gives good warnings Code . <- renv_bootstrap_validate_version(version = "1.2.3", description = list( Version = "2.3.4")) Output renv 2.3.4 was loaded from project library, but this project is configured to use renv 1.2.3. - Use `renv::record("renv@2.3.4")` to record renv 2.3.4 in the lockfile. - Use `renv::restore(packages = "renv")` to install renv 1.2.3 into the project library. Code . <- renv_bootstrap_validate_version(version = "1.2.3-1", description = list( Version = "1.2.3")) Output renv 1.2.3 was loaded from project library, but this project is configured to use renv 1.2.3-1. - Use `renv::record("renv@1.2.3")` to record renv 1.2.3 in the lockfile. - Use `renv::restore(packages = "renv")` to install renv 1.2.3-1 into the project library. Code . <- renv_bootstrap_validate_version(version = structure("1.2.3-1", sha = "22d015905828c3398728a5ff9e381e0433976263"), description = list(Version = "1.2.3-1", RemoteType = "github", RemoteSha = "6b09befaaba3f55e0e2c141cb45c5d247b61ef1e")) Output renv 1.2.3-1 [sha: 6b09bef] was loaded from project library, but this project is configured to use renv 1.2.3-1 [sha: 22d0159]. - Use `renv::record("rstudio/renv@6b09befaaba3f55e0e2c141cb45c5d247b61ef1e")` to record renv 1.2.3-1 [sha: 6b09bef] in the lockfile. - Use `renv::restore(packages = "renv")` to install renv 1.2.3-1 [sha: 22d0159] into the project library. Code . <- renv_bootstrap_validate_version(version = structure("1.2.3-1", sha = "22d015905828c3398728a5ff9e381e0433976263"), description = list(Version = "1.2.3")) Output renv 1.2.3 was loaded from project library, but this project is configured to use renv 1.2.3-1 [sha: 22d0159]. - Use `renv::record("renv@1.2.3")` to record renv 1.2.3 in the lockfile. - Use `renv::restore(packages = "renv")` to install renv 1.2.3-1 [sha: 22d0159] into the project library. # bootstrap version validation handles 'standard' remote types Code . <- renv_bootstrap_validate_version(version = "1.0.0", description = list( Version = "1.0.1", RemoteType = "standard", RemoteSha = "1.0.1")) Output renv 1.0.1 was loaded from project library, but this project is configured to use renv 1.0.0. - Use `renv::record("renv@1.0.1")` to record renv 1.0.1 in the lockfile. - Use `renv::restore(packages = "renv")` to install renv 1.0.0 into the project library. renv/tests/testthat/_snaps/activate.md0000644000176200001440000000172214761160417017644 0ustar liggesusers# activate_prompt behaves as expected Code val <- renv_activate_prompt_impl("snapshot") Output It looks like you've called renv::snapshot() in a project that hasn't been activated yet. How would you like to proceed? 1: Activate the project and use the project library. 2: Do not activate the project and use the current library paths. 3: Cancel and resolve the situation another way. Selection: 2 --- Code val <- renv_activate_prompt_impl("snapshot") Output It looks like you've called renv::snapshot() in a project that hasn't been activated yet. How would you like to proceed? 1: Activate the project and use the project library. 2: Do not activate the project and use the current library paths. 3: Cancel and resolve the situation another way. Selection: 1 - The project is out-of-sync -- use `renv::status()` for details. renv/tests/testthat/_snaps/install.md0000644000176200001440000000462114761160414017510 0ustar liggesusers# renv warns when installing an already-loaded package Code install("bread@0.1.0") Output The following package(s) will be installed: - bread [0.1.0] These packages will be installed into "/". # Installing packages --- - Installing bread ... OK [copied from cache in XXs] Successfully installed 1 package in XXXX seconds. The following loaded package(s) have been updated: - bread Restart your R session to use the new versions. # install has user-friendly output Code install() Output # Downloading packages --- - Downloading breakfast from CRAN ... OK [XXXX bytes in XXs] - Downloading oatmeal from CRAN ... OK [XXXX bytes in XXs] - Downloading toast from CRAN ... OK [XXXX bytes in XXs] - Downloading bread from CRAN ... OK [XXXX bytes in XXs] Successfully downloaded 4 packages in XXXX seconds. The following package(s) will be installed: - bread [1.0.0] - breakfast [1.0.0] - oatmeal [1.0.0] - toast [1.0.0] These packages will be installed into "/". # Installing packages --- - Installing oatmeal ... OK [built from source and cached in XXs] - Installing bread ... OK [built from source and cached in XXs] - Installing toast ... OK [built from source and cached in XXs] - Installing breakfast ... OK [built from source and cached in XXs] Successfully installed 4 packages in XXXX seconds. --- Code install() Output The following package(s) will be installed: - bread [1.0.0] - breakfast [1.0.0] - oatmeal [1.0.0] - toast [1.0.0] These packages will be installed into "/". # Installing packages --- - Installing oatmeal ... OK [copied from cache in XXs] - Installing bread ... OK [copied from cache in XXs] - Installing toast ... OK [copied from cache in XXs] - Installing breakfast ... OK [copied from cache in XXs] Successfully installed 4 packages in XXXX seconds. renv/tests/testthat/_snaps/snapshot.md0000644000176200001440000002532514761160404017704 0ustar liggesusers# snapshot failures are reported Code snapshot() Output The following package(s) are missing their DESCRIPTION files: - oatmeal [/renv/library//oatmeal] These may be left over from a prior, failed installation attempt. Consider removing or reinstalling these packages. The following required packages are not installed: - oatmeal Packages must first be installed before renv can snapshot them. Use `renv::dependencies()` to see where this package is used in your project. What do you want to do? 1: Snapshot, just using the currently installed packages. 2: Install the packages, then snapshot. 3: Cancel, and resolve the situation on your own. Selection: 1 The following package(s) will be updated in the lockfile: # CRAN --- - oatmeal [1.0.0 -> *] - Lockfile written to "/renv.lock". # broken symlinks are reported Code snapshot() Output The following package(s) have broken symlinks into the cache: - oatmeal Use `renv::repair()` to try and reinstall these packages. The following required packages are not installed: - oatmeal Packages must first be installed before renv can snapshot them. Use `renv::dependencies()` to see where this package is used in your project. What do you want to do? 1: Snapshot, just using the currently installed packages. 2: Install the packages, then snapshot. 3: Cancel, and resolve the situation on your own. Selection: 1 The following package(s) will be updated in the lockfile: # CRAN --- - oatmeal [1.0.0 -> *] - Lockfile written to "/renv.lock". # snapshot warns about unsatisfied dependencies Code snapshot() Output The following package(s) have unsatisfied dependencies: - toast requires bread (> 1.0.0), but version 1.0.0 is installed Consider updating the required dependencies as appropriate. Condition Error in `renv_snapshot_validate_report()`: ! aborting snapshot due to pre-flight validation failure # renv reports missing packages in explicit snapshots Code snapshot(type = "explicit") Output The following required packages are not installed: - breakfast Packages must first be installed before renv can snapshot them. If these packages are no longer required, consider removing them from your DESCRIPTION file. What do you want to do? 1: Snapshot, just using the currently installed packages. 2: Install the packages, then snapshot. 3: Cancel, and resolve the situation on your own. Selection: 1 - The lockfile is already up to date. # snapshot() warns when required package is not installed Code snapshot() Output The following required packages are not installed: - breakfast Packages must first be installed before renv can snapshot them. Use `renv::dependencies()` to see where this package is used in your project. What do you want to do? 1: Snapshot, just using the currently installed packages. 2: Install the packages, then snapshot. 3: Cancel, and resolve the situation on your own. Selection: 1 The following package(s) will be updated in the lockfile: # CRAN --- - bread [1.0.0 -> *] - breakfast [1.0.0 -> *] - oatmeal [1.0.0 -> *] - toast [1.0.0 -> *] - Lockfile written to "/renv.lock". --- Code snapshot() Output The following required packages are not installed: - toast [required by breakfast] Consider reinstalling these packages before snapshotting the lockfile. Condition Error in `renv_snapshot_validate_report()`: ! aborting snapshot due to pre-flight validation failure # snapshot always reports on R version changes Code renv_snapshot_report_actions(list(), R4.1, R4.2) Output The version of R recorded in the lockfile will be updated: - R [4.1 -> 4.2] # user can choose to install missing packages Code snapshot() Output The following required packages are not installed: - egg Packages must first be installed before renv can snapshot them. Use `renv::dependencies()` to see where this package is used in your project. What do you want to do? 1: Snapshot, just using the currently installed packages. 2: Install the packages, then snapshot. 3: Cancel, and resolve the situation on your own. Selection: 2 # Downloading packages --- - Downloading egg from CRAN ... OK [XXXX bytes in XXs] Successfully downloaded 1 package in XXXX seconds. The following package(s) will be installed: - egg [1.0.0] These packages will be installed into "/". # Installing packages --- - Installing egg ... OK [built from source and cached in XXs] Successfully installed 1 package in XXXX seconds. The following package(s) will be updated in the lockfile: # CRAN --- - egg [* -> 1.0.0] The version of R recorded in the lockfile will be updated: - R [* -> ] - Lockfile written to "/renv.lock". # automatic snapshot works as expected Code renv_snapshot_task() Output - Automatic snapshot has updated '/renv.lock'. # we report if dependency discover during snapshot() is slow Code . <- snapshot() Output NOTE: Dependency discovery took XXXX seconds during snapshot. Consider using .renvignore to ignore files, or switching to explicit snapshots. See `?renv::dependencies` for more information. - The lockfile is already up to date. # failures in automatic snapshots disable automatic snapshots Code renv_snapshot_task() Output Error generating automatic snapshot: simulated failure in snapshot task Automatic snapshots will be disabled. Use `renv::snapshot()` to manually update the lockfile. # snapshot() reports missing packages even if renv.verbose is FALSE Code . <- snapshot(force = TRUE) Output The following required packages are not installed: - bread Packages must first be installed before renv can snapshot them. Use `renv::dependencies()` to see where this package is used in your project. What do you want to do? 1: Snapshot, just using the currently installed packages. 2: Install the packages, then snapshot. 3: Cancel, and resolve the situation on your own. Selection: 1 - The lockfile is already up to date. # lockfiles are stable (v1) Code . <- writeLines(readLines("renv.lock")) Output { "R": { "Version": "", "Repositories": [ { "Name": "CRAN", "URL": "" } ] }, "Packages": { "bread": { "Package": "bread", "Version": "1.0.0", "Source": "Repository", "Repository": "CRAN", "Hash": "3d2aa8db4086921058b23ce646e01c7a" }, "breakfast": { "Package": "breakfast", "Version": "1.0.0", "Source": "Repository", "Repository": "CRAN", "Requirements": [ "oatmeal", "toast" ], "Hash": "0fcd2a795901b4b21326a3e35442c97c" }, "oatmeal": { "Package": "oatmeal", "Version": "1.0.0", "Source": "Repository", "Repository": "CRAN", "Hash": "1997110c04a1a14551dc791abb7cf8cf" }, "toast": { "Package": "toast", "Version": "1.0.0", "Source": "Repository", "Repository": "CRAN", "Requirements": [ "bread" ], "Hash": "d2f51ee89552a4668cbe9fc25b1f7c1e" } } } # lockfiles are stable (v2) Code . <- writeLines(readLines("renv.lock")) Output { "R": { "Version": "", "Repositories": [ { "Name": "CRAN", "URL": "" } ] }, "Packages": { "bread": { "Package": "bread", "Version": "1.0.0", "Source": "Repository", "Type": "Package", "Repository": "CRAN", "License": "GPL", "Description": "renv test package", "Title": "renv test package", "Author": "Anonymous Person ", "Maintainer": "Anonymous Person ", "Config/Needs/protein": "egg" }, "breakfast": { "Package": "breakfast", "Version": "1.0.0", "Source": "Repository", "Type": "Package", "Depends": [ "oatmeal", "toast (>= 1.0.0)" ], "Suggests": [ "egg" ], "Repository": "CRAN", "License": "GPL", "Description": "renv test package", "Title": "renv test package", "Author": "Anonymous Person ", "Maintainer": "Anonymous Person ", "Config/Needs/protein": "egg" }, "oatmeal": { "Package": "oatmeal", "Version": "1.0.0", "Source": "Repository", "Type": "Package", "Repository": "CRAN", "License": "GPL", "Description": "renv test package", "Title": "renv test package", "Author": "Anonymous Person ", "Maintainer": "Anonymous Person " }, "toast": { "Package": "toast", "Version": "1.0.0", "Source": "Repository", "Depends": [ "bread" ], "Type": "Package", "Repository": "CRAN", "License": "GPL", "Description": "renv test package", "Title": "renv test package", "Author": "Anonymous Person ", "Maintainer": "Anonymous Person " } } } renv/tests/testthat/_snaps/repair.md0000644000176200001440000000142514761160505017324 0ustar liggesusers# repair() can update DESCRIPTION files for GitHub packages Code . <- repair() Output # Library cache links --- - No issues found with the project library's cache links. # Package sources --- The following package(s) do not have an explicitly-declared remote source. However, renv was available to infer remote sources from their DESCRIPTION file. - skeleton [kevinushey/skeleton] `renv::restore()` may fail for packages without an explicitly-declared remote source. What would you like to do? 1: Let renv infer the remote sources for these packages. 2: Do nothing and resolve the situation another way. Selection: 2 Condition Error: ! Operation canceled renv/tests/testthat/_snaps/dependencies.md0000644000176200001440000000317114761160435020472 0ustar liggesusers# renv warns when large number of files found in total Code . <- dependencies() Output A large number of files (7 in total) have been discovered. It may take renv a long time to scan these files for dependencies. Consider using .renvignore to ignore irrelevant files. See `?renv::dependencies` for more information. Set `options(renv.config.dependencies.limit = Inf)` to disable this warning. Finding R package dependencies ... Done! # renv warns when large number of files found in one directory Code . <- dependencies() Output A large number of files (11 in total) have been discovered. It may take renv a long time to scan these files for dependencies. Consider using .renvignore to ignore irrelevant files. See `?renv::dependencies` for more information. Set `options(renv.config.dependencies.limit = Inf)` to disable this warning. Finding R package dependencies ... Done! WARNING: One or more problems were discovered while enumerating dependencies. # --- Error: directory contains 12 files; consider ignoring this directory Please see `?renv::dependencies` for more information. # dependencies() notifies the user if directories contain lots of files Code . <- renv_snapshot_dependencies(project) Output NOTE: Dependency discovery took XXXX seconds during snapshot. Consider using .renvignore to ignore files, or switching to explicit snapshots. See `?renv::dependencies` for more information. - /data: 200 files renv/tests/testthat/_snaps/caution.md0000644000176200001440000000050014761163114017474 0ustar liggesusers# bulletin() creates bulleted list with optional postamble Code bulletin("preamble", letters[1:3]) Output preamble - a - b - c Code bulletin("preamble", letters[1:3], postamble = "after") Output preamble - a - b - c after renv/tests/testthat/_snaps/load.md0000644000176200001440000000170214761160444016761 0ustar liggesusers# load() installs packages if needed Code load() Output - Project '' loaded. [renv ] - None of the packages recorded in the lockfile are currently installed. The following package(s) will be updated: # CRAN --- - bread [* -> 1.0.0] - breakfast [* -> 1.0.0] - oatmeal [* -> 1.0.0] - toast [* -> 1.0.0] # Installing packages --- - Installing bread ... OK [linked from cache in XXs] - Installing oatmeal ... OK [linked from cache in XXs] - Installing toast ... OK [linked from cache in XXs] - Installing breakfast ... OK [linked from cache in XXs] # load() reports on problems Code load() Output - Project '' loaded. [renv ] - The project is out-of-sync -- use `renv::status()` for details. renv/tests/testthat/test-patches.R0000644000176200001440000000025314731330073016756 0ustar liggesusers test_that("a bad TAR is repaired", { renv_scope_envvars(TAR = "/no/such/tar") expect_warning(renv_patch_tar()) expect_false(Sys.getenv("TAR") == "/no/such/tar") }) renv/tests/testthat/test-once.R0000644000176200001440000000027714731330073016261 0ustar liggesusers test_that("once() returns TRUE only once", { method <- function() { expect_true(once()) expect_false(once()) expect_false(once()) } method() expect_error(method()) }) renv/tests/testthat/test-url.R0000644000176200001440000000125714731330073016136 0ustar liggesusers test_that("a variety of URLs can be parsed", { url <- "https://packagemanager.posit.co/cran/latest" parts <- renv_url_parse(url) expect_equal(renv_url_parse(url), list( url = url, protocol = "https://", domain = "packagemanager.posit.co", path = "/cran/latest", parameters = named(list()), fragment = "" )) url <- "https://example.com/path/to/page?name=ferret&color=purple" parts <- renv_url_parse(url) expect_equal(renv_url_parse(url), list( url = url, protocol = "https://", domain = "example.com", path = "/path/to/page", parameters = list( name = "ferret", color = "purple" ), fragment = "" )) }) renv/tests/testthat/test-json.R0000644000176200001440000001034614731330073016304 0ustar liggesusers test_that("sample JSON strings can be read", { expect_identical( renv_json_read(text = '[true, false, null]'), list(TRUE, FALSE, NULL) ) expect_identical( renv_json_read(text = '{"{}": "::"}'), list("{}" = "::") ) expect_identical( renv_json_read(text = '[{"a": 1.0}, {"b": -1e5}]'), list(list(a = 1.0), list(b = -1E5)) ) expect_identical( renv_json_read(text = '[{}, [], {}]'), list(named(list()), list(), named(list())) ) expect_identical( renv_json_read(text = '[{"]": "["}]'), list(list("]" = "[")) ) }) test_that("sample R objects can be converted to JSON", { before <- list(alpha = 1, beta = 2) json <- renv_json_convert(before) after <- renv_json_read(text = json) expect_equal(before, after) }) test_that("empty R lists are converted as expected", { data <- list() json <- renv_json_convert(data) expect_equal(json, "[]") names(data) <- character() json <- renv_json_convert(data) expect_equal(json, "{}") }) test_that("we can parse a GitHub remotes specification", { skip_slow() data <- renv_remotes_resolve("rstudio/renv") expect_true(data$Source == "GitHub") expect_true(data$RemoteUsername == "rstudio") expect_true(data$RemoteRepo == "renv") }) test_that("we can parse a GitHub remotes specification with 'wininet'", { skip_on_cran() skip_on_os("windows") skip_if_not(renv_platform_windows()) renv_scope_envvars(RENV_DOWNLOAD_FILE_METHOD = "wininet") data <- renv_remotes_resolve("rstudio/renv") expect_true(data$Source == "GitHub") expect_true(data$RemoteUsername == "rstudio") expect_true(data$RemoteRepo == "renv") }) test_that("we can read json containing escape characters", { actual <- list(data = "\\\"") json <- renv_json_convert(actual) expected <- renv_json_read(text = json) expect_equal(actual, expected) }) test_that("JSON null is read as R NULL", { json <- "{\"NULL\": null}" actual <- renv_json_read(text = json) expected <- list("NULL" = NULL) expect_equal(actual, expected) }) test_that("some common control characters are escaped", { json <- renv_json_convert("\b\f\n\r\t") expect_equal(json, "\"\\b\\f\\n\\r\\t\"") }) test_that("scalar values are boxed if requested", { json <- renv_json_convert( object = list(A = "hello", B = "world"), config = list(box = "B") ) value <- renv_json_read(text = json) expect_equal(value, list(A = "hello", B = list("world"))) }) test_that("the renv + jsonlite JSON readers are compatible", { skip_if_not_installed("jsonlite") renv_tests_scope("breakfast") init() lhs <- renv_json_read_default("renv.lock") rhs <- renv_json_read_jsonlite("renv.lock") expect_equal(lhs, rhs) }) test_that("NA values are properly serialized", { expect_equal(renv_json_write(NA, file = NULL), "null") expect_equal(renv_json_write(NA_character_, file = NULL), "null") expect_equal(renv_json_write(NA_real_, file = NULL), "null") expect_equal(renv_json_write(NA_integer_, file = NULL), "null") expect_equal(renv_json_write(NA_complex_, file = NULL), "null") expect_equal(renv_json_write(NaN, file = NULL), "null") }) test_that("we fall back to the internal JSON reader if jsonlite fails", { skip_if_not_installed("jsonlite") json <- renv_json_read(text = "[\"key\": NA]") expect_equal(json, list(key = NA)) }) test_that("json-read.R can function standalone", { renv_tests_scope() renv <- asNamespace("renv") keys <- ls(envir = renv, pattern = "^renv_json_read", all.names = TRUE) vals <- mget(c("%||%", keys), envir = renv) # put those into a separate environment inheriting only from base, and # re-mark those as inheriting from base (so they only 'see' each-other) envir <- new.env(parent = baseenv()) list2env(vals, envir = envir) # for each function, check that it only uses functions from base ok <- list() for (val in vals) { recurse(body(val), function(node) { if (is.call(node) && is.symbol(node[[1L]])) { lhs <- as.character(node[[1L]]) ok[[lhs]] <<- exists(lhs, envir = envir) } }) } # convert to logical vector ok <- convert(ok, "logical") bad <- names(ok)[!ok] if (length(bad) != 0L) { fmt <- "json-read.R is not standalone: %s" stopf(fmt, paste(bad, collapse = ", ")) } }) renv/tests/testthat/test-truthy.R0000644000176200001440000000050514731330073016666 0ustar liggesusers test_that("truthy() behaves as expected", { expect_true(truthy(TRUE)) expect_true(truthy("TRUE")) expect_true(truthy("true")) expect_true(truthy(1)) expect_false(truthy(0)) expect_true(truthy(NULL, default = TRUE)) expect_true(truthy(NA, default = TRUE)) expect_true(truthy(numeric(), default = TRUE)) }) renv/tests/testthat/helper-setup.R0000644000176200001440000001622014753207671017003 0ustar liggesusers # Code that needs to run once, before a suite of tests is run. # Here, "suite of tests" might also mean "a single test" interactively. renv_tests_setup <- function(scope = parent.frame()) { # only run if interactive, or if testing ok <- interactive() || testthat::is_testing() if (!ok) return() # make sure this only runs once if (!once()) return() # remove automatic tasks so we can capture explicitly in tests renv_task_unload() # cache path before working directory gets changed renv_tests_root() # make sure required packages are loaded # (not scoped to the environment since packages can't reliably be unloaded) renv_tests_setup_packages() # fix up the library paths if needed for testing renv_tests_setup_libpaths(scope = scope) # make sure we clean up sandbox on exit renv_tests_setup_sandbox(scope = scope) # initialize test repositories renv_tests_setup_repos(scope = scope) # scope relevant environment variables renv_tests_setup_envvars(scope = scope) renv_tests_setup_options(scope = scope) } renv_tests_setup_envvars <- function(scope = parent.frame()) { # set up root directory root <- renv_scope_tempfile("renv-root-", scope = scope) ensure_directory(root) # set up sandbox directory sandbox <- file.path(root, "sandbox") ensure_directory(sandbox) renv_scope_envvars( RENV_AUTOLOAD_ENABLED = FALSE, RENV_CONFIG_LOCKING_ENABLED = FALSE, RENV_DOWNLOAD_METHOD = NULL, RENV_PATHS_ROOT = root, RENV_PATHS_LIBRARY = NULL, RENV_PATHS_LIBRARY_ROOT = NULL, RENV_PATHS_LOCAL = NULL, RENV_PATHS_LOCKFILE = NULL, RENV_PATHS_RENV = NULL, RENV_PATHS_SANDBOX = sandbox, RENV_WATCHDOG_ENABLED = FALSE, RENV_WATCHDOG_DEBUG = FALSE, scope = scope ) envvars <- Sys.getenv() configvars <- grep("^RENV_CONFIG_", names(envvars), value = TRUE) renv_scope_envvars( list = rep_named(configvars, list(NULL)), scope = scope ) } renv_tests_setup_options <- function(scope = parent.frame()) { renv_scope_options( renv.bootstrap.quiet = TRUE, renv.config.user.library = FALSE, renv.config.sandbox.enabled = TRUE, renv.consent = TRUE, restart = NULL, renv.config.install.transactional = FALSE, renv.tests.running = TRUE, scope = scope ) } # Force loading of packages from current .libPaths(); needed for packages # that would otherwise loaded in a renv_tests_scope() renv_tests_setup_packages <- function() { # load recursive dependencies of testthat deps <- renv_package_dependencies("testthat") for (dep in names(deps)) requireNamespace(dep, quietly = TRUE) # also load remotes requireNamespace("remotes", quietly = TRUE) # pak needs a little special handling if (renv_package_installed("pak")) { # set environment variables that influence pak usr <- file.path(tempdir(), "usr-cache") ensure_directory(file.path(usr, "R/renv")) pkg <- file.path(tempdir(), "pkg-cache") ensure_directory(pkg) renv_scope_envvars( R_USER_CACHE_DIR = usr, R_PKG_CACHE_DIR = pkg ) # load pak now requireNamespace("pak", quietly = TRUE) # trigger package load in pak subprocess # # TODO(Kevin): This fails for me with: # # Error in `source_file()`: # ! In path: "/Users/kevin/r/pkg/renv/tests/testthat/helper-zzz.R" # Caused by error in `pak$remote()`: # ! Subprocess is busy or cannot start tryCatch({ pak <- renv_namespace_load("pak") pak$remote(function() {}) }, error = function(e) { options(renv.pak.enabled = FALSE) }) } } renv_tests_setup_libpaths <- function(scope = parent.frame()) { # remove the sandbox from the library paths, just in case we tried # to run tests from an R session where the sandbox was active old <- .libPaths() new <- grep("renv/sandbox", old, fixed = TRUE, invert = TRUE, value = TRUE) renv_scope_libpaths(new, scope = scope) } renv_tests_setup_sandbox <- function(scope = parent.frame()) { renv_scope_options(renv.sandbox.locking_enabled = FALSE) defer(renv_sandbox_unlock(), scope = scope) } renv_tests_setup_repos <- function(scope = parent.frame()) { # use internal tar implementations here; on Windows, external is too slow # note the environment variable names are not case sensitive on Windows if (renv_platform_windows()) { renv_scope_envvars(TAR = "internal") } else { renv_scope_envvars(TAR = "internal", tar = "internal") } # also prefer using internal R copy method renv_scope_options(renv.config.copy.method = "r") # generate our dummy repository repopath <- renv_tests_repopath() if (file.exists(repopath)) return() # create repository source directory contrib <- file.path(repopath, "src/contrib") ensure_directory(contrib) # copy package stuff to tempdir (because we'll mutate them a bit) source <- renv_tests_path("packages") target <- renv_scope_tempfile("renv-packages-", scope = scope) renv_file_copy(source, target) renv_scope_wd(target) # update the local packrat package version to match what's available version <- tryCatch( renv_package_version("packrat"), error = function(cnd) "0.9.2" ) dcf <- renv_dcf_read(file = "packrat/DESCRIPTION") dcf$Version <- version renv_dcf_write(dcf, file = "packrat/DESCRIPTION") # helper function for 'uploading' a package to our test repo upload <- function(path, root, subdir = FALSE) { # create package tarball desc <- renv_description_read(path) package <- basename(path) tarball <- sprintf("%s_%s.tar.gz", package, desc$Version) tar(tarball, package, compression = "gzip") # copy into repository tree components <- c(root, if (subdir) package, tarball) target <- paste(components, collapse = "/") ensure_parent_directory(target) renv_file_move(tarball, target, overwrite = TRUE) } # just in case? renv_scope_options(renv.config.filebacked.cache = FALSE) # copy in packages paths <- list.files(getwd(), full.names = TRUE) subdirs <- file.path(getRversion(), "Recommended") for (path in paths) { # upload the 'regular' package upload(path, contrib, subdir = FALSE) # upload a subdir (mocking what R does during upgrades) upload(path, file.path(contrib, subdirs), subdir = FALSE) # generate an 'old' version of the packages descpath <- file.path(path, "DESCRIPTION") desc <- renv_description_read(descpath) desc$Version <- "0.0.1" write.dcf(desc, file = descpath) # place packages at top level (simulating packages with multiple # versions at the top level of the repository) upload(path, contrib, subdir = FALSE) # generate an 'old' version of the packages descpath <- file.path(path, "DESCRIPTION") desc <- renv_description_read(descpath) desc$Version <- "0.1.0" desc$Depends <- gsub("99.99.99", "1.0.0", desc$Depends %||% "", fixed = TRUE) write.dcf(desc, file = descpath) # place these packages into the archive upload(path, file.path(contrib, "Archive"), subdir = TRUE) } # update PACKAGES metadata tools::write_PACKAGES( dir = contrib, subdirs = subdirs, type = "source", latestOnly = FALSE ) # return path to on-disk repository repopath } renv/tests/testthat/test-hash.R0000644000176200001440000000143014731330073016250 0ustar liggesusers test_that("whitespace does not affect hash", { descpath <- renv_tests_path("packages/breakfast/DESCRIPTION") contents <- readLines(descpath) hash <- renv_hash_description(descpath) # NOTE: this also implies that the hash should be stable # across different versions of R expect_identical(hash, "0fcd2a795901b4b21326a3e35442c97c") renv_scope_tempdir() descpath <- file.path(getwd(), "DESCRIPTION") for (whitespace in c(" ", "\t")) { writeLines(paste(contents, whitespace), descpath) expect_identical(renv_hash_description(descpath), hash) } }) test_that("hash outputs do not change over time", { descpath <- file.path(getwd(), "resources/DESCRIPTION") hash <- renv_hash_description(descpath) expect_equal(hash, "2edf28b7db72297da02d913babfc1ef3") }) renv/tests/testthat/test-recurse.R0000644000176200001440000000214414731330073017000 0ustar liggesusers test_that("recurse() can handle missing objects", { data <- substitute(list(a = A), list(A = quote(expr = ))) expect_no_error(recurse(data, function(node) force(node))) }) test_that("recurse() can handle lists", { data <- list( list(a = 1, b = 2), list( list(c = 3, d = list(4)) ) ) items <- list() recurse(data, function(el) { if (is.numeric(el)) items[[length(items) + 1L]] <<- el }) expect_equal(items, list(1, 2, 3, 4)) items <- list() recurse(data, function(el, ignored) { if (is.numeric(el)) items[[length(items) + 1L]] <<- el }, ignored = 42) expect_equal(items, list(1, 2, 3, 4)) }) test_that("recurse() can handle dots", { counter <- 0L recurse(list(1, list(2, list(3, list(4, list(5))))), function(node) { if (is.list(node)) counter <<- counter + 1L }) expect_equal(counter, 5L) counter <- 0L recurse(list(1, list(2, list(3, list(4, list(5))))), function(node, extra) { expect_equal(extra, 42) if (is.list(node)) counter <<- counter + 1L }, extra = 42) expect_equal(counter, 5L) }) renv/tests/testthat/test-paths.R0000644000176200001440000000423714731330073016454 0ustar liggesusers test_that("all renv paths live within tempdir() during tests", { renv_tests_scope() info <- c(root = renv_paths_root(), tempdir = tempdir()) expect_true(renv_path_within(renv_paths_root(), tempdir()), info = info) }) test_that("the cache path can be set through an environment variable", { cachepath <- renv_scope_tempfile("renv-cache-") renv_scope_envvars(RENV_PATHS_CACHE = cachepath) expect_true(startsWith(renv_paths_cache(), cachepath)) }) test_that("we can construct paths to multiple files with path APIs", { root <- renv_paths_root() files <- renv_paths_root(c("A", "B", "C"), c("a", "b", "c")) expected <- file.path(root, c("A/a", "B/b", "C/c")) expect_equal(files, expected) }) test_that("RENV_PATHS_PREFIX is respected", { os <- "ubuntu-bionic" renv_scope_envvars(RENV_PATHS_PREFIX = os) cachepath <- renv_paths_cache() expect_true(grepl(os, cachepath, fixed = TRUE)) libpath <- renv_paths_library() expect_true(grepl(os, libpath, fixed = TRUE)) prefix <- renv_platform_prefix() expect_true(startsWith(prefix, os)) }) test_that("renv_path_normalize is correctly initialized", { expect_identical( renv_path_normalize, if (renv_platform_windows()) renv_path_normalize_win32 else renv_path_normalize_unix ) }) test_that("UTF-8 paths can be normalized", { skip_if(getRversion() < "4.0.0") info <- l10n_info() if (!identical(info$`UTF-8`, TRUE)) skip("locale is not UTF-8") name <- enc2utf8("\u4f60\u597d") # nihao root <- renv_path_normalize(tempdir()) path <- paste(root, name, sep = "/") defer(unlink(path)) expect_true(file.create(path)) expected <- path actual <- renv_path_normalize(path) expect_equal(expected, actual) }) test_that("our fallback R_user_dir() implementation is compatible", { skip_if(getRversion() < "4.0.0") actual <- renv_paths_root_default_impl_v2_fallback() expected <- tools::R_user_dir("renv", which = "cache") expect_true(renv_path_same(actual, expected)) }) test_that("path environment variables can use ';' as separator", { renv_scope_envvars(RENV_PATHS_LOCAL = "a;b;c") paths <- renv_paths_local() expect_equal(paths, c("a", "b", "c")) }) renv/tests/testthat/test-status.R0000644000176200001440000000322214731330073016651 0ustar liggesuserstest_that("reports if status not possible", { renv_tests_scope() expect_snapshot(. <- status()) init(bare = TRUE) expect_snapshot(. <- status()) snapshot() unlink("renv/library", recursive = TRUE) expect_snapshot(. <- status()) }) test_that("reports when project is synchronised", { renv_tests_scope() init() expect_snapshot(. <- status()) }) test_that("reports installation problems with non-installed packages", { renv_tests_scope() init() writeLines(c("library(egg)", "library(bread)"), con = "script.R") record("egg") record("oatmeal") expect_snapshot(. <- status()) }) test_that("reports synchronisation problems with installed packages", { renv_tests_scope() init() install(c("egg", "bread")) writeLines("library(bread)", con = "script.R") record("egg") expect_snapshot(. <- status()) }) test_that("reports version differences", { renv_tests_scope(c("egg", "oatmeal")) init() record("egg@2.0.0") record("oatmeal@0.9.0") expect_snapshot(. <- status()) }) test_that("status() notifies user if R version does not match", { project <- renv_tests_scope() init() # simulate a different version of R in lockfile local({ renv_scope_options(renv.verbose = FALSE) lockfile <- renv_lockfile_read(file = "renv.lock") lockfile$R$Version <- "1.0.0" renv_lockfile_write(lockfile, file = "renv.lock") }) expect_snapshot(. <- status()) }) test_that("status() notifies user if packages are missing and inconsistent", { project <- renv_tests_scope("bread") init() writeLines("library(breakfast)", con = "_deps.R") install("bread@0.1.0") expect_snapshot(. <- status()) }) renv/tests/testthat/test-cache.R0000644000176200001440000001762414731330073016404 0ustar liggesusers test_that("issues within the cache are reported", { skip_on_cran() # use a temporary cache for this test as we're going # to mutate and invalidate it tempcache <- renv_scope_tempfile("renv-tempcache-") ensure_directory(tempcache) renv_scope_envvars(RENV_PATHS_CACHE = tempcache) # initialize project renv_tests_scope("breakfast") init() # find packages in the cache cache <- renv_cache_list() # diagnostics for missing DESCRIPTION bread <- renv_cache_list(packages = "bread") descpath <- file.path(bread, "DESCRIPTION") unlink(descpath) if (length(descpath) > 1L) { writeLines(descpath) stop("unexpected descpath") } # diagnostics for bad hash breakfast <- renv_cache_list(packages = "breakfast") descpath <- file.path(breakfast, "DESCRIPTION") if (length(descpath) > 1L) { writeLines(descpath) stop("unexpected descpath") } desc <- renv_description_read(descpath) desc$Version <- "2.0.0" renv_dcf_write(desc, file = descpath) # check problems explicitly problems <- renv_cache_diagnose(verbose = FALSE) expect_true(nrow(problems) == 2) }) test_that("use.cache project setting is honored", { skip_on_os("windows") renv_tests_scope("breakfast") init() packages <- list.files(renv_paths_library(), full.names = TRUE) types <- renv_file_type(packages) expect_true(all(types == "symlink")) settings$use.cache(FALSE) packages <- list.files(renv_paths_library(), full.names = TRUE) types <- renv_file_type(packages) expect_true(all(types == "directory")) settings$use.cache(TRUE) packages <- list.files(renv_paths_library(), full.names = TRUE) types <- renv_file_type(packages) expect_true(all(types == "symlink")) }) test_that("package installation does not fail with non-writable cache", { skip_on_os("windows") renv_tests_scope() cache <- renv_scope_tempfile("renv-cache-") dir.create(cache, mode = "0555") renv_scope_envvars(RENV_PATHS_CACHE = cache) init() records <- install("bread") expect_true(records$bread$Package == "bread") location <- find.package("bread") type <- renv_file_type(location) expect_false(type == "symlink") }) test_that("the cache is used even if RENV_PATHS_LIBRARY is non-canonical", { skip_on_os("windows") libpath <- renv_scope_tempfile("renv-library") ensure_directory(libpath) renv_scope_envvars(RENV_PATHS_LIBRARY = file.path(libpath, ".")) renv_tests_scope("bread") init() remove("bread") restore() bread <- system.file(package = "bread") expect_true(renv_file_type(bread) == "symlink") }) test_that("malformed folders in the cache are ignored", { skip_on_cran() renv_tests_scope() cachepath <- renv_scope_tempfile("renv-cache-") renv_scope_envvars(RENV_PATHS_CACHE = cachepath) badpath <- renv_paths_cache("a-b/c-d/e-f/g-h/i-j") dir.create(dirname(badpath), recursive = TRUE) file.create(badpath) paths <- list.files(renv_paths_cache(), recursive = TRUE) expect_length(paths, 1) paths <- renv_cache_list() expect_length(paths, 0) }) test_that("corrupt Meta/package.rds is detected", { skip_on_cran() renv_tests_scope() cachepath <- renv_scope_tempfile("renv-cache-") renv_scope_envvars(RENV_PATHS_CACHE = cachepath) init() install("bread") path <- renv_cache_find(list(Package = "bread", Version = "1.0.0")) expect_true(nzchar(path) && file.exists(path)) metapath <- file.path(path, "Meta/package.rds") expect_true(file.exists(metapath)) writeLines("whoops!", con = file.path(path, "Meta/package.rds")) diagnostics <- renv_cache_diagnose(verbose = FALSE) expect_true(is.data.frame(diagnostics)) expect_true(nrow(diagnostics) == 1) expect_true(diagnostics$Package == "bread") expect_true(diagnostics$Version == "1.0.0") }) test_that("invalid Built field is detected", { skip_on_cran() renv_tests_scope() cachepath <- renv_scope_tempfile("renv-cache-") renv_scope_envvars(RENV_PATHS_CACHE = cachepath) init() install("bread") path <- renv_cache_find(list(Package = "bread", Version = "1.0.0")) expect_true(nzchar(path) && file.exists(path)) descpath <- file.path(path, "DESCRIPTION") contents <- readLines(descpath) old <- paste("R", getRversion()) new <- paste("R 1.0.0") replaced <- gsub(old, new, contents) writeLines(replaced, con = descpath) diagnostics <- renv_cache_diagnose(verbose = FALSE) expect_true(is.data.frame(diagnostics)) expect_true(nrow(diagnostics) == 1) expect_true(diagnostics$Package == "bread") expect_true(diagnostics$Version == "1.0.0") }) test_that("ACLs set on packages in project library are reset", { skip_on_cran() skip_if(!renv_platform_linux()) # use a custom tracer to set ACLs on a package after it's been installed trace("renv_install_package_impl", exit = quote({ system(paste("setfacl -m g::-", renv_shell_path(installpath))) }), where = renv_envir_self(), print = FALSE) defer(untrace("renv_install_package_impl", where = renv_envir_self())) # use a custom cache cachedir <- renv_scope_tempfile("renv-cache-") ensure_directory(cachedir) renv_scope_envvars(RENV_PATHS_CACHE = cachedir) # initialize project with bread; don't try to reset ACLs local({ renv_scope_envvars(RENV_CACHE_ACLS = "FALSE") renv_tests_scope("bread") init() # check that the ACLs were not reset pkgpath <- find.package("bread") mode <- file.mode(pkgpath) expect_false(file.mode(pkgpath) == file.mode(dirname(pkgpath))) }) # try again, but reset ACLs this time local({ renv_scope_envvars(RENV_CACHE_ACLS = "TRUE") renv_tests_scope("toast") init() # check that the ACLs were reset this time pkgpath <- find.package("toast") mode <- file.mode(pkgpath) expect_true(file.mode(pkgpath) == file.mode(dirname(pkgpath))) }) }) # test_that("multiple cache directories are used", { # skip_on_cran() # skip_on_os("windows") # setting folder permissions is a bit more complex on windows # # chmod <- function(path, mode = c("read", "read+write")) { # mode <- match.arg(mode) # path <- normalizePath(path) # stopifnot(file.exists(path)) # # '5' (read and execute) is the minimum permission that will work. # # With '4' (read only) things like dir() don't work anymore. # numbers <- if (mode == "read") "555" else "777" # system(sprintf("chmod %s %s", numbers, path)) # } # # # use multiple temporary caches for this test # tempcache1 <- tempfile("renv-tempcache-") # ensure_directory(tempcache1) # tempcache2 <- tempfile("renv-tempcache-") # ensure_directory(tempcache2) # # defer({ # unlink(tempcache1, recursive = TRUE) # unlink(tempcache2, recursive = TRUE) # }) # # # add both packages to the cache # renv_scope_envvars(RENV_PATHS_CACHE = paste(tempcache1, tempcache2, sep = ";")) # # # initialize project # renv_tests_scope() # init() # # # there should be two paths in the cache # expect_length(paths$cache(), 2L) # # # install bread to first cache path # install("bread") # # # test that there is one package (bread) and it is installed in the first cache # cache <- renv_cache_list() # expect_length(cache, 1L) # expect_true(startsWith(cache[basename(cache) == "bread"], tempcache1)) # # # make the first cache read only # chmod(paths$cache()[1L], "read") # # # install oatmeal to second cache path # install("oatmeal") # # # test that there are 2 packages and the latest package (oatmeal) is installed in the second cache # cache <- renv_cache_list() # expect_length(cache, 2L) # expect_true(startsWith(cache[basename(cache) == "oatmeal"], tempcache2)) # # # make the first cache read+write again, should now install into the first cache again # chmod(paths$cache()[1L], "read+write") # # install("toast") # # # test that there are 3 packages and the latest package (toast) is installed in the first cache # cache <- renv_cache_list() # expect_length(cache, 3L) # expect_true(startsWith(cache[basename(cache) == "toast"], tempcache1)) # # }) renv/tests/testthat/test-config.R0000644000176200001440000000675514737275011016617 0ustar liggesusers test_that("config variables read from appropriate scope", { local({ renv_scope_options(renv.config.test = "hello") expect_equal(renv_config_get("test"), "hello") }) local({ renv_scope_envvars(RENV_CONFIG_TEST = "hello") expect_equal(renv_config_get("test"), "hello") }) }) test_that("invalid configuration options are reported", { local({ renv_scope_options(renv.config.test = "apple") expect_warning(renv_config_get("test", type = "integer[1]")) }) }) test_that("values are coerced as appropriate", { local({ renv_scope_options(renv.config.test = 1) expect_identical(renv_config_get("test", type = "numeric[1]"), 1) expect_identical(renv_config_get("test", type = "integer[1]"), 1L) expect_identical(renv_config_get("test", type = "logical[1]"), TRUE) }) local({ renv_scope_options(renv.config.test = 0) expect_identical(renv_config_get("test", type = "numeric[1]"), 0) expect_identical(renv_config_get("test", type = "integer[1]"), 0L) expect_identical(renv_config_get("test", type = "logical[1]"), FALSE) }) }) test_that("functions are returned as-is for '*' types", { local({ helper <- function() {} renv_scope_options(renv.config.helper = helper) value <- renv_config_get("helper", type = "*") expect_identical(value, helper) }) }) test_that("we can query options without warnings", { local({ renv_scope_envvars(RENV_CONFIG_EXTERNAL_LIBRARIES = "/tmp") expect_identical(config$external.libraries(), "/tmp") }) }) test_that("invalid configuration options trigger a warning", { local({ renv_scope_options(renv.config.connect.timeout = "oops") expect_warning(config$connect.timeout()) }) }) test_that("we can query the default configuration values without issue", { for (key in names(config)) config[[key]]() expect_true(TRUE) }) test_that("multiple library paths can be set in RENV_CONFIG_EXTERNAL_LIBRARIES", { renv_scope_envvars(RENV_CONFIG_EXTERNAL_LIBRARIES = "/a:/b:/c") libpaths <- config$external.libraries() expect_equal(libpaths, c("/a", "/b", "/c")) }) test_that("cache symlinks are disabled if the cache and project library lie in different volumes", { skip_on_cran() skip_if(renv_platform_unix()) project <- renv_tests_scope() renv_scope_envvars(RENV_PATHS_CACHE = "//network/drive") expect_false(renv_cache_config_symlinks(project = project)) projlib <- renv_paths_library(project = project) renv_scope_envvars(RENV_PATHS_CACHE = dirname(projlib)) expect_true(renv_cache_config_symlinks(project = project)) }) test_that("RENV_CONFIG_EXTERNAL_LIBRARIES is decoded appropriately", { envname <- "RENV_CONFIG_EXTERNAL_LIBRARIES" expect_equal( renv_config_decode_envvar(envname, "/apple"), "/apple" ) expect_equal( renv_config_decode_envvar(envname, "C:/apple"), "C:/apple" ) expect_equal( renv_config_decode_envvar(envname, "C:/apple:C:/banana"), c("C:/apple", "C:/banana") ) expect_equal( renv_config_decode_envvar(envname, "C:/apple;C:/banana"), c("C:/apple", "C:/banana") ) expect_equal( renv_config_decode_envvar(envname, "C:/apple,C:/banana"), c("C:/apple", "C:/banana") ) expect_equal( renv_config_decode_envvar(envname, "/apple:/banana"), c("/apple", "/banana") ) expect_equal( renv_config_decode_envvar(envname, "/apple;/banana"), c("/apple", "/banana") ) expect_equal( renv_config_decode_envvar(envname, "/apple,/banana"), c("/apple", "/banana") ) }) renv/tests/testthat/test-use.R0000644000176200001440000000150014746267032016131 0ustar liggesusers test_that("use() works as intended", { skip_on_cran() renv_tests_scope() init() oldpaths <- .libPaths() use( package = "toast", isolate = FALSE, attach = FALSE, verbose = FALSE, sandbox = FALSE ) newpaths <- .libPaths() expect_true(length(newpaths) == length(oldpaths) + 1) toast <- find.package("toast") expect_true(renv_file_same(dirname(toast), .libPaths()[1])) }) test_that("use(lockfile) works as intended", { skip_on_cran() skip_on_windows() renv_tests_scope("bread") init() renv_scope_libpaths() use(lockfile = "renv.lock", isolate = TRUE, verbose = FALSE, sandbox = FALSE) libpath <- renv_use_libpath() pkgpath <- renv_package_find("bread") expect_equal( renv_path_normalize(pkgpath), renv_path_normalize(file.path(libpath, "bread")) ) }) renv/tests/testthat/test-binding.R0000644000176200001440000000000014731330073016727 0ustar liggesusersrenv/tests/testthat/test-system.R0000644000176200001440000000051414731330073016653 0ustar liggesusers test_that("system errors are reported as expected", { skip_on_cran() skip_on_os("windows") cnd <- catch( renv_system_exec( command = R(), args = c("--vanilla", "-s", "-e", renv_shell_quote("stop('barf')")), quiet = FALSE ) ) expect_s3_class(cnd, "error") expect_true(!is.null(cnd$meta)) }) renv/tests/testthat/helper-snapshot.R0000644000176200001440000000377614740260564017512 0ustar liggesusers expect_snapshot <- function(..., cran = FALSE, error = FALSE, transform = strip_dirs, variant = NULL, cnd_class = FALSE) { renv_scope_options(renv.verbose = TRUE) testthat::expect_snapshot( ..., cran = cran, error = error, transform = transform, variant = variant, cnd_class = cnd_class ) } strip_dirs <- function(x) { # TODO: we might want to map multiple strings to the same # placeholder, so this should probably be flipped? # # note also that order matters for snapshot tests; the least-specific # paths should go at the end of this list prefix <- if (renv_platform_windows()) "^file:///" else "^file://" filters <- list( "" = file.path(R.home("bin"), "R"), "" = renv_paths_cache(), "" = renv_platform_prefix(), "" = getRversion(), "" = getOption("repos")[[1L]], "" = gsub(prefix, "", getOption("repos")[[1L]]), "" = renv_path_normalize(renv_paths_root()), "" = renv_path_aliased(getwd()), "" = renv_path_normalize(getwd()), "" = renv_path_normalize(tempdir()), "" = basename(getwd()) ) # apply filters enumerate(filters, function(target, source) { x <<- gsub(source, target, x, fixed = TRUE) }) # other pattern-based filters here x <- gsub("renv-library-\\w+", "", x) x <- gsub(renv_path_aliased(getwd()), "", x, fixed = TRUE) x <- gsub(renv_path_aliased(tempdir()), "", x, fixed = TRUE) # Standardise the dashes produced by header() x <- gsub("-{3,}\\s*$", "---", x, perl = TRUE) # Standardise version x <- gsub(renv_metadata_version_friendly(), "", x, fixed = TRUE) x } strip_versions <- function(x) { gsub("\\[[0-9.-]*\\]", "[]", x) } renv/tests/testthat/test-packages.R0000644000176200001440000000141314731330073017104 0ustar liggesusers test_that("remote field updates are written to both DESCRIPTION, packages.rds", { url <- renv_tests_path("local/skeleton/skeleton_1.0.1.tar.gz") record <- list( Package = "skeleton", Version = "1.0.1", Source = "local", RemoteUrl = url ) renv_tests_scope() renv_scope_envvars(RENV_PATHS_LOCAL = NULL) install(packages = list(record)) pkgpath <- renv_package_find("skeleton") descpath <- file.path(pkgpath, "DESCRIPTION") desc <- renv_description_read(descpath) metapath <- file.path(pkgpath, "Meta/package.rds") meta <- as.list(readRDS(metapath)$DESCRIPTION) expect_true(desc$RemoteType == "local") expect_true(meta$RemoteType == "local") expect_true(desc$RemoteUrl == url) expect_true(meta$RemoteUrl == url) }) renv/tests/testthat/test-index.R0000644000176200001440000000524514731330073016444 0ustar liggesusers scope <- renv_id_generate() counter <- (function() { .count <- 0L list( increment = function() .count <<- .count + 1L, get = function() .count, reset = function() .count <<- 0L ) })() test_that("an index value is computed only once", { counter$reset() key <- renv_id_generate() index( scope = scope, key = key, value = counter$increment() ) expect_equal(counter$get(), 1L) index( scope = scope, key = key, value = counter$increment() ) expect_equal(counter$get(), 1L) }) test_that("a timeout of 0 forces index to be re-computed", { counter$reset() key <- renv_id_generate() index( scope = scope, key = key, value = counter$increment() ) expect_equal(counter$get(), 1L) index( scope = scope, key = key, value = counter$increment(), limit = 0L ) expect_equal(counter$get(), 2L) }) test_that("other processes can use the index", { path <- renv_scope_tempfile("renv-index-") ensure_directory(path) renv_scope_envvars(RENV_PATHS_INDEX = path) key <- renv_id_generate() index( scope = scope, key = key, value = TRUE ) script <- renv_test_code({ indexed <- renv:::index(scope, key, value) writeLines(as.character(indexed)) }, list(scope = scope, key = key, value = FALSE)) output <- local({ renv_system_exec( command = R(), args = c("--vanilla", "-s", "-f", shQuote(script)), action = "testing renv index", quiet = FALSE ) }) expect_equal(output, "TRUE") }) test_that("a corrupt index is cleared and remade", { counter$reset() key <- renv_id_generate() index( scope = scope, key = key, value = counter$increment() ) path <- renv_paths_index(scope, "index.json") expect_true(file.exists(path)) expect_equal(counter$get(), 1L) writeLines("BARF!", con = path) contents <- readLines(path) expect_true(identical(contents, "BARF!")) index( scope = scope, key = key, value = counter$increment() ) contents <- readLines(path) expect_false(identical(contents, "BARF!")) expect_equal(counter$get(), 2L) }) test_that("the available packages index is updated and cleaned", { scope <- "available-packages" path <- renv_scope_tempfile("renv-index-") ensure_directory(path) renv_scope_envvars(RENV_PATHS_INDEX = path) renv_tests_scope() # request available packages db <- available_packages(type = "source") # check that an index entry was created idx <- index(scope) expect_length(idx, 1L) # check that we have two files in the index path root <- renv_paths_index(scope) files <- list.files(root) expect_length(files, 2L) }) renv/tests/testthat/test-clean.R0000644000176200001440000000133414731330073016412 0ustar liggesusers test_that("clean removes stale lockfiles", { renv_tests_scope("bread") init() library <- renv_paths_library() # old temporary directory tmpdir <- tempfile(tmpdir = library) ensure_directory(tmpdir) # stale lockfile lockpath <- file.path(library, "00LOCK-package") ensure_directory(lockpath) Sys.setFileTime(lockpath, Sys.time() - 36000) # installed but unused package suppressWarnings(install("toast")) # clean up the project actions <- c("package.locks", "library.tempdirs", "unused.packages") clean(actions = actions) # check the project has been cleaned expect_false(file.exists(tmpdir)) expect_false(file.exists(lockpath)) expect_false(file.exists(file.path(library, "toast"))) }) renv/tests/testthat/test-dynamic.R0000644000176200001440000000046414731330073016757 0ustar liggesusers test_that("dynamic variables are cool and good", { a <- 0L envir <- environment() example <- function() { dynamic( key = list(), value = a <<- a + 1L, envir = envir ) } local({ example() expect_equal(a, 1L) example() expect_equal(a, 1L) }) }) renv/tests/testthat/test-call.R0000644000176200001440000000125414731330073016244 0ustar liggesusers test_that("renv_call_matches() works as expected", { call <- quote(foo(1, 2)) expect_true(renv_call_matches(call, "foo")) expect_true(renv_call_matches(call, "foo", nargs = 2L)) expect_false(renv_call_matches(call, "bar")) expect_false(renv_call_matches(call, "bar", nargs = 1L)) expect_true(renv_call_matches(call, names = c("foo", "bar"))) expect_true(renv_call_matches(call, names = c("foo", "bar"), nargs = 2L)) }) test_that("renv_call_expect() works as expected", { node <- quote(R6::R6Class("Class", inherit = "ParentClass")) class <- renv_call_expect(node, "R6", "R6Class") expect_identical(class, quote(R6Class("Class", inherit = "ParentClass"))) }) renv/tests/testthat/test-authentication.R0000644000176200001440000000267714731330073020362 0ustar liggesusers test_that("renv.auth is respected in various contexts", { # using local 'renv.auth.' local({ record <- list(Package = "dplyr") renv_scope_options(renv.auth.dplyr = list(GITHUB_PAT = "")) local({ renv_scope_auth(record = record) expect_true(Sys.getenv("GITHUB_PAT") == "") }) expect_false(Sys.getenv("GITHUB_PAT") == "") }) # using global 'renv.auth' local({ record <- list(Package = "tidyr") renv_scope_options(renv.auth = list(tidyr = list(GITHUB_PAT = ""))) local({ renv_scope_auth(record = record) expect_true(Sys.getenv("GITHUB_PAT") == "") }) expect_false(Sys.getenv("GITHUB_PAT") == "") }) # using an auth function local({ record <- list(Package = "dplyr") renv_scope_options(renv.auth.dplyr = function(record) { list(GITHUB_PAT = "") }) local({ renv_scope_auth(record = record) expect_true(Sys.getenv("GITHUB_PAT") == "") }) expect_false(Sys.getenv("GITHUB_PAT") == "") }) # using an auth function local({ record <- list(Package = "dplyr") renv_scope_options(renv.auth = function(package, record) { if (package == "dplyr") return(list(GITHUB_PAT = "")) }) local({ renv_scope_auth(record = record) expect_true(Sys.getenv("GITHUB_PAT") == "") }) expect_false(Sys.getenv("GITHUB_PAT") == "") }) }) renv/tests/testthat/test-base64.R0000644000176200001440000000116214731330073016413 0ustar liggesusers test_that("some sample strings can be base64-encoded", { expect_equal(renv_base64_encode("renv"), "cmVudg==") original <- rawToChar(as.raw(1:255)) encoded <- renv_base64_encode(original) decoded <- renv_base64_decode(encoded) expect_equal(original, decoded) }) test_that("some random base64 strings can be round-tripped", { set.seed(123) bytes <- 1:255 text <- replicate(1000, { n <- sample(128L, 1L) rawToChar(as.raw(sample(bytes, n, replace = TRUE))) }) encoded <- lapply(text, renv_base64_encode) decoded <- lapply(encoded, renv_base64_decode) expect_true(all(text == decoded)) }) renv/tests/testthat/test-rehash.R0000644000176200001440000000103214731330073016575 0ustar liggesusers test_that("rehash() migrates cached packages as expected", { tempcache <- renv_scope_tempfile("renv-cache-") renv_scope_envvars(RENV_PATHS_CACHE = tempcache) renv_scope_envvars(RENV_CACHE_VERSION = "v4") renv_tests_scope("breakfast") init() cached <- renv_cache_list() expect_length(cached, 4L) expect_match(cached, "/v4/", fixed = TRUE) renv_scope_envvars(RENV_CACHE_VERSION = "v5") rehash(prompt = FALSE) cached <- renv_cache_list() expect_length(cached, 4L) expect_match(cached, "/v5/", fixed = TRUE) }) renv/tests/testthat/test-install.R0000644000176200001440000004756514751445257017033 0ustar liggesusers # TODO: This test assumes 'pak' integration is disabled? test_that("install works when DESCRIPTION contains no dependencies", { renv_tests_scope() desc <- c("Type: Package", "Package: test", "Version: 1.0") writeLines(desc, con = "DESCRIPTION") expect_length(install(), 0L) }) test_that("requested version in DESCRIPTION file is honored", { renv_tests_scope() desc <- c( "Type: Package", "Package: test", "Imports: bread (== 0.1.0), toast" ) writeLines(desc, con = "DESCRIPTION") install() expect_true(renv_package_version("bread") == "0.1.0") }) test_that("installation failure is well-reported", { # TODO: test seems to fail because a connection gets # left open by utils::package.skeleton() skip_on_os("windows") renv_scope_tempdir() # init dummy library library <- renv_scope_tempfile("renv-library-") ensure_directory(library) # dummy environment envir <- new.env(parent = emptyenv()) envir[["hello"]] <- function() {} # prepare dummy package package <- "renv.dummy.package" unlink(package, recursive = TRUE) suppressMessages(utils::package.skeleton(package, environment = envir)) # remove broken man files unlink("renv.dummy.package/Read-and-delete-me") unlink("renv.dummy.package/man", recursive = TRUE) # give the package a build-time error writeLines("parse error", con = file.path(package, "R/error.R")) # try to build it and confirm error record <- list(Package = package, Path = package) expect_error(renv_install_package_impl(record)) }) test_that("install forces update of dependencies as needed", { # TODO: this fails on CRAN presumedly because the wrong # version of the breakfast package is searched for; need # to figure out where the repositories are getting changed. skip_on_cran() renv_tests_scope("breakfast") # install the breakfast package install("breakfast") # ensure its dependencies were installed packages <- c("bread", "oatmeal", "toast") for (package in packages) expect_true(file.exists(renv_package_find(package))) # remove breakfast remove("breakfast") # modify 'toast' so that it's now too old path <- renv_package_find("toast") descpath <- file.path(path, "DESCRIPTION") desc <- renv_description_read(descpath) desc$Version <- "0.1.0" renv_dcf_write(desc, file = descpath) # try to install 'breakfast' again install("breakfast") # validate that 'toast' was updated to 1.0.0 desc <- renv_description_read(package = "toast") expect_equal(desc$Version, "1.0.0") }) test_that("packages can be installed from sources", { renv_tests_scope() init() # get path to package sources in local repos repos <- getOption("repos")[["CRAN"]] tarball <- file.path(repos, "src/contrib/bread_1.0.0.tar.gz") # try to install it install(tarball) expect_true(renv_package_version("bread") == "1.0.0") }) test_that("various remote styles can be used during install", { skip_on_cran() skip_if_no_github_auth() renv_tests_scope() init() # install CRAN latest install("bread") expect_true(renv_package_installed("bread")) expect_true(renv_package_version("bread") == "1.0.0") # install from archive install("bread@0.1.0") expect_true(renv_package_installed("bread")) expect_true(renv_package_version("bread") == "0.1.0") # install from github install("kevinushey/skeleton") expect_true(renv_package_installed("skeleton")) expect_true(renv_package_version("skeleton") == "1.0.1") # install from github PR install("kevinushey/skeleton#1") expect_true(renv_package_installed("skeleton")) expect_true(renv_package_version("skeleton") == "1.0.2") # install from branch install("kevinushey/skeleton@feature/version-bump") expect_true(renv_package_installed("skeleton")) expect_true(renv_package_version("skeleton") == "1.0.2") # install from subdir install("kevinushey/subdir:subdir") expect_true(renv_package_installed("subdir")) expect_true(renv_package_version("subdir") == "0.0.0.9000") # install from URL to zip install("https://github.com/kevinushey/skeleton/archive/master.zip") expect_true(renv_package_installed("skeleton")) expect_true(renv_package_version("skeleton") == "1.0.1") }) test_that("Remotes fields in a project DESCRIPTION are respected", { skip_on_cran() skip_if_no_github_auth() renv_tests_scope() init() desc <- c( "Type: Package", "Package: renv.test.package", "Suggests: skeleton", "Remotes: kevinushey/skeleton" ) writeLines(desc, con = "DESCRIPTION") install() record <- renv_snapshot_description(package = "skeleton") expect_true(record$Source == "GitHub") }) test_that("source packages in .zip files can be installed", { renv_tests_scope() location <- download.packages("bread", destdir = renv_scope_tempfile()) path <- location[1, 2] renv_archive_decompress(path, exdir = "bread") zippath <- file.path(getwd(), "bread_1.0.0.zip") setwd("bread") status <- catchall(zip(zippath, files = ".", extras = "-q")) setwd("..") if (inherits(status, "condition")) skip("could not zip archive") install(zippath) expect_true(renv_package_installed("bread")) }) test_that("renv warns when installing an already-loaded package", { skip_on_cran() renv_tests_scope() install("bread@1.0.0") renv_namespace_load("bread") defer(renv_namespace_unload("bread")) expect_snapshot(install("bread@0.1.0")) }) test_that("install() writes out Github fields for backwards compatibility", { skip_if_no_github_auth() renv_tests_scope() install("rstudio/packrat") descpath <- file.path(.libPaths()[1], "packrat/DESCRIPTION") dcf <- renv_description_read(descpath) expect_equal(dcf$RemoteRepo, dcf$GithubRepo) expect_equal(dcf$RemoteUsername, dcf$GithubUsername) expect_equal(dcf$RemoteRef, dcf$GithubRef) expect_equal(dcf$RemoteSha, dcf$GithubSHA1) }) test_that("renv uses safe library paths on Windows", { skip_if_not(renv_platform_windows()) renv_tests_scope() goodlib <- "Research and Development" expect_true(renv_libpaths_safe(goodlib) == goodlib) badlib <- "R&D" expect_false(renv_libpaths_safe(badlib) != badlib) ensure_directory(badlib) renv_libpaths_set(badlib) install("bread") descpath <- file.path(getwd(), "R&D/bread") desc <- renv_description_read(descpath) expect_true(desc$Package == "bread") expect_true(desc$Version == "1.0.0") }) test_that("renv uses safe library path when needed", { renv_tests_scope() badlib <- file.path(getwd(), "Has'Single'Quote") dir.create(badlib) expect_false(renv_libpaths_safe(badlib) == badlib) }) test_that("renv can install packages from Bitbucket", { skip_on_cran() renv_tests_scope() install("bitbucket::kevinushey/skeleton") expect_true(renv_package_installed("skeleton")) }) test_that("install via version succeeds", { skip_on_cran() renv_tests_scope() install("bread@0.0.1") expect_true(renv_package_installed("bread")) expect_true(renv_package_version("bread") == "0.0.1") }) test_that("install() installs inferred dependencies", { skip_on_cran() renv_tests_scope("breakfast") # try installing packages records <- install() # validate that we've installed breakfast + deps expect_length(records, 4L) expect_true(renv_package_installed("breakfast")) # try calling install once more; nothing should happen records <- install() expect_length(records, 0L) }) test_that("install() prefers cellar when available", { skip_on_cran() renv_tests_scope() locals <- paste( renv_tests_path("nowhere"), renv_tests_path("local"), sep = ";" ) renv_scope_options(renv.config.cache.enabled = FALSE) renv_scope_envvars(RENV_PATHS_CELLAR = locals) records <- install("skeleton") record <- records$skeleton expect_equal(record$Source, "Cellar") path <- renv_path_normalize(renv_tests_path("local/skeleton")) prefix <- if (renv_platform_windows()) "file:///" else "file://" uri <- paste0(prefix, path) expect_equal(attr(record, "url"), uri) }) test_that("packages can be installed from the archive w/libcurl", { skip_on_cran() # validate that we have libcurl ok <- identical(capabilities("libcurl"), c(libcurl = TRUE)) skip_if(!ok, "libcurl is not available") # perform test renv_tests_scope() renv_scope_envvars(RENV_DOWNLOAD_FILE_METHOD = "libcurl") install("bread@0.1.0") expect_true(renv_package_installed("bread")) expect_equal(renv_package_version("bread"), "0.1.0") }) test_that("issue #609", { skip_on_cran() renv_tests_scope() renv_scope_options(configure.vars = c(breakfast = "")) install("bread") expect_true(renv_package_installed("bread")) }) test_that("we can install packages from git remotes within subdirs", { skip_on_cran() skip_on_ci() skip("unreliable test") renv_tests_scope("subdir") install("git@github.com:kevinushey/subdir.git:subdir", rebuild = TRUE) expect_true(renv_package_installed("subdir")) snapshot() remove("subdir") expect_false(renv_package_installed("subdir")) restore(packages = "subdir", rebuild = TRUE) expect_true(renv_package_installed("subdir")) }) test_that("packages embedded in the project use a project-local RemoteURL", { skip_if(getRversion() < "4.1") skip_if_not_installed("usethis") renv_tests_scope("example") usethis <- renv_namespace_load("usethis") skip_if(is.null(usethis$create_package)) renv_scope_options(usethis.quiet = TRUE) unlink("example", recursive = TRUE) fields <- list( "Authors@R" = utils::person( "Kevin", "Ushey", email = "kevinushey@gmail.com", role = c("aut", "cre"), comment = c(ORCID = "0000-0003-2880-7407") ) ) usethis$create_package("example", fields = fields, rstudio = FALSE, open = FALSE) install("./example") lockfile <- snapshot(lockfile = NULL) expect_equal(lockfile$Packages$example$RemoteUrl, "./example") # TODO: if the user provides a "weird" path, we'll use it as-is. # is that okay? what about relative paths that resolve outside of # the project root directory? install("./././example") lockfile <- snapshot(lockfile = NULL) expect_equal(lockfile$Packages$example$RemoteUrl, "./././example") }) test_that("packages installed from cellar via direct path", { skip_on_cran() renv_tests_scope("skeleton") locals <- paste( renv_tests_path("nowhere"), renv_tests_path("local"), sep = ";" ) renv_scope_options(renv.config.cache.enabled = FALSE) renv_scope_envvars(RENV_PATHS_CELLAR = locals) path <- renv_tests_path("local/skeleton/skeleton_1.0.1.tar.gz") records <- install(path, rebuild = TRUE) expect_equal(records$skeleton$Source, "Cellar") lockfile <- snapshot(lockfile = NULL) expect_equal(lockfile$Packages$skeleton$Source, "Cellar") }) test_that("staging library path has same permissions as library path", { skip_on_cran() skip_on_windows() renv_tests_scope() library <- renv_paths_library() ensure_directory(library) renv_scope_libpaths(library) umask <- Sys.umask("0") Sys.chmod(library, "0775") Sys.umask(umask) staging <- renv_install_staged_library_path() expect_equal(file.mode(staging), file.mode(library)) }) test_that("packages installed from a RemoteSubdir can be retrieved from cache", { skip_on_windows() skip_slow() renv_tests_scope() cachepath <- renv_scope_tempfile("renv-cache-") ensure_directory(cachepath) renv_scope_envvars(RENV_PATHS_CACHE = cachepath) init() # install first from remote install("kevinushey/subdir:subdir") # remove, and re-install from cache remove("subdir") install("kevinushey/subdir:subdir") expect_true(renv_package_installed("subdir")) }) test_that("repositories containing multiple packages can be installed", { skip_on_windows() skip_slow() renv_tests_scope() install("kevinushey/subdir:pkgA") expect_true(renv_package_installed("pkgA")) install("kevinushey/subdir:pkgB") expect_true(renv_package_installed("pkgB")) }) test_that("Suggest dependencies are used when requested", { renv_tests_scope("breakfast") fields <- c("Imports", "Depends", "LinkingTo", "Suggests") settings$package.dependency.fields(fields) install("breakfast") expect_true(renv_package_installed("egg")) }) test_that("custom dependency fields in install are supported", { skip_on_cran() skip_on_windows() renv_tests_scope() install("breakfast", dependencies = "strong") expect_false(renv_package_installed("egg")) install("breakfast", dependencies = c("strong", "Config/Needs/protein")) expect_true(renv_package_installed("egg")) }) test_that("install has user-friendly output", { renv_scope_libpaths() renv_scope_envvars(RENV_PATHS_CACHE = renv_scope_tempfile("renv-tempcache-")) renv_tests_scope("breakfast") expect_snapshot(install()) renv_tests_scope("breakfast") expect_snapshot(install()) }) test_that("package sources of the form _.zip can be installed", { skip_on_cran() skip_if(!renv_platform_windows()) renv_tests_scope() renv_tests_scope_repos() # get path to .tar.gz source <- download.packages("bread", type = "source")[1, 2] # repack as a .zip archive exdir <- renv_scope_tempfile("bread-") ensure_directory(exdir) renv_archive_decompress(source, exdir = exdir) zipfile <- file.path(tempdir(), "bread_f96a78e23d44d68d329c2dbf168a4dee1882a1c6.zip") local({ renv_scope_wd(exdir) zip(zipfile, files = "bread") }) # now try to install it install(zipfile) expect_true(renv_package_installed("bread")) }) test_that("package binaries of the form _.zip can be installed", { skip_on_cran() skip_if(!renv_platform_windows()) renv_tests_scope() renv_tests_scope_repos() # install bread install("bread") # create a zipfile from the installed package library <- renv_libpaths_active() zipfile <- file.path(tempdir(), "bread_f96a78e23d44d68d329c2dbf168a4dee1882a1c6.zip") local({ renv_scope_wd(library) zip(zipfile, files = "bread", extras = "-q") }) # remove bread remove("bread") expect_false(renv_package_installed("bread")) # now try to install from zipfile install(zipfile) expect_true(renv_package_installed("bread")) }) test_that("install() reports failure when a 'bad' binary is installed", { skip_on_cran() renv_tests_scope() # test package load in this scope on all platforms renv_scope_envvars(RENV_INSTALL_TEST_LOAD = TRUE) # install bread install("bread") # copy the installed package, and create a 'broken' binary src <- renv_package_find("bread") tgt <- file.path(tempdir(), "bread") renv_file_copy(src, tgt) local({ renv_scope_wd(tgt) dir.create("R") writeLines("stop('oh no')", con = "R/bread") }) # try installing the broken binary remove("bread") expect_false(renv_package_installed("bread")) expect_error(install(tgt)) expect_false(renv_package_installed("bread")) # try skipping the load test renv_scope_options(INSTALL_opts = c(bread = "--no-test-load")) install(tgt) expect_true(renv_package_installed("bread")) expect_error(renv_namespace_load(bread)) remove("bread") }) test_that("install() respects dependencies argument", { skip_on_cran() project <- renv_tests_scope() init() contents <- heredoc(" Type: Project Depends: coffee Imports: bread Suggests: muffin ") writeLines(contents, con = "DESCRIPTION") install(dependencies = "Imports") expect_true(renv_package_installed("bread")) expect_false(renv_package_installed("coffee")) expect_false(renv_package_installed("muffin")) }) test_that("install() succeeds even some repositories cannot be queried", { renv_tests_scope() repos <- getOption("repos") repos[["NARC"]] <- file.path(repos[["CRAN"]], "missing") renv_scope_options(repos = repos) init() install("bread") expect_true(renv_package_installed("bread")) }) test_that("install() doesn't duplicate authentication headers", { skip_on_cran() skip_if_no_github_auth() renv_scope_envvars(RENV_DOWNLOAD_METHOD = "libcurl") project <- renv_tests_scope() init() install("kevinushey/skeleton") expect_true(renv_package_installed("skeleton")) }) test_that("install() stores repository information for installed packages", { project <- renv_tests_scope(isolated = TRUE) init() # unset repository option repos <- getOption("repos") renv_scope_options(repos = character()) # try to install a package writeLines("library(bread)", con = "_deps.R") install("bread", repos = c(TEST = unname(repos))) # create a lockfile snapshot() # validate that the repository information is stored lockfile <- renv_lockfile_read("renv.lock") record <- lockfile$Packages$bread expect_equal(!!record$Source, "Repository") expect_equal(!!record$Repository, !!unname(repos)) # now, add the repository back; it should then be aliased in lockfile options(repos = c(TEST = unname(repos))) snapshot() lockfile <- renv_lockfile_read("renv.lock") record <- lockfile$Packages$bread expect_equal(!!record$Source, "Repository") expect_equal(!!record$Repository, "TEST") }) test_that("install() lazily resolves project remotes", { skip_on_cran() skip_if_no_github_auth() project <- renv_tests_scope() init() writeLines("Remotes: kevinushey/skeleton", con = "DESCRIPTION") install("bread") expect_false(renv_package_installed("skeleton")) }) test_that("install() records the repository used to retrieve a package", { project <- renv_tests_scope(isolated = TRUE) init() url <- unname(getOption("repos")) local({ renv_scope_options(repos = character()) install("bread", repos = c(TEST = url), rebuild = TRUE) }) dcf <- renv_description_read(package = "bread") expect_equal(!!dcf$RemoteRepos, !!url) expect_equal(!!dcf$RemoteReposName, "TEST") }) test_that("recursive dependency versions are properly resolved", { project <- renv_tests_scope() init() install("phone@0.1.0") install("jamie") expect_equal(renv_package_version("phone"), "1.0.0") }) test_that("install(lock = TRUE) updates lockfile", { project <- renv_tests_scope() init() # first, get a lockfile as created via install + lock install("breakfast", lock = TRUE) actual <- renv_lockfile_load(project = project) # now, try re-computing the lockfile via snapshot writeLines("library(breakfast)", con = "dependencies.R") expected <- snapshot(lockfile = NULL) # make sure they're equal expect_mapequal( renv_lockfile_records(actual), renv_lockfile_records(expected) ) }) test_that("installation of package from local sources works", { project <- renv_tests_scope() # test the shim repopath <- renv_tests_repopath() setwd(file.path(repopath, "src/contrib")) renv_shim_install_packages("bread_1.0.0.tar.gz", repos = NULL, type = "source") expect_true(renv_package_installed("bread")) # test a regular invocation of install remove.packages("bread") info <- download.packages("bread", destdir = tempdir()) install(info[, 2], repos = NULL, type = "source") expect_true(renv_package_installed("bread")) }) test_that("packages installed from r-universe preserve their remote metadata", { skip_on_cran() skip_on_ci() project <- renv_tests_scope(packages = "rlang") install("rlang", repos = "https://r-lib.r-universe.dev") record <- renv_snapshot_description(package = "rlang") expect_true(is.character(record[["RemoteSha"]])) }) # https://github.com/rstudio/renv/issues/2071 test_that("irrelevant R version requirements don't prevent package installation", { renv_tests_scope() init() # package in repository not compatible with this version of R expect_error(install("today")) # but older version can be successfully installed install("today@0.1.0") expect_true(renv_package_installed("today")) expect_equal(renv_package_version("today"), "0.1.0") # other packages can be installed even if this project depends on it writeLines("Depends: future, today", con = "DESCRIPTION") install("future") expect_true(renv_package_installed("future")) remove("future") # but installing that package should still fail expect_error(install("today")) }) renv/tests/testthat/test-remotes.R0000644000176200001440000001543114761163114017014 0ustar liggesusers test_that("we can parse a variety of remotes", { remote <- renv_remotes_parse("git@github.com:kevinushey/repo.git:subdir") expect_equal(remote$type, "git") expect_equal(remote$host, "github.com") expect_equal(remote$repo, "kevinushey/repo.git") expect_equal(remote$subdir, "subdir") remote <- renv_remotes_parse("url::https://github.com/kevinushey/renv.git1/archive/refs/heads/main.zip") expect_equal(remote$type, "url") expect_equal(remote$url, "https://github.com/kevinushey/renv.git1/archive/refs/heads/main.zip") expect_equal(remote$protocol, "https") # https://github.com/rstudio/renv/issues/1004 remote <- renv_remotes_parse("git@github.com:abc123/def456.git") expect_equal(remote$type, "git") expect_equal(remote$host, "github.com") expect_equal(remote$repo, "abc123/def456.git") # https://github.com/rstudio/renv/issues/667 remote <- renv_remotes_parse("package=git@github.com:abc123/def456.git") expect_equal(remote$package, "package") # https://github.com/rstudio/renv/issues/667 remote <- renv_remotes_parse("git@github.com:abc/def/ghi") expect_equal(remote$repo, "abc/def/ghi") }) test_that("we can parse a variety of remotes", { skip_on_cran() skip_if_no_github_auth() skip_on_os("windows") renv_tests_scope() # cran latest record <- renv_remotes_resolve("breakfast") expect_equal(record$Package, "breakfast") expect_equal(record$Version, NULL) # cran archive record <- renv_remotes_resolve("breakfast@0.1.0") expect_equal(record$Package, "breakfast") expect_equal(record$Version, "0.1.0") # github master record <- renv_remotes_resolve("kevinushey/skeleton") expect_equal(record$Package, "skeleton") expect_equal(record$Version, "1.0.1") expect_equal(record$RemoteRef, "master") expect_equal(record$RemoteSha, "e4aafb92b86ba7eba3b7036d9d96fdfb6c32761a") # by commit record <- renv_remotes_resolve("kevinushey/skeleton@209c4e48e505e545ad7ab915904d983b5ab83b93") expect_equal(record$Package, "skeleton") expect_equal(record$Version, "1.0.0") expect_equal(record$RemoteSha, "209c4e48e505e545ad7ab915904d983b5ab83b93") # by branch record <- renv_remotes_resolve("kevinushey/skeleton@feature/version-bump") expect_equal(record$Package, "skeleton") expect_equal(record$Version, "1.0.2") expect_equal(record$RemoteRef, "feature/version-bump") expect_equal(record$RemoteSha, "86b5737411d3c6a6927dfcccd2c15a69284659fe") # by PR record <- renv_remotes_resolve("kevinushey/skeleton#1") expect_equal(record$Package, "skeleton") expect_equal(record$Version, "1.0.2") expect_equal(record$RemoteSha, "86b5737411d3c6a6927dfcccd2c15a69284659fe") # bitbucket record <- renv_remotes_resolve("bitbucket::kevinushey/skeleton") expect_equal(record$Package, "skeleton") expect_equal(record$Version, "1.0.1") expect_equal(record$RemoteHost, "api.bitbucket.org/2.0") expect_equal(record$RemoteRef, "master") expect_equal(record$RemoteSha, "958296dbbbf7f1d82f7f5dd1b121c7558604809f") # gitlab record <- renv_remotes_resolve("gitlab::kevinushey/skeleton") expect_equal(record$Package, "skeleton") expect_equal(record$Version, "1.0.1") expect_equal(record$RemoteHost, "gitlab.com") expect_equal(record$RemoteRef, "master") expect_equal(record$RemoteSha, "958296dbbbf7f1d82f7f5dd1b121c7558604809f") # git - https record <- renv_remotes_resolve("git::https://github.com/kevinushey/renv.git1.git@main") expect_equal(record$Package, "renv.git1") expect_equal(record$Version, "0.0.0.9000") expect_equal(record$RemoteUrl, "https://github.com/kevinushey/renv.git1.git") expect_equal(record$RemoteRef, "main") # git + *release record <- renv_remotes_resolve("kevinushey/skeleton@*release") expect_equal(record$Package, "skeleton") expect_equal(record$RemoteRef, "v1.0.1") # git + prefix record <- renv_remotes_resolve("skeleton=kevinushey/skeleton@*release") expect_equal(record$Package, "skeleton") expect_equal(record$RemoteRef, "v1.0.1") # url record <- renv_remotes_resolve("url::https://github.com/kevinushey/renv.git1/archive/refs/heads/main.zip") expect_equal(record$Package, "renv.git1") expect_equal(record$Version, "0.0.0.9000") expect_equal(record$RemoteUrl, "https://github.com/kevinushey/renv.git1/archive/refs/heads/main.zip") expect_equal(record$Source, "URL") # # git - ssh # # this test appears to fail on CI (ssh access to GitHub disallowed?) # record <- renv_remotes_resolve("git::git@github.com:kevinushey/renv.git1.git@main") # expect_equal(record$Package, "renv.git1") # expect_equal(record$Version, "0.0.0.9000") # expect_equal(record$RemoteUrl, "git@github.com:kevinushey/renv.git1.git") # expect_equal(record$RemoteRef, "main") # error expect_error(renv_remotes_resolve("can't parse this")) }) test_that("subdirectories are parsed in remotes", { spec <- "gitlab::user/repo:subdir@ref" remote <- renv_remotes_parse(spec) expected <- list( spec = spec, package = NULL, type = "gitlab", host = NULL, user = "user", repo = "repo", subdir = "subdir", pull = NULL, ref = "ref" ) expect_equal(remote, expected) }) test_that("custom hosts can be supplied", { spec <- "gitlab@localhost::user/repo" remote <- renv_remotes_parse(spec) expected <- list( spec = spec, package = NULL, type = "gitlab", host = "localhost", user = "user", repo = "repo", subdir = NULL, pull = NULL, ref = NULL ) expect_equal(remote, expected) }) test_that("paths specified with '.' are treated as local", { renv_tests_scope() writeLines(con = "DESCRIPTION", c( "Type: Package", "Package: test", "Version: 1.0" )) record <- renv_remotes_resolve(".") expect_equal(record$Package, "test") expect_equal(record$Version, "1.0") }) test_that("packages can be installed from GitLab groups", { # test parsing of spec spec <- "gitlab::renv-group/renv-subgroup/subpackage" remote <- renv_remotes_parse(spec) expected <- list( spec = spec, package = NULL, type = "gitlab", host = NULL, user = "renv-group", repo = "renv-subgroup/subpackage", subdir = NULL, pull = NULL, ref = NULL ) expect_equal(remote, expected) # test installation skip_slow() renv_tests_scope() install(spec) expect_true(renv_package_installed("subpackage")) }) test_that("remote specs referencing packages in sub-sub-directories are parsed correctly", { spec <- "github::user/repo/subdir/subsubdir" remote <- renv_remotes_parse(spec) expected <- list( spec = spec, package = NULL, type = "github", host = NULL, user = "user", repo = "repo", subdir = "subdir/subsubdir", pull = NULL, ref = NULL ) expect_equal(remote, expected) }) renv/tests/testthat/test-profile.R0000644000176200001440000000643214731330073016774 0ustar liggesusers test_that("renv/profile is read and used to select a profile", { skip_on_cran() project <- renv_tests_scope() renv_scope_envvars(RENV_PROFILE = NULL) init(profile = "testing") # make sure we have renv installed for this test libpaths <- the$default_libpaths[[".libPaths()"]] source <- find.package("renv", lib.loc = libpaths) renv_imbue_self(project) # check that profile was written expect_true(file.exists("renv/profile")) # check that its contents equal 'testing' contents <- readLines("renv/profile") expect_equal(contents, "testing") # check that an R session launched here gets that profile renv_scope_envvars(R_PROFILE_USER = NULL) script <- renv_test_code({ writeLines(Sys.getenv("RENV_PROFILE")) }) args <- c("-s", "-f", shQuote(script)) output <- renv_system_exec(R(), args, action = "reading profile") }) test_that("a profile changes the default library / lockfile path", { skip_on_cran() renv_tests_scope() renv_scope_envvars(RENV_PROFILE = "testing") project <- getwd() init() # NOTE: renv/profile should not be written here as we've only forced # activation via an environment variable and not explicitly via API profile <- file.path(project, "renv/profile") expect_false(file.exists(profile)) # however, other paths should resolve relative to the active profile prefix <- "renv/profiles/testing" expect_equal( renv_path_normalize(paths$lockfile(project = project)), renv_path_normalize(file.path(project, prefix, "renv.lock")) ) expect_equal( renv_path_normalize(paths$library(project = project)), renv_path_normalize(file.path(project, prefix, "renv/library", renv_platform_prefix())) ) expect_equal( renv_path_normalize(paths$settings(project = project)), renv_path_normalize(file.path(project, prefix, "renv/settings.json")) ) }) test_that("profile-specific dependencies can be written", { renv_tests_scope() # initialize project with 'testing' profile renv_scope_envvars(RENV_PROFILE = "testing") init() # have this profile depend on 'toast' path <- renv_paths_renv("_dependencies.R") ensure_parent_directory(path) writeLines("library(toast)", con = path) # validate the dependency is included deps <- dependencies() expect_true("toast" %in% deps$Package) # switch to other profile renv_scope_envvars(RENV_PROFILE = "other") # 'toast' is no longer required deps <- dependencies() expect_false("toast" %in% deps$Package) }) test_that("profile-specific dependencies can be declared in DESCRIPTION", { renv_tests_scope() renv_scope_envvars(RENV_PROFILE = "testing") init() writeLines( "Config/renv/profiles/testing/dependencies: toast", con = "DESCRIPTION" ) deps <- dependencies() expect_true("toast" %in% deps$Package) }) test_that("profile-specific remotes are parsed", { project <- renv_tests_scope() renv_scope_envvars(RENV_PROFILE = "testing") init() desc <- heredoc(' Type: Project Config/renv/profiles/testing/dependencies: bread Config/renv/profiles/testing/remotes: bread@0.1.0 ') writeLines(desc, con = "DESCRIPTION") remotes <- renv_project_remotes(project) actual <- resolve(remotes$bread) expected <- list(Package = "bread", Version = "0.1.0", Source = "Repository") expect_equal(actual, expected) }) renv/tests/testthat/test-python.R0000644000176200001440000001403214731330073016650 0ustar liggesusers renv_test_scope_python <- function(scope = parent.frame()) { renv_scope_envvars( PATH = Sys.getenv("PATH"), RENV_PYTHON = NULL, RETICULATE_PYTHON = NULL, RETICULATE_PYTHON_ENV = NULL, scope = scope ) } test_that("we can activate Python with a project", { skip_on_os("windows") skip_on_cran() skip_if_no_python() renv_test_scope_python() renv_tests_scope("breakfast") use_python(python = sys_python(), type = "system") lockfile <- renv_lockfile_read("renv.lock") expect_true(!is.null(lockfile$Python)) }) test_that("we can activate Python with a virtualenv in a project", { skip_slow() skip_on_os("windows") skip_if_no_virtualenv() renv_test_scope_python() renv_tests_scope("breakfast") use_python(python = sys_python(), type = "virtualenv") lockfile <- renv_lockfile_read("renv.lock") expect_equal(lockfile$Python$Type, "virtualenv") }) test_that("renv uses local virtual environment for names beginning with '.'", { skip_on_os("windows") skip_on_cran() skip_if_no_virtualenv() renv_test_scope_python() renv_tests_scope("breakfast") use_python(python = sys_python(), name = ".venv") expect_true(renv_file_exists(".venv")) lockfile <- renv_lockfile_read("renv.lock") expect_equal(lockfile$Python$Type, "virtualenv") expect_equal(lockfile$Python$Name, ".venv") }) test_that("renv can bind to virtualenvs in WORKON_HOME", { skip_on_os("windows") skip_on_cran() skip_if_no_virtualenv() renv_test_scope_python() # work with temporary virtualenv home renv_scope_envvars(WORKON_HOME = tempdir()) home <- renv_python_virtualenv_home() expect_true(renv_path_same(home, tempdir())) # construct environment name, path name <- "renv-test-environment" path <- file.path(home, name) # clean up when we're done defer(unlink(path, recursive = TRUE)) # create a test project renv_tests_scope("breakfast") use_python(python = sys_python(), name = "renv-test-environment") expect_true(renv_file_exists(path)) lockfile <- renv_lockfile_read("renv.lock") expect_equal(lockfile$Python$Type, "virtualenv") expect_equal(lockfile$Python$Name, "renv-test-environment") }) test_that("installed Python packages are snapshotted / restored [virtualenv]", { skip_slow() skip_on_os("windows") skip_if_no_virtualenv() renv_test_scope_python() renv_tests_scope("breakfast") python <- sys_python() # initialize python python <- use_python( python, name = renv_scope_tempfile("python-virtualenv-"), type = "virtualenv" ) # install python-dotenv expect_false(renv_python_module_available(python, "dotenv")) pip_install("python-dotenv", python = python) expect_true(renv_python_module_available(python, "dotenv")) # snapshot changes snapshot() # check requirements.txt for install expect_true(file.exists("requirements.txt")) reqs <- renv_properties_read("requirements.txt", delimiter = "==") expect_true("python-dotenv" %in% names(reqs)) # uninstall python-dotenv expect_true(renv_python_module_available(python, "dotenv")) pip_uninstall("python-dotenv", python = python) expect_false(renv_python_module_available(python, "dotenv")) # try to restore restore() # check that we can load python-dotenv now expect_true(renv_python_module_available(python, "dotenv")) }) test_that("installed Python packages are snapshotted / restored [conda]", { skip_if_local() skip_on_os("windows") skip_on_cran() skip_if_no_miniconda("3.6") renv_test_scope_python() renv_tests_scope("breakfast") # initialize python quietly(use_python(type = "conda")) python <- Sys.getenv("RETICULATE_PYTHON") # install numpy cmd <- paste(shQuote(python), "-m pip install --quiet numpy") system(cmd, ignore.stdout = TRUE, ignore.stderr = TRUE) # snapshot changes snapshot() # check requirements.txt for install expect_true(file.exists("environment.yml")) # uninstall numpy cmd <- paste(shQuote(python), "-m pip uninstall --quiet --yes numpy") system(cmd, ignore.stdout = TRUE, ignore.stderr = TRUE) # can no longer load numpy cmd <- paste(shQuote(python), "-c 'import numpy'") status <- system(cmd, ignore.stdout = TRUE, ignore.stderr = TRUE) expect_false(status == 0L) # try to restore restore() # check that we can load numpy now cmd <- paste(shQuote(python), "-c 'import numpy'") status <- system(cmd, ignore.stdout = TRUE, ignore.stderr = TRUE) expect_true(status == 0L) }) test_that("python environment name is preserved after snapshot", { skip_if_local() skip_on_os("windows") skip_on_cran() skip_if_no_miniconda("3.6") renv_test_scope_python() # create and use local python environment renv_tests_scope() init() system2(python, c("-m", "venv", "virtualenv")) use_python(name = "./virtualenv") # check that the lockfile has been updated lockfile <- renv_lockfile_read("renv.lock") expect_equal(lockfile$Python$Name, "./virtualenv") # try to snapshot snapshot() # check that the virtual environment name was preserved lockfile <- renv_lockfile_read("renv.lock") expect_equal(lockfile$Python$Name, "./virtualenv") }) test_that("renv_python_discover() respects PATH ordering", { skip_on_cran() skip_on_windows() renv_tests_scope() renv_test_scope_python() # create a bunch of python directories wd <- renv_path_normalize(getwd()) pythons <- file.path(wd, c("1", "2", "3"), "python") for (python in pythons) { ensure_parent_directory(python) file.create(python) Sys.chmod(python, "0755") } # update the path path <- paste(dirname(pythons), collapse = .Platform$path.sep) discovered <- local({ renv_scope_envvars(PATH = path) renv_python_discover() }) # check that we looked at them in the right order expect_equal(tail(discovered, n = 3L), pythons) }) test_that("renv uses symlinks for system python", { skip_on_cran() skip_on_windows() renv_tests_scope() renv_test_scope_python() skip_if(!file.exists("/usr/bin/python3")) use_python("/usr/bin/python3", type = "system") expect_false(Sys.which("python3") == "/usr/bin/python3") }) renv/tests/testthat/helper-interactive.R0000644000176200001440000000062614731330073020150 0ustar liggesusers if (interactive()) { # create a 'done' object that, when printed, will # run any pending defer handlers envir <- attach(NULL, name = "tools:renv") envir$done <- structure(list(), class = "renv_done") registerS3method("print", "renv_done", function(x, ...) { renv:::renv_defer_execute(teardown_env()) }) # detach when we're done defer(detach("tools:renv"), scope = teardown_env()) } renv/tests/testthat/test-lockfile-validate.R0000644000176200001440000001553214731330073020714 0ustar liggesusers test_that("a known-good lockfile passes validation", { skip_on_cran() skip_if_not_installed("jsonvalidate") lockfile <- ' { "R": { "Version": "4.2.3", "Repositories": [ { "Name": "CRAN", "URL": "https://cloud.r-project.org" }, { "Name": "BioCsoft", "URL": "https://bioconductor.org/packages/3.8/bioc" } ] }, "Python": { "Version": "3.10.12", "Type": "virtualenv", "Name": "./renv/python/virtualenvs/renv-python-3.10" }, "Bioconductor": { "Version": "3.8" }, "Packages": { "markdown": { "Package": "markdown", "Version": "1.0", "Source": "Repository", "Repository": "CRAN", "Hash": "4584a57f565dd7987d59dda3a02cfb41" }, "mime": { "Package": "mime", "Version": "0.12.1", "Source": "GitHub", "RemoteType": "github", "RemoteHost": "api.github.com", "RemoteUsername": "yihui", "RemoteRepo": "mime", "RemoteRef": "main", "RemoteSha": "1763e0dcb72fb58d97bab97bb834fc71f1e012bc", "Requirements": [ "tools" ], "Hash": "c2772b6269924dad6784aaa1d99dbb86" } } } ' expect_no_error(lockfile_validate(lockfile = lockfile)) expect_true(lockfile_validate(lockfile = lockfile)) }) test_that("a known-good lockfile with extra fields passes validation", { skip_on_cran() skip_if_not_installed("jsonvalidate") # Lockfile adds a R$Nickname field not present in the schema lockfile <- ' { "R": { "Version": "4.2.3", "Nickname": "Shortstop Beagle", "Repositories": [ { "Name": "CRAN", "URL": "https://cloud.r-project.org" }, { "Name": "BioCsoft", "URL": "https://bioconductor.org/packages/3.8/bioc" } ] }, "Python": { "Version": "3.10.12", "Type": "virtualenv", "Name": "./renv/python/virtualenvs/renv-python-3.10" }, "Bioconductor": { "Version": "3.8" }, "Packages": { "markdown": { "Package": "markdown", "Version": "1.0", "Source": "Repository", "Repository": "CRAN", "Hash": "4584a57f565dd7987d59dda3a02cfb41" }, "mime": { "Package": "mime", "Version": "0.12.1", "Source": "GitHub", "RemoteType": "github", "RemoteHost": "api.github.com", "RemoteUsername": "yihui", "RemoteRepo": "mime", "RemoteRef": "main", "RemoteSha": "1763e0dcb72fb58d97bab97bb834fc71f1e012bc", "Requirements": [ "tools" ], "Hash": "c2772b6269924dad6784aaa1d99dbb86" } } } ' expect_no_error(lockfile_validate(lockfile = lockfile)) expect_true(lockfile_validate(lockfile = lockfile)) }) test_that("a custom schema file can be used for successful validation", { skip_on_cran() skip_if_not_installed("jsonvalidate") # Custom schema adds a required R$Nickname field present in the lockfile lockfile <- ' { "R": { "Version": "4.2.3", "Nickname": "Shortstop Beagle", "Repositories": [ { "Name": "CRAN", "URL": "https://cloud.r-project.org" } ] }, "Packages": { "markdown": { "Package": "markdown", "Version": "1.0", "Source": "Repository", "Repository": "CRAN", "Hash": "4584a57f565dd7987d59dda3a02cfb41" } } } ' schema <- ' { "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "properties": { "R": { "type": "object", "properties": { "Version": { "type": "string" }, "Nickname": { "type": "string" } }, "required": ["Version", "Nickname"] } } } ' expect_no_error(lockfile_validate(lockfile = lockfile, schema = schema)) expect_true(lockfile_validate(lockfile = lockfile, schema = schema)) }) test_that("a custom schema file can be used for failed validation", { skip_on_cran() skip_if_not_installed("jsonvalidate") # Custom schema adds a required R$Nickname field not present in the lockfile lockfile <- ' { "R": { "Version": "4.2.3", "Repositories": [ { "Name": "CRAN", "URL": "https://cloud.r-project.org" } ] }, "Packages": { "markdown": { "Package": "markdown", "Version": "1.0", "Source": "Repository", "Repository": "CRAN", "Hash": "4584a57f565dd7987d59dda3a02cfb41" } } } ' schema <- ' { "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "properties": { "R": { "type": "object", "properties": { "Version": { "type": "string" }, "Nickname": { "type": "string" } }, "required": ["Version", "Nickname"] } } } ' expect_false(lockfile_validate(lockfile = lockfile, schema = schema)) }) test_that("an incorrect Packages$Hash field fails validation", { skip_on_cran() skip_if_not_installed("jsonvalidate") lockfile <- ' { "R": { "Version": "4.2.3", "Repositories": [ { "Name": "CRAN", "URL": "https://cloud.r-project.org" } ] }, "Packages": { "markdown": { "Package": "markdown", "Version": "1.0", "Source": "Repository", "Repository": "CRAN", "Hash": "4584a57f565dd" } } } ' expect_false(lockfile_validate(lockfile = lockfile)) }) test_that("invalid JSON fails validation", { skip_on_cran() skip_if_not_installed("jsonvalidate") # Packages uses [] which is not valid JSON lockfile <- ' { "R": { "Version": "4.2.3", "Repositories": [ { "Name": "CRAN", "URL": "https://cloud.r-project.org" } ] }, "Packages": [ "markdown": { "Package": "markdown", "Version": "1.0", "Source": "Repository", "Repository": "CRAN", "Hash": "4584a57f565dd7987d59dda3a02cfb41" } ] } ' expect_error(lockfile_validate(lockfile = lockfile, error = TRUE)) }) test_that("strict mode catches unknown keyword in provided schema", { skip_on_cran() skip_if_not_installed("jsonvalidate") # Custom schema provides "Version" with "type": "UNKNOWN" lockfile <- ' { "R": { "Version": "4.2.3", "Repositories": [ { "Name": "CRAN", "URL": "https://cloud.r-project.org" } ] }, "Packages": { "markdown": { "Package": "markdown", "Version": "1.0", "Source": "Repository", "Repository": "CRAN", "Hash": "4584a57f565dd7987d59dda3a02cfb41" } } } ' schema <- ' { "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "properties": { "R": { "type": "object", "properties": { "Version": { "UNKNOWN": "string" } } } } } ' expect_true(lockfile_validate(lockfile = lockfile, schema = schema)) expect_error(lockfile_validate(lockfile = lockfile, schema = schema, strict = TRUE)) }) renv/tests/testthat/test-records.R0000644000176200001440000001051114731330073016766 0ustar liggesusers test_that("renv_records_select() handles missing packages gracefully", { # simulate what happens during printing of records during install lhs <- list() rhs <- list(skeleton = list(Package = "skeleton")) actions <- c(skeleton = "install") action <- "install" expect_identical(renv_records_select(lhs, actions, action), lhs) expect_identical(renv_records_select(rhs, actions, action), rhs) }) test_that("we can format records in various ways", { old <- list( Package = "skeleton", Version = "1.0.0", Source = "Repository", Repository = "CRAN" ) new <- list( Package = "skeleton", Version = "1.0.0", Source = "github", RemoteUsername = "kevinushey", RemoteRepo = "skeleton" ) expect_equal(renv_record_format_short(old), "1.0.0") expect_equal(renv_record_format_short(new), "kevinushey/skeleton") expect_equal( renv_record_format_pair(old, new), "[1.0.0 -> kevinushey/skeleton]" ) expect_equal( renv_record_format_pair(new, new), "[kevinushey/skeleton: unchanged]" ) record <- list( Package = "skeleton", Version = "1.0.0", Source = "github", RemoteUsername = "kevinushey", RemoteRepo = "skeleton", RemoteRef = "feature/branch" ) expect_equal( renv_record_format_short(record), "kevinushey/skeleton@feature/branch" ) old <- list( Package = "skeleton", Version = "1.0.0", Source = "GitHub", RemoteUsername = "kevinushey", RemoteRepo = "skeleton" ) new <- list( Package = "skeleton", Version = "1.0.0", Source = "Gitlab", RemoteUsername = "kevinushey", RemoteRepo = "skeleton" ) expect_equal( renv_record_format_pair(old, new), "[1.0.0: GitHub -> Gitlab]" ) }) test_that("compatible records from pak are handled correctly", { lhs <- list( Package = "anytime", Version = "0.3.9", Source = "Repository", Depends = "R (>= 3.2.0)", Imports = "Rcpp (>= 0.12.9)", LinkingTo = c("Rcpp (>= 0.12.9)", "BH"), Repository = "CRAN", RemoteType = "standard", RemotePkgRef = "anytime", RemoteRef = "anytime", RemoteRepos = "https://cran.rstudio.com", RemotePkgPlatform = "aarch64-apple-darwin20", RemoteSha = "0.3.9", Hash = "74a64813f17b492da9c6afda6b128e3d" ) rhs <- list( Package = "anytime", Version = "0.3.9", Source = "CRAN", Repository = "CRAN", RemoteType = "standard", RemotePkgRef = "anytime", RemoteRef = "anytime", RemoteRepos = "https://cran.rstudio.com", RemotePkgPlatform = "aarch64-apple-darwin20", RemoteSha = "0.3.9", Hash = "74a64813f17b492da9c6afda6b128e3d", Requirements = list() ) change <- renv_lockfile_diff_record(lhs, rhs) expect_null(change) }) test_that("pak's cran remotes are considered cranlike", { record <- list( Package = "rlang", Version = "1.0.0", RemoteType = "standard", RemotePkgRef = "rlang", RemoteRef = "rlang", RemoteRepos = "https://cloud.R-project.org", RemotePkgPlatform = "aarch64-apple-darwin20", RemoteSha = "1.1.3" ) actual <- renv_record_normalize(record) expected <- list(Package = "rlang", Version = "1.0.0") expect_identical(!!actual, !!expected) }) test_that("we format github remotes appropriately", { record <- list( Package = "skeleton", Version = "1.1.0", RemoteType = "github", RemoteUsername = "kevinushey", RemoteRepo = "skeleton", RemoteRef = "main", RemoteSha = "e4aafb92b86ba7eba3b7036d9d96fdfb6c32761a", RemoteSubdir = "subdir" ) remote <- renv_record_format_remote(record, compact = FALSE) expect_equal(remote, "kevinushey/skeleton:subdir@e4aafb92b86ba7eba3b7036d9d96fdfb6c32761a") remote <- renv_record_format_remote(record, compact = TRUE) expect_equal(remote, "kevinushey/skeleton:subdir") remote <- renv_record_format_remote(record, pak = TRUE) expect_equal(remote, "kevinushey/skeleton/subdir@e4aafb92b86ba7eba3b7036d9d96fdfb6c32761a") }) renv/tests/testthat/helper-aaa.R0000644000176200001440000000040014740070207016343 0ustar liggesusers teardown_env <- function() { if (testthat::is_testing()) testthat::teardown_env() else globalenv() } the$tests_repopath <- renv_scope_tempfile("renv-repos-", scope = teardown_env()) renv_tests_repopath <- function() { the$tests_repopath } renv/tests/testthat.R0000644000176200001440000000071614731330073014356 0ustar liggesusers library(testthat) library(renv, warn.conflicts = FALSE) if (!renv:::renv_tests_supported()) { message("* renv does not support running tests on this platform.") if (!interactive()) quit(status = 0L) } if (Sys.info()[["sysname"]] == "Linux") { tools <- asNamespace("tools") if (is.function(tools$R_user_dir)) { cachedir <- tools$R_user_dir("renv", "cache") dir.create(cachedir, recursive = TRUE, showWarnings = FALSE) } } test_check("renv") renv/MD50000644000176200001440000006732414761174012011553 0ustar liggesusers125915401cafbf273ce1f5f84330dda7 *DESCRIPTION 0d37e729582c25a6fc726cf9f8763140 *LICENSE 9cd30053a51f82ef8e77b7f7a33786c2 *NAMESPACE a23e35e74a614d51dfcece2ed111a62a *NEWS.md 023d17b249bc6c7a7b41e7dc00af95e8 *R/aaa.R 9b07a711084eb714556bcd4a2e15a0ae *R/abi.R fbd5779f1ba597caed25184243a17f6e *R/abort.R 692dcfa228420183aaa2a9bd71f4d040 *R/acls.R f4582137c392edd0f71de326aebcd642 *R/actions.R fc43239a255ed63a6e3aa9982b3dcafd *R/activate.R af4d7d522bbaed38ebeff8780e65f4a0 *R/addins.R 800e81aa46fdf55d8b7e4d884f460d6c *R/aliases.R ffcb24fff43e79a44d7a8e006032cc87 *R/ansify.R cc207980d0293129ed02f6f01040420f *R/archive.R 7c347ba714df72d81d6bc35559518b54 *R/autoload.R 1e96fb1e7c5b777a64b3c983e775f2e3 *R/available-packages.R 6c19226a1f77a02a873b8211800058cd *R/backports.R 60163156551822a3ad5ce60546be5390 *R/base64.R 6c2cbb5b3f73155e1736d61c16f2429d *R/bind.R 0722a531cd5f2e29c2dbf9e761c66f97 *R/binding.R ed09bc2a99dd36d0d3230e729be76be3 *R/bioconductor.R ccebb1c463166a82d67316d308146c6b *R/bootstrap.R 3313b97ee7e8168d86aa4305253a649f *R/cache.R 909bd8bbc8efa548e2cabf2efb5f9241 *R/call.R 08c918afeae0ad2486a1d17b0e252115 *R/caution.R 2fff23181fbad1d72f92143a2c1a1e99 *R/cellar.R ce4c1982d86b950a1857955f3d28f1cf *R/check.R c4b9970db2b641633e117624c0af8a16 *R/checkout.R 1bbf6bfe4ceffc9a66eca874840a7b10 *R/clean.R c8b858c7241b99a12b2ae3c961dd893b *R/cleanse.R 514d181117e3deeaf62739db389b17f6 *R/cli.R 63aad727450d622b556cd5814377a6d5 *R/conda.R e6bdfaa56b33db335dff082cc58c1bd2 *R/condition.R 1a630585b13a8f1a25b0045bdc64709b *R/config-defaults.R 51d6fb9a268babc55621b8a2f7474f80 *R/config.R 37834a7b7966a6030195e6e7b0f51432 *R/consent.R 16adef3752678189da5a5252fbcdbe84 *R/cran.R 32d10d32726b15a9109d4be19d62e101 *R/curl.R ab17817a7367e2813b130d48abbe08f0 *R/data_frame.R 846703026a361c959b3dcd7d087914a5 *R/dcf.R e087e69e3a26cdfc5f10dfc67ced28fd *R/deactivate.R cf51e72226ee4730eda902b40e7928bc *R/debuggify.R 1ef3052486279870534d96be30aae975 *R/defer.R f038babcffc0e464bc568c008927774c *R/dependencies.R 5f3807c89ba864f4a6f6cd329a655146 *R/description.R 39b5bb903a33e7a311c7309bd9d2305a *R/diagnostics.R 15b7302f0f7ecb785f2398754bec0ae0 *R/difftime.R 4354a2ee95fb4abf5c4b370f4a513197 *R/dots.R d0fb61ee5aabe54ea144d12ec4fb1ffc *R/download.R fdf830020cad72eade4dd58c287fc827 *R/dynamic.R 167b0dcf6f577103a17446f9cf0510bd *R/embed.R 6b2fada02b263fccd9c0409fdd888892 *R/encoding.R a77a7ecdfa47f23cafce5de0801e8f0b *R/ensure.R 727bf9ea9bb4b0794d519eb0fd8cca47 *R/envir.R 3873acbfbc2bd1e3149c5b4e2dbc3642 *R/envvar.R 834a686673aa7241d024864fb0cffbed *R/envvars.R 90a9d251f721baf2528d03afe50638ee *R/equip-macos.R dac14d802593e488d283f479673d7c2e *R/equip.R 5faf3b1bd3961cf6aabb8ffbb3c9dbbe *R/errors.R 52bd1ee318142887bce0bdb3e25fbbb1 *R/expr.R 07d6eff810d97fe1d4a8e74eb9e7f790 *R/ext.R 7ca541176d61b1a12406f28ebc9cde8e *R/extsoft.R e11fab3e0392d4b83a38edbae0246e15 *R/ffi.R 5343270fd4427fdd98537c7e0e19511d *R/filebacked.R f8fbac3f9f1049dab400cb46b4b823e2 *R/files.R 47975d7a9054d3742411c22cacdabcb6 *R/git.R 68b329da9893e34099c7d8ad5cb9c940 *R/github.R ce3cbed9090804d30dd21a15e5e70d46 *R/graph.R f3da0fd54ff76b93400c0f694cf82fab *R/hash.R 63c293c7b5bf5e65dbf90efc4d2b11d3 *R/history.R d3cafca84dbab7b7526f88f06d896661 *R/homebrew.R b9705c69d66b25b9553f9904bb525fa0 *R/http.R 7296d856180cf13b872a119345a2d841 *R/hydrate.R 54140a086184ea3eccc12aa00409f2e6 *R/id.R 2825fff9b1a29a0c22ad1a162c1fdc56 *R/imbue.R ec4abbbb0600a2a1885be45f6dd5a54a *R/imports.R 5672fee0a5ebda976fb9e98feb366558 *R/index.R 29156edf73882121a9d1d6c5296bcb24 *R/infrastructure.R 5f8b067622248f15bc40912f2325a7cc *R/init.R ab01387e9c64928174262634462e93c5 *R/install.R b892edb1633f3b0357d4353f3fb00536 *R/installed-packages.R c0277d55cd747cceea55ca12e7123761 *R/isolate.R aacf922da906b8f541bffae8e36d7cd6 *R/job.R dae0df0bd0901ffe8e34948a6009ae9c *R/json-read.R 4959a87ac335b69dad55650e90e18a48 *R/json-write.R 4273e105111bae33418d9aaa94ade82b *R/json.R 4256ff6a99fdc1e4432752578b0d892a *R/knitr.R e0aa8903561ec828f03162435b2f08a6 *R/l10n.R b165329e710b35d5aaf65f0aeb07ca23 *R/libpaths.R e901a54036d872ed770ee27403e16556 *R/library.R b5a642ea1995b91faeaf244e633f21dd *R/license.R 8d7206b1c3de534e2ba35c4b72a854c9 *R/load.R 2c2262f6ae5782c309554449fb81823d *R/lock.R cc52c2695fb260045ca8506586f1ef96 *R/lockfile-api.R 0d8e81d87b15023039bd915dd3b26920 *R/lockfile-diff.R bf293887a541aa6ed3755d2907bb2e79 *R/lockfile-read.R 2db962571eac20be850ff71a3e87d23c *R/lockfile-validate.R ef7dee8af46e08489c7693233740a708 *R/lockfile-write.R 48f828ae4b1db0b025075e68b484ded0 *R/lockfile.R 61d1d6ed5aa6f8dd777c99cf74dff36c *R/lockfiles.R c7b59e8d7c5f7e7b4dfff25acd512739 *R/log.R b2cce5f66e99528d6f9f59b3a989cf52 *R/manifest-convert.R 7f10787e154ec87742f7fe7d153d807e *R/mapping.R 820442c444250b124587018ae6cec43a *R/mask.R e73d9d5da298a92add51c029840a95ba *R/memoize.R 982c519edf0d1c631639a117dfd8fdf5 *R/metadata.R d270a8fb44736f917fc950d1113e4786 *R/methods.R 1737fbe054dc5125918d1790a1ef05fa *R/migrate.R 099f2760a4cfe9d572e2bdd0a4e3b431 *R/modify.R 9f2439cad0abfc492bcc70da5ec2eabf *R/namespace.R 8d5f58bf4389c1a6b8f01f63e704a099 *R/new.R e2f8a0119fda528fb8955ab1f7523859 *R/nexus.R cb402f9d40e7525341d5b5fd32af6e0b *R/once.R d7e8da9a7fc122484d5b72f19dda2415 *R/options.R d52f7657541d03e6f0a5300ef1731cc6 *R/p3m.R 65805936f0a1c92c05b7ff8948b5ceee *R/package.R 15d8f755e96fa1592ad24c0997769ad9 *R/packages.R f344fa8930084fc22f6ff14f3f91a969 *R/pak.R 3da22adf03b2ee74abeb69f97bdb1e38 *R/parallel.R 6b812c39b3bbfdffb2ce36670a49c3ea *R/parse.R ff1eb6a56d3c022d5bd5cb2614be82dc *R/patch.R 7009c91c21946ad4cc3701a2361eb672 *R/path.R 3a2af80327fb875f1e58d6a1c93940cb *R/paths.R 7b8320e251e71c2ccd6fbd33f8433463 *R/pip.R 8e7b93a64c7fe8db169bbd3e1525a4b9 *R/platform.R 88804ea96bbb4a36ba8851293ccfe334 *R/ppm.R 7572a3eae43ab7182627a57a124ba20e *R/predicate.R f8e60bcd58baa37cf4dfc5b3b780126b *R/preflight.R 054c322a69ba396764b0705b81bb52b2 *R/pretty.R 08e455d42d808dbae37e90ee98ac81f4 *R/process.R 20704240f49b328c32691882e1575a25 *R/profile.R a4dce602579fe3368110a0031ee8e23a *R/progress.R 600cff95e1a66c311499ebefcbc8764c *R/project.R 54d9f5ad90de9c786361bbc9f2a9c60d *R/properties.R 2ecfba9a1d8e4a33411f07b48341f96e *R/purge.R b277511e651ffd01eb3afda94ad0c2c2 *R/pyenv.R 2965da47d459a7d515846923281aaf6a *R/python-conda.R ff1b2582784190a25a777b35e2cc4950 *R/python-virtualenv.R bcdfb58cfd17deb9465c2b1f99dfdb26 *R/python.R 8df674000a1d4464e624f0b6cc4f1990 *R/r.R 719af485300923bfbbe9581d66d355d9 *R/rebuild.R 71f9f7a8c700a55cc594e8035a784ef0 *R/record.R ff960956745314a76d2b7e1aa2773c29 *R/records.R f99013affeea6e4381214f5a802d11c9 *R/recurse.R cf6d249a4e8d5c06b9d36cea98f0f515 *R/refresh.R a3ac62c61a50f8d638dd3271bfe389b4 *R/regexps.R 756fd8e23195245a5be5d2c4b1478b27 *R/rehash.R 78f0181eeb534c8771e2a1578eb9adff *R/release.R 06475b6dbc45bb9472928a500a8c5d07 *R/remotes.R f98296c4d78e0ce931d5e5eda7808ebe *R/remove.R a39f191b89685a860d5fe4d26f1ba1ba *R/renv-package.R 95c32a33a0a733ee4c50eb330b56777e *R/renvignore.R a8cac5e937d7c25da533abab63e4db9b *R/repair.R b1415cbb0c79ec5179e469751dc029a0 *R/report.R 2e248737cdb68098364527b1a27af926 *R/repos.R 64684c00ced62bf8cfcb5510a8b858cc *R/restart.R 50a02346197054e5e5feda9a75a68c73 *R/restore.R 14c5bc736b4e17586ad591df0473cf9a *R/retrieve.R 65c68aa1fd5ba537d7f55db614773d1b *R/revdeps.R e18df07619a5063c9357e8ac9493057d *R/robocopy.R c67eba260c8a285875258665d033b0c7 *R/roxygen.R fad7389a315c2e59deefc6b74924ef36 *R/rstudio.R 3422b4adae70e9fcc7cc9980fb9a8711 *R/rtools.R cb11ba77750c9cf0c63908fea97aa181 *R/run.R 109142d500be96295d74cae7fe2df5ce *R/sandbox.R b7a46c670191a4a5450c2708e0037136 *R/scaffold.R 5592bc86878317983d01bdd3f2f41f1e *R/scope.R cc674c56e11a329dc2beb413151a6490 *R/sdkroot.R b9624c36951baaa8a661b436cba034ee *R/session.R 32f6ea15199976c8c5459d14864f1cc3 *R/settings.R 586310ad170e0d36081346372f6b7808 *R/shell.R ff4e2a18b98e82c1375a360c68ab3242 *R/shims.R 86f8ec7aef1893e68fd27b2413f8174a *R/snapshot-auto.R 0507f832ef21d4bcebe24418fbefc812 *R/snapshot.R dcfe5ff97d110ca2c3a1344f8a329e25 *R/socket.R 2f84840b2fd6025a5f8a04f5dd554dab *R/stack.R b9d4bbf9aac654cedb8e094a91b14333 *R/status.R 95e9305e1abb643a003f36beb5431ce5 *R/sysreqs.R 1cba2a199013fb8a05fa2dfcdc7c074a *R/system.R 31c21ab3c50bb336591a67e2bed472f3 *R/tar.R 338ddd73e08d6154e34d51d1f0bb6bb6 *R/task.R a8ea6e60b3864fa964c389086365fb1b *R/tempdir.R 4c9314be01c17d8faad6ac81a278c351 *R/template.R 141240cbba4dcb6b4b8f90f451dd634e *R/tests.R bc80d3fcfff8e7505361bbd896ebb9a9 *R/testthat-helpers.R 494c087cf3b62201f7cfc2c6fbb02f61 *R/truthy.R d3553c9cb197ccd296fc55486f09eb32 *R/type.R 02470dca901425c8996f84fb8ea13978 *R/unload.R 3d7c85c9737cfdd0c59283349ff3e24b *R/update.R eefa10033c9b07ce5710340fdecc4931 *R/updates.R c1eda85c5261115467ccd537fcd803da *R/upgrade.R 7e25482b5d215605dcc48d699fd5d9c9 *R/url.R caf665ad6254b541e78a2557b8b020ab *R/use-python.R 8b51c3aeed68c22991016aadea4f731d *R/use.R eaf0dcb7e59a5c5ec607455ad95f5de5 *R/utils-connections.R a980c9a47353b0d8df574455aeb8b0d8 *R/utils-format.R b974b9d350a8cfc4af42959936c6fa73 *R/utils-map.R 00ddc9ffaeca8eeb6636cc3b5f9b1d3d *R/utils.R 56fdd203295601551779ba9153b9492a *R/vector.R ac36d82031edb7c7e7539339498077bc *R/vendor.R eaa4f6964c74b5c19c9c9b8149f7a0e0 *R/verbose.R 0b18f9a7bb755cedd5dec63fe137336d *R/version.R ff88a0e567f2c677c7eadc1c602c71fd *R/virtualization.R a14cc1b29a95a5cc36dbddb0954a655b *R/warnings.R 8d8d6167d4e86ceebdaef0948653c986 *R/watchdog-server.R ed26db8f353c17494a5022f63289a44a *R/watchdog.R 3298e40f3b89bcc738448d75d58cba1a *R/xcode.R 6451364369dbcbd3ad310d485ae09a7c *R/yaml.R 433bac5c703270689a6ed3f1d51f5caf *R/zzz-libs.R f2cceb9ff026cb5d50588936a51ea35a *R/zzz.R 286beae286861f34a5e092474b914bc8 *README.md a4643a241b519def9a3fb5491d44494d *build/vignette.rds bbd0f31a99032014b85ee7e57b604da8 *inst/bin/renv 64af3537ec68355c83d8c11a9edc5ace *inst/bin/renv.bat ed79102f3bbeaa1db3cb59472a5d572c *inst/config.yml 5e270ae6034b339c2a13d20a5f7d2dec *inst/doc/ci.R 4667baec3545b17b14b08cb2174b61eb *inst/doc/ci.Rmd ad9c47f5536a3f8ef143da8433b8decc *inst/doc/ci.html 5e270ae6034b339c2a13d20a5f7d2dec *inst/doc/docker.R a733ba7a749bd884071abc41dc6e505c *inst/doc/docker.Rmd 21bb468d2f55a85f614f64abffa4fe67 *inst/doc/docker.html e51cefd9da3945c94b05fb0d9de93dde *inst/doc/faq.R 045a0feaa8a4cb1ac8fc3804e6b53547 *inst/doc/faq.Rmd 13b3d8aee5765c66fac10b1098742f5a *inst/doc/faq.html 9b61388f12cf9c5652a8c783d5be122d *inst/doc/package-install.R 8ba46314bb505ba97a4de0ed31f12536 *inst/doc/package-install.Rmd bf10d7364adf46990ea0d5aec2ed462e *inst/doc/package-install.html 45c2c3dcb9bb620fd8e4b0d60224958c *inst/doc/package-sources.R 019105820fd1e4431f2c725a19c8171a *inst/doc/package-sources.Rmd 5ed8e0a6032242d74cbd7ba5826d7fbb *inst/doc/package-sources.html f5101418889291ac16c8f29a6175792c *inst/doc/packages.R dcc7ee3fbe21f463b453ec267808331c *inst/doc/packages.Rmd 43c1b5ffd7313c10132fb9cdf0f66771 *inst/doc/packages.html f5839f0bc54470abd1ff870714dc719c *inst/doc/packrat.R c64031202c79373629fa9209d0c4c68c *inst/doc/packrat.Rmd b31c489ea4a2e1e38ec1b7f91d44150e *inst/doc/packrat.html 9b5b498f98ccbdde1b8fd9e71cabac2d *inst/doc/profiles.R c81b02cdb060e70555c9e3eb812d0336 *inst/doc/profiles.Rmd c301f8d650a68997f50f7d3bae42eb15 *inst/doc/profiles.html 5e270ae6034b339c2a13d20a5f7d2dec *inst/doc/python.R f236a1978c977ad26d0f471c6a226b3d *inst/doc/python.Rmd c9eeedc715edc6cd9d9dde296fd0d12e *inst/doc/python.html 15cae186d7a4a3ccb0a45415581296ad *inst/doc/renv.R 4bcce0483713046ea3143fb72a9508b0 *inst/doc/renv.Rmd a49b01446fedc04fa837b36d3fd3abe8 *inst/doc/renv.html 5e270ae6034b339c2a13d20a5f7d2dec *inst/doc/rsconnect.R bd9cf6dbccd0d23949d8614f47b0a311 *inst/doc/rsconnect.Rmd cf275db2d13e9aecb383ac1f5e78dd42 *inst/doc/rsconnect.html 2ae06701f8059e053b6862920fcab4b9 *inst/ext/renv.c 7ba2bf981c64e65afaa15f8dee661f37 *inst/repos/src/contrib/PACKAGES 2983a8c98bdc2b8835af68fbb41d7741 *inst/repos/src/contrib/PACKAGES.gz 37e23838cff5a90b9e75c2c6e91e6832 *inst/repos/src/contrib/PACKAGES.rds d8bbfa75ad95e16f3a330e44e8a14836 *inst/repos/src/contrib/renv_1.1.2.tar.gz c852598faf15e9a614eee1ae5933ad09 *inst/resources/WELCOME e908fbdfae0b871133378e5984526536 *inst/resources/activate.R 38ebcc4778f15ad0c0e1cf144f17bf4e *inst/resources/scripts-git-askpass.cmd 0f28660d57f2dd9dad52380e812e83d5 *inst/resources/scripts-git-askpass.sh 63ba0c4b0cfe770b0241478e3e93ef37 *inst/resources/vendor/renv.R d41d8cd98f00b204e9800998ecf8427e *inst/resources/watchdog-process.R 5a30cca829fc56645aa643e1b9b12ae9 *inst/rstudio/addins.dcf 6a3b1c86f4da96e35c3e53285441cad6 *inst/schema/draft-07.renv.lock.schema.json cb53b2a8530cb4f79a591bb130894caa *inst/sysreqs/sysreqs.json 15f87922e8cb3ea699d1f450246efebf *man/activate.Rd 77cdf9b839c2459b53935a2eccfaeddc *man/autoload.Rd 8bc65fd9a61155fd9145dc9d6769808b *man/checkout.Rd 25485cd685957dbec43b29366d209b64 *man/clean.Rd 44730a392c80591eeab22006b8a97d58 *man/config.Rd 4808570f281170923b50c3a3157f2a4d *man/consent.Rd 3ca853e38b1da09b7bbf4c65b94e9c19 *man/dependencies.Rd cedb0605872fc11e449843cd8ac7c92a *man/diagnostics.Rd d6ba6e24bccbcd398c7a6cc797404042 *man/embed.Rd ad44c91ca5fc9577e8b2f33e4795d65c *man/equip.Rd 268d837fc485cdd1a80a48b8dcfde613 *man/figures/logo.svg cfa23fc64d55e1c2773532d7937c76ff *man/graph.Rd 2bea73c482f4e6a296d03f583ac5b7f9 *man/history.Rd 4b36a35d47db23df3e7f474d9717b488 *man/hydrate.Rd 1ad360dcf86672159aa97a7a28150465 *man/imbue.Rd d37ee1cd722ca6a6be9cbd193ffa6678 *man/init.Rd c732a4450505a379046b9c9e0cc610b4 *man/install.Rd 6ceb6e07af3908ae839e6783b7c6ee12 *man/isolate.Rd 1567248857f3b28cd1f2357c15f5e582 *man/load.Rd c501b2bf5156dcee87beb185e4685507 *man/lockfile-api.Rd f3658d3461b931094709c73d42711acc *man/lockfile_validate.Rd 0d5157ba5505e28dcf7c6e37d98be33b *man/lockfiles.Rd 1a3fee98bc5504f06102421807cbc46b *man/migrate.Rd 68288e3da86259acdd6397b4a30aa24b *man/modify.Rd 51ff3c588c6aa7d7927b817d6358e4ad *man/paths.Rd 94318761523a7d7e2c6d093ff2a1f381 *man/project.Rd 99640368ef9e45adc3c868f666cce0a0 *man/purge.Rd cff0e7900e9cdb212f18e9275364e698 *man/rebuild.Rd 2c6a3b5ee0c779e1376466eb2f3b9b65 *man/record.Rd 7440439bb01a617eaea4d9515160c236 *man/refresh.Rd 8a3f7697aa3ff8731e1c8464ad30d31e *man/rehash.Rd d4b32a322eedf73d46f0728f4d383777 *man/remote.Rd 6248020c66483e23a4e6a1ea783e4d46 *man/remove.Rd 6c90861ded8d80f80abdeeb860c0bc6c *man/renv-package.Rd 34d8155f750fe0e20a52b2ad18866b76 *man/renv_lockfile_from_manifest.Rd 2bd8cc3cad202a405b69dbd684c328b8 *man/repair.Rd 3fe186f88bfa1d9a2e731abed903cae9 *man/restore.Rd 82b4fe61536186f25d22b6c2c0a38518 *man/retrieve.Rd 060047562ada33058282fe1fe2c77819 *man/run.Rd dad9cdc5b855dcfe4fa0c5a1a59bdf94 *man/sandbox.Rd b9e2763ab4b8b82e9d6fcb52036c1189 *man/scaffold.Rd 79781f9d15670439961f4c944bc5b869 *man/settings.Rd 791102a13ee52d39debb541a07664e3a *man/snapshot.Rd 263c86b0d4f668275f62d69ee78f10c4 *man/status.Rd b31be928b300f853f70ce2131e7cc720 *man/sysreqs.Rd 81971157b25c7afe569deb7e018f8c99 *man/update.Rd f10e9b9e7d168ec61f432ec466b17920 *man/upgrade.Rd 27b365daf754f0a352533181256797e3 *man/use_python.Rd e56d66a731422646bc5d50a97b4bce20 *man/vendor.Rd 84e12495b35b34ba23be8f230d3c714d *tests/testthat.R a522d42399a4752b947f493505d49766 *tests/testthat/_snaps/activate.md 10f7b4f8d0f44a03f41a4efe5679ed91 *tests/testthat/_snaps/bootstrap.md b84cf15c7ea06cdeb05569010cd6719e *tests/testthat/_snaps/caution.md 955552a22eb3eb4922b118b8c0b41063 *tests/testthat/_snaps/dependencies.md 667cb01ab01f8d9ab702d51de59d953a *tests/testthat/_snaps/dots.md 11cad668d39c51954d4090b6b478ed3e *tests/testthat/_snaps/init.md 221a1b45b7e5f760290994495b69d973 *tests/testthat/_snaps/install.md 7a64e51cdf1666d3a2c8f8e350fcf9f1 *tests/testthat/_snaps/load.md 47a992d858144eac4ade9d38fc45804e *tests/testthat/_snaps/lockfile-read.md 66a5fd8756d9a9eae68c709c1315d7f2 *tests/testthat/_snaps/preflight.md 415323c3650c5efaf41b4936ee652d4a *tests/testthat/_snaps/repair.md 1b5f58d7855444dfc3f4c09c66267214 *tests/testthat/_snaps/snapshot.md 3cd157ede8968dd44986f9f87bd4a6d1 *tests/testthat/_snaps/status.md 44228f89122296afa253746b94c11902 *tests/testthat/helper-aaa.R 55a6b15026da541d35ed445f327ae0e8 *tests/testthat/helper-interactive.R 99fe6e0cc5dc350ac6d0bf74d7103e6c *tests/testthat/helper-scope.R 72c87f8b4d6dd458921f41798e44fcc9 *tests/testthat/helper-setup.R 4198b2cc977c55f04875b2accabffefb *tests/testthat/helper-skip.R 26ecc8be3eada09ae0b58e5901802e4c *tests/testthat/helper-slow.R 86760bd6f8e475f93ee94d5efe1ced1b *tests/testthat/helper-snapshot.R 4fc7e929b48406b49dbcd0d8f9cb2975 *tests/testthat/helper-testthat.R 3f4f59d7539c506d2ab77b0ec3432ee2 *tests/testthat/helper-watchdog.R a297c8d98ef876f43b3823af46ddbd97 *tests/testthat/helper-zzz.R 42b1ae313b4736bf34eb9e7a00e2e9ae *tests/testthat/local/skeleton/skeleton_1.0.0.tar.gz 699d76aa165c08e796aa4078a80a7078 *tests/testthat/local/skeleton/skeleton_1.0.1.tar.gz 915cb04675aabe78269dac78df2223e7 *tests/testthat/packages/bread/DESCRIPTION b96f328502fa0f1b1a737f2fe505e3fd *tests/testthat/packages/breakfast/DESCRIPTION a7bee8f8c4813a45eef6f7fc8d31ede4 *tests/testthat/packages/brunch/DESCRIPTION 0017c18d55523785f25e8526a0aee95c *tests/testthat/packages/egg/DESCRIPTION ab251f4b409074f1c549ea5da1bfc0e3 *tests/testthat/packages/future/DESCRIPTION b9c611aa752a6a474089ccdd766e4cda *tests/testthat/packages/halloween/DESCRIPTION 0a5d59dcd5fe0865532d37fa86f0b9a7 *tests/testthat/packages/jamie/DESCRIPTION 85b6479daa199f3167a6d63f7c617dd9 *tests/testthat/packages/kevin/DESCRIPTION 9b8c86b88ddca962e06e96642c8a6536 *tests/testthat/packages/oatmeal/DESCRIPTION 04a38ec8d56773b609d67291d7c63a22 *tests/testthat/packages/packrat/DESCRIPTION 8ceb37e28be5038f42b68481e87df8b4 *tests/testthat/packages/packrat/README 30837a7ac762e06631ca82bb1ca61c95 *tests/testthat/packages/phone/DESCRIPTION 56f0d499c1a2a6b49638e416d19a1cdc *tests/testthat/packages/renv/DESCRIPTION f8529a38f614a14e8340f7179ff0c231 *tests/testthat/packages/toast/DESCRIPTION 6c4bccbdf8cb642979a89b880a3b3a9e *tests/testthat/packages/today/DESCRIPTION d5a75c5b09075ae51e43e29d1a7ff0ab *tests/testthat/packages/unixonly/DESCRIPTION d2bb98a5da8ac9d83f022c2554be1802 *tests/testthat/packages/windowsonly/DESCRIPTION 468418b29a26d2ccaf9b03cbffb857d2 *tests/testthat/resources/DESCRIPTION 7a92b6480fb47f41b7cae3cee81d899a *tests/testthat/resources/bioconductor.lock 082eeabeb97d9cd4bda4ad7d6588046d *tests/testthat/resources/box.R db5a9d1b695699fb92fcf91f51a02bf5 *tests/testthat/resources/bslib.Rmd f03d606cb7fc456dedf3bb7e3524e9ee *tests/testthat/resources/chunk-errors.Rmd 8360e80848d7d544ddd1f1e30f11beed *tests/testthat/resources/chunk-eval.Rmd 1660bdb268f045754aa9c2abc86314af *tests/testthat/resources/chunk-yaml.Rmd 3036fa0b757e5a9226d7b70dbb406fee *tests/testthat/resources/code.R 72696513dec25876e5c26540d53d09a4 *tests/testthat/resources/empty-chunk.Rmd 30a4447d60f43fcba61d6c9780bcf82e *tests/testthat/resources/empty-label.Rmd cdf80fc720b2bd26a670ac77c43c3696 *tests/testthat/resources/eval.Rmd ae769b29090ed5560026983ec96e1ba2 *tests/testthat/resources/evil.Rmd 1eda05269e06e0644b0c3261fe4abd47 *tests/testthat/resources/glue.R 5b061b3b76dd9a899c3faee5c55ec969 *tests/testthat/resources/ignore.Rmd 07e7d80bd1911aebaf28e5338b5e761e *tests/testthat/resources/import.R fd0eadb6efacd11a7ede4c17009cc4ce *tests/testthat/resources/inline-chunks.Rmd 9b42c20afbe0993ba81d413a4844fca3 *tests/testthat/resources/knitr-reused-chunks.Rmd 88b35923d1f2d71bd83ca4dac1f72bf0 *tests/testthat/resources/knitr-spin.R fb5f3cfd6e916899ce984a56b20b9daf *tests/testthat/resources/learnr-exercise.Rmd 39138917b7f030358c933af0687aa7aa *tests/testthat/resources/magrittr.R 6a21f02c45a10fb799537428a28fd540 *tests/testthat/resources/manifest.json 424ae5d2f2c15519d180c66f3b1e3f54 *tests/testthat/resources/modules-empty.R 9012ec4a593d3f028851fe557f31d322 *tests/testthat/resources/modules.R 6ca46dd921e77d444bc35181748ff2a3 *tests/testthat/resources/multiple-output-formats.Rmd c7aab7b798da0946338b038bdd962fb5 *tests/testthat/resources/notebook.ipynb 2d930ff8329f5ddeac0a56b673b66b44 *tests/testthat/resources/pacman.R 24220863395f470c7a07e3660940408e *tests/testthat/resources/params.Rmd 57b41b394a879456ac756543e399868e *tests/testthat/resources/parsnip.R b11c70e6b1fe50d7c1dcbac422a508bd *tests/testthat/resources/properties.txt 9c81b694ddbe48b42b84c82810434202 *tests/testthat/resources/quarto-empty.qmd 91e15224851456b3d9f84987e829d8d6 *tests/testthat/resources/quarto-r-chunks.qmd b914a832be0dce05c933043a36b95413 *tests/testthat/resources/rmd-base-format.Rmd 3bdfb5be8b5a9f5c15d4bcca628bc380 *tests/testthat/resources/shiny-prerendered.Rmd 537245781355d5ab959030df56bd3952 *tests/testthat/resources/targets.R 2d99f765d6536c02b297c06df5601a07 *tests/testthat/resources/utility a73cd986907f65ddfca49ed8b9ac9a89 *tests/testthat/resources/yaml-chunks.Rmd 6e55aadff2d7e4cbf3e1978b9bec2a43 *tests/testthat/test-acls.R 14aa7f8d1382e395cf4af2b9e9512fc1 *tests/testthat/test-actions.R 1b8b5fbb0d59d419895612c9ee2fa846 *tests/testthat/test-activate.R 9fa22e8f87115033b301f867d2b6bbc6 *tests/testthat/test-archive.R 4a4ef7fb3d01e6f8afd3be68b36bd60e *tests/testthat/test-authentication.R d6fc9ae828e82898ff8949470f05fcd5 *tests/testthat/test-autoload.R 057ec4e648112c252c041a66d1ef599f *tests/testthat/test-available-packages.R 104ef1fbe1db8b3a32756916ea36af98 *tests/testthat/test-base64.R 81c5e2612d6ef43aba6c9dba8073f95e *tests/testthat/test-bind.R d41d8cd98f00b204e9800998ecf8427e *tests/testthat/test-binding.R 1f54e8d44169429b06e429e923a16f70 *tests/testthat/test-bioconductor.R 55b6ee8198de0688efe9b2234926c633 *tests/testthat/test-bootstrap.R a8a0ff36a2a877573238aa18591e26f4 *tests/testthat/test-cache.R 6d6032221fe017477caaa00039bff778 *tests/testthat/test-call.R 41d06c4eb786533b4dcb0d8744588c3e *tests/testthat/test-caution.R 52fc27a46d0fb3e94f021ad44262665c *tests/testthat/test-cellar.R 39334513c1472164007ab974d5e6d9bb *tests/testthat/test-checkout.R 21baadf0ea0427719e5f93be9f2f4fb4 *tests/testthat/test-clean.R 3f4178047417f4ee0eb30eeb995b6ecf *tests/testthat/test-cleanse.R cc51623e5df6b3f278684fd3a4e09fa5 *tests/testthat/test-config.R ad4db01224cc269d1d6405c270f740d8 *tests/testthat/test-consent.R 13ced21728f0e221e417ffa35bb01343 *tests/testthat/test-dcf.R 76cdb82d8b433e107597ae2259aba474 *tests/testthat/test-deactivate.R a56a402ee5ca93a0b311cf45b9bd6f5a *tests/testthat/test-defer.R ada680fe8084f59c8ade502ce91cd4c8 *tests/testthat/test-dependencies.R 60a37ec02af29de60049166e4c83ef4a *tests/testthat/test-description.R da500d08110074d29da0522cb8c8cea5 *tests/testthat/test-dots.R d71d556a47b8d5a0c002f97f0e7189df *tests/testthat/test-download.R 6754b7b6e57b2e1dd776cbe10c00076b *tests/testthat/test-dynamic.R cdff2be36a9cf76bd0ff2377e624345d *tests/testthat/test-enumerate.R 888a3d35f5fe491632bf2cff0b7a58ea *tests/testthat/test-envvar.R eaca8d6bb018ebc08ff07b58bad11315 *tests/testthat/test-envvars.R 1616323a4869745c062a452833bc609a *tests/testthat/test-errors.R 1708b62576df87d09531ed93ca8a745f *tests/testthat/test-expr.R f33c69fda289812737da5efb3aa4e494 *tests/testthat/test-ffi.R 4e829a1680036be1def5c1460e077316 *tests/testthat/test-filebacked.R 12fa3f889e746edf8641dca824f1fd26 *tests/testthat/test-files.R 9c29b955b41daf521242bb49ae9ad90d *tests/testthat/test-hash.R f64975865495a0e82690b28b9cc4b256 *tests/testthat/test-history.R 2015efc09aef11df0301ea1c754030b9 *tests/testthat/test-http.R 430332f3aab3cf72749601eb5f92a0aa *tests/testthat/test-hydrate.R 192409a509609d8577292f0b8e791667 *tests/testthat/test-id.R 5bf0eb51d2d7d9ec5ac01533e41e8596 *tests/testthat/test-index.R 74c7789d6d0859009533ff335a57c048 *tests/testthat/test-infrastructure.R 5351e81ce66dd1ef1708513285f6b2a3 *tests/testthat/test-init.R 1977b03a3a599ea44dbf2bf24deece95 *tests/testthat/test-install.R 05b8167d3563475747e3087f351e5746 *tests/testthat/test-internal.R e70dade08da06b0920cf17d2a0707f2b *tests/testthat/test-job.R 6527b4a26f08d518b329f51d23aca166 *tests/testthat/test-json.R 2f9eda06c90e9570370b2bc3621a6a38 *tests/testthat/test-load.R 8b95372959eb981a4b513cc896bb6a7a *tests/testthat/test-lock.R caf07e7ec72e26068673ab3cf44ff5eb *tests/testthat/test-lockfile-read.R e68eaaf5af645a5a7b922ccfb99c3ffa *tests/testthat/test-lockfile-validate.R debf0243661f8f07acd9223e99936214 *tests/testthat/test-lockfile.R 0d89546c5150cd314320afe65d94550d *tests/testthat/test-memoize.R 2c043331b226d80f103de81056f90928 *tests/testthat/test-metadata.R ad18405db8a7045652b043ddda7c5853 *tests/testthat/test-migrate.R 69a6be840b79a7fedc48b31cfdbd9114 *tests/testthat/test-miscellaneous.R b8dbe33a74ea6e2a41b32c1a92569220 *tests/testthat/test-modify.R c04fe7cca49663c0fc7826480d4b0857 *tests/testthat/test-mran.R ab6782eaedc44003bf7d1417b32cb45f *tests/testthat/test-once.R 8e947cfb288bb22cc386f3e3a6c1e6d8 *tests/testthat/test-packages.R 3e32fc7ebf7f9f7633d739b5c58cb934 *tests/testthat/test-pak.R 835801d696e2392ea383c8bcbf1078c8 *tests/testthat/test-parse.R 862a2ac040dacdc33ca6868442989686 *tests/testthat/test-patches.R b3235e0978edaf9e34aa0d8716c0188a *tests/testthat/test-path.R e1de75e75626d9846648361de2fcccf7 *tests/testthat/test-paths.R 582d6dfe0b26eaf3e75ea8db4e792daa *tests/testthat/test-platform.R bbb78e2fb673e451fdb4a14054473a42 *tests/testthat/test-ppm.R e001714bc7dae44a4b1da81557270ea6 *tests/testthat/test-preflight.R e289618d14559225cfd60cf0a809124d *tests/testthat/test-profile.R 1b644abc1fd36e1fe8fa618ae4126755 *tests/testthat/test-profiles.R 3db9f25c4066a1cac652363e3d28c0b9 *tests/testthat/test-properties.R 0a1ebe2792d918a69ee43a0ebbfea302 *tests/testthat/test-purge.R 16fd4d9b32048e52d3676352b805a044 *tests/testthat/test-python.R a087dbb445eaf6d9aaafdd2f476a1a09 *tests/testthat/test-r.R 6128261a4992ef0cb27beaed300e4efe *tests/testthat/test-rebuild.R 5635aa1cb104d713df3d4991371166de *tests/testthat/test-record.R dbd6f5f51832eac82c7a96d2e6efe396 *tests/testthat/test-records.R 576b50e66c038509155ab07f31d0aa1e *tests/testthat/test-recurse.R aa519340f01f1fe2b26f3e0330448efd *tests/testthat/test-rehash.R 175c4334e6b37703cdcad033657223f2 *tests/testthat/test-reload.R e81768334b86ed2bdff461dc2953f564 *tests/testthat/test-remotes.R ce682aa751891ba3bd2d76722a5d6aaf *tests/testthat/test-renvignore.R 2fcee48c3c55cb3d734bd21411b26ef9 *tests/testthat/test-repair.R 5d88ba366a05caf71cf5c29cf1bde3e3 *tests/testthat/test-repos.R 97c86dd2698d2106c0569a144c20d302 *tests/testthat/test-restore.R e06867136f60f0e3ed24e627c96fb170 *tests/testthat/test-retrieve.R c94b452d1789c4b1c942fac23d8b7be7 *tests/testthat/test-rmd.R e398fc0ac04c8297d6ee3c6959fb7935 *tests/testthat/test-run.R 2d11fa03a9233ca93ac5b05547856092 *tests/testthat/test-sandbox.R 7889096fa752322ceffd0b475a5b0322 *tests/testthat/test-scaffold.R 6417d123dde5532c02f8cda378b3a9cc *tests/testthat/test-scope.R c964acb4357273a8623edc2dcf3349dc *tests/testthat/test-settings.R c50eeeb96716263d4862c751a6c5b421 *tests/testthat/test-snapshot.R a7414596312d3f5aa9057214e5728e2a *tests/testthat/test-socket.R e2bb867ea64312abe05aa1c543c8b9e3 *tests/testthat/test-status.R 9d91cb0f29dfdf7a76d95d924635f1a9 *tests/testthat/test-sysreqs.R f50e83b12f2dd47616d455e5a6e445da *tests/testthat/test-system.R 3e2c3e76070e6a4861ac2e7284ed2932 *tests/testthat/test-truthy.R 6a7de8aea6dfe222dbb82c8e56909e1a *tests/testthat/test-update.R b1882b374747f6fb0b5ee9c89a45e602 *tests/testthat/test-upgrade.R 2a14e286c7e4dbd093e52026f5aa9ad2 *tests/testthat/test-url.R 417baab51f2d8c513296445c49439750 *tests/testthat/test-use.R d492e7e08899053342daa360ad0dc3bd *tests/testthat/test-utils.R e2778186c30980ef73c3bbaf86fe3bf7 *tests/testthat/test-vendor.R ced84e0e27b8c548866fb59b801b08f9 *tests/testthat/test-version.R 287f1c86a6f9ecdbe8dd030699d3b51b *tests/testthat/test-watchdog.R 4667baec3545b17b14b08cb2174b61eb *vignettes/ci.Rmd a733ba7a749bd884071abc41dc6e505c *vignettes/docker.Rmd 045a0feaa8a4cb1ac8fc3804e6b53547 *vignettes/faq.Rmd 48844afc4d13518c5fd83f21233bb9d5 *vignettes/ide-publish.png 8ba46314bb505ba97a4de0ed31f12536 *vignettes/package-install.Rmd 019105820fd1e4431f2c725a19c8171a *vignettes/package-sources.Rmd dcc7ee3fbe21f463b453ec267808331c *vignettes/packages.Rmd c64031202c79373629fa9209d0c4c68c *vignettes/packrat.Rmd c81b02cdb060e70555c9e3eb812d0336 *vignettes/profiles.Rmd f236a1978c977ad26d0f471c6a226b3d *vignettes/python.Rmd 4bcce0483713046ea3143fb72a9508b0 *vignettes/renv.Rmd 0882e6085b1cb3be223124300eb978b2 *vignettes/renv.png bd9cf6dbccd0d23949d8614f47b0a311 *vignettes/rsconnect.Rmd renv/R/0000755000176200001440000000000014761167213011435 5ustar liggesusersrenv/R/ffi.R0000644000176200001440000000165314731330073012322 0ustar liggesusers `__ffi__enumerate` <- function(x, f, ..., FUN.VALUE = NULL) { f <- match.fun(f) .Call( "renv_ffi__enumerate", x, FUN.VALUE, environment(), PACKAGE = "renv" ) } `__ffi__recurse` <- function(object, callback, ...) { callback <- match.fun(callback) .Call( "renv_ffi__recurse", object, callback, environment(), PACKAGE = "renv" ) } `__ffi__renv_call_expect` <- function(node, package, methods) { .Call( "renv_ffi__renv_call_expect", node, as.character(package), as.character(methods), PACKAGE = "renv" ) } `__ffi__renv_dependencies_recurse` <- function(object, callback) { symbol <- as.symbol(names(formals(args(callback)))[[1L]]) expr <- body(callback) envir <- new.env(parent = environment(callback)) .Call( "renv_ffi__renv_dependencies_recurse", object, symbol, expr, envir, PACKAGE = "renv" ) } renv/R/caution.R0000644000176200001440000000221214761163114013213 0ustar liggesusers caution <- function(fmt = "", ..., con = stdout()) { enabled <- getOption("renv.caution.verbose", default = TRUE) if (!is.null(fmt) && enabled) writeLines(sprintf(fmt, ...), con = con) } bulletin <- function(preamble = NULL, values = NULL, postamble = NULL, ..., bullets = TRUE, emitter = NULL) { if (empty(values)) return(invisible()) renv_dots_check(...) lines <- c( if (length(preamble)) paste(preamble, collapse = "\n"), if (bullets) paste("-", values, collapse = "\n") else paste(values, collapse = "\n"), if (length(postamble)) paste(postamble, collapse = "\n"), "" ) text <- paste(lines, collapse = "\n") renv_caution_impl(text, emitter) } renv_caution_impl <- function(text, emitter = NULL) { # NOTE: Used by vetiver, so perhaps is part of the API. # We should think of a cleaner way of exposing this. # https://github.com/rstudio/renv/issues/1413 emitter <- emitter %||% { getOption("renv.pretty.print.emitter", default = caution) } emitter(text) invisible(NULL) } renv/R/report.R0000644000176200001440000000072314731330073013066 0ustar liggesusers renv_report_ok <- function(message, elapsed = 0, verbose = FALSE) { # handle verbose printing first if (verbose) { fmt <- "- OK [%s in %s]" return(writef(fmt, message, renv_difftime_format_short(elapsed))) } # treat 'quick' times specially if (!testing() && elapsed < 0.1) return(writef("OK [%s]", message)) # otherwise, report step with elapsed time fmt <- "OK [%s in %s]" writef(fmt, message, renv_difftime_format_short(elapsed)) } renv/R/files.R0000644000176200001440000004112014740260564012660 0ustar liggesusers # NOTE: all methods here should either return TRUE if they were able to # operate successfully, or throw an error if not # # TODO: some of these operations are a bit racy renv_file_preface <- function(source, target, overwrite) { callback <- function() {} if (!renv_file_exists(source)) stopf("source file '%s' does not exist", source) if (overwrite) callback <- renv_file_backup(target) if (renv_file_exists(target)) stopf("target file '%s' already exists", target) callback } renv_file_copy <- function(source, target, overwrite = FALSE) { if (renv_file_same(source, target)) return(TRUE) callback <- renv_file_preface(source, target, overwrite) defer(callback()) # check to see if we're copying a plain file -- if so, things are simpler if (dir.exists(source)) renv_file_copy_dir(source, target) else renv_file_copy_file(source, target) } renv_file_copy_file <- function(source, target) { # copy to temporary path tmpfile <- renv_scope_tempfile(".renv-copy-", tmpdir = dirname(target)) status <- catchall(file.copy(source, tmpfile)) if (inherits(status, "condition")) stop(status) # move from temporary path to final target status <- catchall(renv_file_move(tmpfile, target)) if (inherits(status, "condition")) stop(status) # validate that the target file exists if (!renv_file_exists(target)) { fmt <- "attempt to copy file %s to %s failed (unknown reason)" stopf(fmt, renv_path_pretty(source), renv_path_pretty(target)) } invisible(TRUE) } renv_file_copy_dir_robocopy <- function(source, target) { renv_robocopy_copy(source, target) } # TODO: the version of rsync distributed with macOS # does not reliably copy file modified times, etc. renv_file_copy_dir_rsync <- function(source, target) { source <- sub("/*$", "/", source) flags <- if (renv_platform_macos()) "-aAX" else "-a" args <- c(flags, renv_shell_path(source), renv_shell_path(target)) renv_system_exec("rsync", args, action = "copying directory") } renv_file_copy_dir_cp <- function(source, target) { # ensure 'source' ends with a single trailing slash source <- sub("/*$", "/", source) # ensure tildes are path-expanded source <- path.expand(source) target <- path.expand(target) # build 'cp' arguments args <- c("-PR", renv_shell_path(source), renv_shell_path(target)) # execute command renv_system_exec("cp", args, action = "copying directory") } renv_file_copy_dir_r <- function(source, target) { # create sub-directory to host copy attempt tempdir <- renv_scope_tempfile(".renv-copy-", tmpdir = dirname(target)) ensure_directory(tempdir) # attempt to copy to generated folder status <- catchall( file.copy( source, tempdir, recursive = TRUE, copy.mode = TRUE, copy.date = TRUE ) ) if (inherits(status, "error")) stop(status) # R will copy the directory to a sub-directory in the # requested folder with the same filename as the source # folder, so peek into that folder to grab it and rename tempfile <- file.path(tempdir, basename(source)) status <- catchall(renv_file_move(tempfile, target)) if (inherits(status, "condition")) stop(status) } renv_file_copy_dir_impl <- function(source, target) { methods <- list( cp = renv_file_copy_dir_cp, r = renv_file_copy_dir_r, robocopy = renv_file_copy_dir_robocopy, rsync = renv_file_copy_dir_rsync ) copy <- config$copy.method() if (is.function(copy)) return(copy(source, target)) method <- methods[[tolower(copy)]] if (!is.null(method)) return(method(source, target)) if (renv_platform_windows()) renv_file_copy_dir_robocopy(source, target) else if (renv_platform_unix()) renv_file_copy_dir_cp(source, target) else renv_file_copy_dir_r(source, target) file.exists(target) } renv_file_copy_dir <- function(source, target) { # create temporary sub-directory tmpdir <- dirname(target) ensure_directory(tmpdir) tempdir <- renv_scope_tempfile(".renv-copy-", tmpdir = tmpdir) # copy to that directory status <- catchall(renv_file_copy_dir_impl(source, tempdir)) if (inherits(status, "condition")) stop(status) # move directory to final location status <- catchall(renv_file_move(tempdir, target)) if (inherits(status, "condition")) stop(status) # validate that the target file exists if (!renv_file_exists(target)) { fmt <- "attempt to copy directory %s to %s failed (unknown reason)" stopf(fmt, renv_path_pretty(source), renv_path_pretty(target)) } invisible(TRUE) } renv_file_move <- function(source, target, overwrite = FALSE) { if (renv_file_same(source, target)) return(TRUE) callback <- renv_file_preface(source, target, overwrite) defer(callback()) # first, attempt to do a plain rename # use catchall since this might fail for e.g. cross-device links # (note that junction points on Windows will be copies as-is) move <- catchall(file.rename(source, target)) if (renv_file_exists(target)) return(TRUE) # expand tildes source <- path.expand(source) target <- path.expand(target) # on unix, try using 'mv' command directly # (can handle cross-device copies / moves a bit more efficiently) if (renv_platform_unix()) { args <- c(renv_shell_path(source), renv_shell_path(target)) status <- catchall(system2("mv", args, stdout = FALSE, stderr = FALSE)) if (renv_file_exists(target)) return(TRUE) } # on Windows, similarly try 'robocopy' command # (should be faster than 'move' for large directories) if (renv_platform_windows()) { status <- catchall(renv_robocopy_move(source, target)) if (renv_file_exists(target)) return(TRUE) } # nocov start # rename failed; fall back to copying # (and be sure to remove the source file / directory on success) copy <- catchall(renv_file_copy(source, target, overwrite = overwrite)) if (identical(copy, TRUE) && file.exists(target)) { unlink(source, recursive = TRUE) return(TRUE) } # rename and copy both failed: inform the user fmt <- stack() fmt$push("could not copy / move file '%s' to '%s'") if (inherits(move, "condition")) fmt$push(paste("move:", conditionMessage(move))) if (inherits(copy, "condition")) fmt$push(paste("copy:", conditionMessage(copy))) text <- paste(fmt$data(), collapse = "\n") stopf(text, source, target) # nocov end } renv_file_link <- function(source, target, overwrite = FALSE) { if (renv_file_same(source, target)) return(TRUE) callback <- renv_file_preface(source, target, overwrite) defer(callback()) if (renv_platform_windows()) { # use junction points on Windows by default as symlinks # are unreliable / un-deletable in some circumstances status <- catchall(Sys.junction(source, target)) if (identical(status, TRUE)) return(TRUE) # if Sys.junction() fails, it may leave behind an empty # directory. this may occur if the source and target files # reside on different volumes. either way, remove an empty # left-behind directory on failure unlink(target, recursive = TRUE, force = TRUE) } else { # on non-Windows, we can try to create a symlink status <- catchall(file.symlink(source, target)) if (identical(status, TRUE)) return(TRUE) } # all else fails, just perform a copy renv_file_copy(source, target, overwrite = overwrite) } renv_file_junction <- function(source, target) { if (!renv_platform_windows()) stopf("'renv_file_junction()' is only available on Windows") if (renv_file_exists(target)) stopf("file '%s' already exists") status <- catchall(Sys.junction(source, target)) if (inherits(status, "condition")) { unlink(target, recursive = TRUE, force = TRUE) stop(status) } TRUE } renv_file_same <- function(source, target) { # if the paths are the same, we can return early if (identical(source, target)) return(TRUE) # check to see if they're equal after normalization # (e.g. for symlinks pointing to same file) source <- renv_path_normalize(source) target <- renv_path_normalize(target) if (identical(source, target)) return(TRUE) # for hard links + junction points, it's difficult to detect # whether the two files point to the same object; use some # heuristics to guess (note that these aren't perfect) sinfo <- renv_file_info(source) if (is.na(sinfo$isdir)) return(FALSE) tinfo <- renv_file_info(target) if (is.na(tinfo$isdir)) return(FALSE) # NOTE: we intentionally exclude 'size' for directories, as # on Windows the 'size' field might be reported as 0 for # junction points? if (renv_platform_windows()) { if (sinfo$isdir || tinfo$isdir) sinfo$size <- tinfo$size <- 0L } identical(c(sinfo), c(tinfo)) } # NOTE: returns a callback which should be used in e.g. an defer handler # to restore the file if the attempt to update the file failed renv_file_backup <- function(path) { # if no file exists then nothing to backup if (!renv_file_exists(path)) return(function() {}) # normalize the path (since the working directory could change # by the time the callback is invoked). note that the file may # be a broken symlink so construct the path by normalizing the # parent directory and building path relative to that parent <- renv_path_normalize(dirname(path), mustWork = TRUE) path <- file.path(parent, basename(path)) # attempt to rename the file pattern <- sprintf(".renv-backup-%i-%s", Sys.getpid(), basename(path)) tempfile <- tempfile(pattern, tmpdir = dirname(path)) if (!renv_file_move(path, tempfile)) stopf("couldn't rename '%s' prior to transaction", renv_path_pretty(path)) # return callback that will restore if needed function() { if (!renv_file_exists(path)) renv_file_move(tempfile, path) else unlink(tempfile, recursive = TRUE) } } renv_file_info <- function(paths, extra_cols = FALSE) { suppressWarnings(file.info(paths, extra_cols = extra_cols)) } renv_file_mode <- function(paths) { suppressWarnings(file.mode(paths)) } # NOTE: returns true for files that are broken symlinks renv_file_exists <- function(path) { if (renv_platform_windows()) renv_file_exists_win32(path) else renv_file_exists_unix(path) } renv_file_exists_win32 <- function(path) { file.exists(path) } renv_file_exists_unix <- function(path) { !is.na(Sys.readlink(path)) | file.exists(path) } renv_file_list <- function(path, full.names = TRUE) { # list files files <- renv_file_list_impl(path) # NOTE: paths may be marked with UTF-8 encoding; # if that's the case we need to use paste rather # than file.path to preserve the encoding if (full.names && length(files)) files <- paste(path, files, sep = "/") files } renv_file_list_impl <- function(path) { if (renv_platform_unix()) renv_file_list_impl_unix(path) else renv_file_list_impl_win32(path) } renv_file_list_impl_unix <- function(path) { list.files(path, all.files = TRUE, no.. = TRUE) } # nocov start renv_file_list_impl_win32 <- function(path) { # first, try a plain list.files to see if we can get away with that files <- list.files(path, all.files = TRUE, no.. = TRUE) if (!any(grepl("?", files, fixed = TRUE))) return(files) # otherwise, try some madness ... # # change working directory (done just to avoid encoding issues # when submitting path to cmd shell) renv_scope_wd(path) # NOTE: a sub-shell is required here in some contexts; e.g. when running # tests non-interactively or building in the RStudio pane command <- paste(comspec(), "/U /C dir /B") conn <- pipe(command, open = "rb", encoding = "native.enc") defer(close(conn)) # read binary output from connection output <- stack() while (TRUE) { data <- readBin(conn, what = "raw", n = 1024L) if (empty(data)) break output$push(data) } # join into single raw vector encoded <- unlist(output$data(), recursive = FALSE, use.names = FALSE) # convert raw data (encoded as UTF-16LE) to UTF-8 converted <- iconv(list(encoded), from = "UTF-16LE", to = "UTF-8") # split on (Windows) newlines paths <- strsplit(converted, "\r\n", fixed = TRUE)[[1]] # just in case? paths[nzchar(paths)] } # nocov end renv_file_type <- function(paths, symlinks = TRUE) { info <- renv_file_info(paths) types <- character(length(paths)) types[info$isdir %in% FALSE] <- "file" types[info$isdir %in% TRUE ] <- "directory" if (symlinks && !renv_platform_windows()) { links <- Sys.readlink(paths) types[!is.na(links) & nzchar(links)] <- "symlink" } types } # nocov start renv_file_edit <- function(path) { # https://github.com/rstudio/renv/issues/44 dlls <- getLoadedDLLs() if (is.null(dlls[["(embedding)"]])) return(utils::file.edit(path)) routines <- getDLLRegisteredRoutines("(embedding)") routine <- routines[[".Call"]][["rs_editFile"]] if (is.null(routine)) return(utils::file.edit(path)) do.call(.Call, list(routine, path, PACKAGE = "(embedding)")) } # nocov end renv_file_find <- function(path, predicate) { # canonicalize path # (note: don't normalize as we don't want to follow symlinks) path <- renv_path_canonicalize(path) parent <- dirname(path) # compute number of slashes # (avoid searching beyond home directory, unless we're virtualized) virtualized <- renv_virtualization_type() != "native" slashes <- gregexpr("/", path, fixed = TRUE)[[1L]] n <- length(slashes) - if (virtualized) 0L else 2L for (i in 1:n) { if (file.exists(path)) { status <- predicate(path) if (!is.null(status)) return(status) } path <- parent parent <- dirname(path) } predicate(path) } renv_file_read <- function(path) { renv_scope_options(warn = -1L) contents <- readLines(path, warn = FALSE, encoding = "UTF-8") paste(contents, collapse = "\n") } renv_file_shebang <- function(path) { # NOTE: we use 'condition' as a cheap way to capture both errors and warnings # since 'file()' may just report a warning rather than an error if it fails # to open a file due to inadequate permissions tryCatch( renv_file_shebang_impl(path), condition = function(e) "" ) } renv_file_shebang_impl <- function(path) { renv_scope_options(warn = -1L) # open connection to file con <- file(path, open = "rb", encoding = "native.enc") defer(close(con)) # validate file starts with '#!' -- read using 'raw' vector to avoid # issues which files that might start with null bytes bytes <- readBin(con, what = "raw", n = 2L) expected <- as.raw(c(0x23L, 0x21L)) if (!identical(bytes, expected)) return("") # read a single line from the connection readLines(con, n = 1L, warn = FALSE) } # here, 'broken' implies a file which is a link pointing to a file that # doesn't exist, so only returns true if the file is "link"-y and the # file it points to doesn't exist renv_file_broken <- function(paths) { if (renv_platform_unix()) renv_file_broken_unix(paths) else renv_file_broken_win32(paths) } renv_file_broken_unix <- function(paths) { # a symlink is broken if: # - the file is a symlink (tested via Sys.readlink) # - the file it points to does not exist (tested via file.exists) !is.na(Sys.readlink(paths)) & !file.exists(paths) } # unfortunately, as far as I know, there isn't a more reliable # way of detecting broken junction points on Windows using vanilla R renv_file_broken_win32 <- function(paths) { time <- Sys.time() map_lgl(paths, function(path) { file.access(path) == 0L && !Sys.setFileTime(path, time) }) } renv_file_size <- function(path) { file.info(path, extra_cols = FALSE)$size } renv_file_remove <- function(paths) { if (renv_platform_windows()) renv_file_remove_win32(paths) else renv_file_remove_unix(paths) } renv_file_remove_win32 <- function(paths) { for (path in paths) { command <- paste("rmdir /S /Q", renv_shell_path(path)) shell(command) } } renv_file_remove_unix <- function(paths) { unlink(paths, recursive = TRUE, force = TRUE) } renv_file_writable <- function(path) { # allow users to opt-out just in case override <- getOption("renv.download.check_writable", default = TRUE) if (!identical(override, TRUE)) return(TRUE) # if we're given the path to a file, use the parent directory instead info <- renv_file_info(path) if (!identical(info$isdir, TRUE)) path <- dirname(path) # if we still don't have a directory, bail info <- renv_file_info(path) if (!identical(info$isdir, TRUE)) return(FALSE) # try creating and removing a temporary file in this directory tempfile <- renv_scope_tempfile(".renv-write-test-", tmpdir = path) ok <- dir.create(tempfile, showWarnings = FALSE) # return ok if we succeeded ok } renv/R/imbue.R0000644000176200001440000000643514731330073012662 0ustar liggesusers #' Imbue an renv Installation #' #' Imbue an renv installation into a project, thereby making the requested #' version of renv available within. #' #' Normally, this function does not need to be called directly by the user; it #' will be invoked as required by [init()] and [activate()]. #' #' @inherit renv-params #' #' @param version The version of renv to install. If `NULL`, the version #' of renv currently installed will be used. The requested version of #' renv will be retrieved from the renv public GitHub repository, #' at . #' #' @param quiet Boolean; avoid printing output during install of renv? #' imbue <- function(project = NULL, version = NULL, quiet = FALSE) { renv_scope_error_handler() project <- renv_project_resolve(project) renv_scope_options(renv.verbose = !quiet) vtext <- version %||% renv_metadata_version() writef("Installing renv [%s] ...", vtext) status <- renv_imbue_impl(project, version) writef("- Done! renv has been successfully installed.") invisible(status) } renv_imbue_impl <- function(project, library = NULL, version = NULL, force = FALSE) { # don't imbue during tests unless explicitly requested if (renv_tests_running() && !force) return(NULL) # resolve library path library <- library %||% renv_paths_library(project = project) ensure_directory(library) # NULL version means imbue this version of renv if (is.null(version)) return(renv_imbue_self(project, library = library)) # otherwise, try to download and install the requested version # of renv from GitHub remote <- paste("rstudio/renv", version %||% "main", sep = "@") record <- renv_remotes_resolve(remote) records <- list(renv = record) renv_scope_restore( project = project, library = library, records = records, packages = "renv", recursive = FALSE ) records <- renv_retrieve_impl("renv") renv_install_impl(records) record <- records[["renv"]] invisible(record) } renv_imbue_self <- function(project, library = NULL, source = NULL) { # construct source, target paths # (check if 'renv' is loaded to handle embedded case) source <- source %||% { if ("renv" %in% loadedNamespaces()) { renv_namespace_path("renv") } else { renv_package_find("renv") } } if (!file.exists(source)) stop("internal error: could not find where 'renv' is installed") library <- library %||% renv_paths_library(project = project) target <- file.path(library, "renv") if (renv_file_same(source, target)) return(TRUE) type <- renv_package_type(source, quiet = TRUE) case( type == "source" ~ renv_imbue_self_source(source, target), type == "binary" ~ renv_imbue_self_binary(source, target) ) } renv_imbue_self_source <- function(source, target) { # if the package already exists, just skip if (file.exists(target)) return(TRUE) # otherwise, install it library <- dirname(target) ensure_directory(library) r_cmd_install("renv", source, library) } renv_imbue_self_binary <- function(source, target) { ensure_parent_directory(target) renv_file_copy(source, target, overwrite = TRUE) } renv/R/cache.R0000644000176200001440000003572214761163114012630 0ustar liggesusers # tools for interacting with the renv global package cache renv_cache_version <- function() { # NOTE: users should normally not override the cache version; # this is provided just to make testing easier Sys.getenv("RENV_CACHE_VERSION", unset = "v5") } renv_cache_version_previous <- function() { version <- renv_cache_version() number <- as.integer(substring(version, 2L)) paste("v", number - 1L, sep = "") } # given a record, find a compatible version of that package in the cache, # using a computed hash if available; if no hash is available, then try # to match based on the package name + version renv_cache_find <- function(record) { # validate required fields -- if any are missing, we can't use the cache required <- c("Package", "Version") missing <- renv_vector_diff(required, names(record)) if (length(missing)) return("") # if we have a hash, use it directly if (!is.null(record$Hash)) { # generate path to package installations in cache paths <- with(record, renv_paths_cache(Package, Version, Hash, Package)) # if there are multiple cache entries, return the first existing one # if no entries exist, return path into first cache entry for (path in paths) if (file.exists(path)) return(path) return(paths[[1L]]) } # if the record doesn't have a hash, check to see if we can still locate a # compatible package version within the cache root <- with(record, renv_paths_cache(Package, Version)) hashes <- list.files(root, full.names = TRUE) packages <- list.files(hashes, full.names = TRUE) # iterate over package paths, read DESCRIPTION, and look # for something compatible with the requested record for (package in packages) { # try to read the DESCRIPTION file dcf <- catch(as.list(renv_description_read(package))) if (inherits(dcf, "error")) next # if we're requesting an install from an R package repository, # and the cached package has a "Repository" field, then use it source <- renv_record_source(record) hasrepo <- source %in% c("cran", "repository") && "Repository" %in% names(dcf) if (hasrepo) return(package) # check for compatible fields fields <- unique(c( renv_record_names(record, c("Package", "Version")), renv_record_names(dcf, c("Package", "Version")) )) # drop unnamed fields record <- record[nzchar(record)] dcf <- dcf[nzchar(dcf)] # drop remote fields for cranlike remotes if (renv_record_cranlike(dcf)) dcf <- dcf[grep("^Remote(?!s)", names(dcf), invert = TRUE, perl = TRUE)] # check identical lhs <- keep(record, fields) rhs <- keep(dcf, fields) if (identical(lhs, rhs)) return(package) } # failed; return "" as proxy for missing file "" } # given the path to a package's description file, # compute the location it would be assigned if it # were moved to the renv cache renv_cache_path <- function(path) { record <- renv_description_read(path) record$Hash <- renv_hash_description(path) renv_cache_find(record) } renv_cache_path_components <- function(path) { data_frame( Package = renv_path_component(path, 1L), Hash = renv_path_component(path, 2L), Version = renv_path_component(path, 3L) ) } renv_cache_synchronize <- function(record, linkable = FALSE) { # construct path to package in library library <- renv_libpaths_active() path <- file.path(library, record$Package) if (!file.exists(path)) return(FALSE) # bail if the package source is unknown # (packages with an unknown source are not cacheable) desc <- renv_description_read(path) source <- renv_snapshot_description_source(desc) if (identical(source, list(Source = "unknown"))) return(FALSE) # bail if record not cacheable if (!renv_record_cacheable(record)) return(FALSE) # if we don't have a hash, compute it now record$Hash <- record$Hash %||% renv_hash_description(path) # construct cache entry caches <- renv_cache_find(record) # try to synchronize copied <- FALSE for (cache in caches) { copied <- renv_cache_synchronize_impl(cache, record, linkable, path) if (copied) return(TRUE) } return(FALSE) } renv_cache_synchronize_impl <- function(cache, record, linkable, path) { # double-check we have a valid cache path if (!nzchar(cache)) return(FALSE) # if our cache -> path link is already up to date, then nothing to do if (renv_file_same(cache, path)) return(TRUE) # try to create the cache directory target # (catch errors due to permissions, etc) parent <- dirname(cache) status <- catchall(ensure_directory(parent)) if (inherits(status, "error")) return(FALSE) # double-check that the cache is writable writable <- local({ file <- renv_scope_tempfile("renv-tempfile-", tmpdir = parent) status <- catchall(file.create(file)) file.exists(file) }) if (!writable) return(FALSE) # obtain lock on the cache lockpath <- file.path(parent, ".cache.lock") renv_scope_lock(lockpath) # if we already have a cache entry, back it up restore <- renv_file_backup(cache) defer(restore()) # copy package from source location into the cache if (linkable) { renv_cache_move(path, cache, overwrite = TRUE) renv_file_link(cache, path, overwrite = TRUE) } else { renv_cache_copy(path, cache, overwrite = TRUE) } if (renv_platform_unix()) { # change the cache owner if set user <- Sys.getenv("RENV_CACHE_USER", unset = NA) if (!is.na(user)) { parent <- dirname(dirname(dirname(cache))) renv_system_exec( command = "chown", args = c("-Rf", renv_shell_quote(user), renv_shell_path(parent)), action = "chowning cached package", quiet = TRUE, success = NULL ) } # change file modes after copy if set mode <- Sys.getenv("RENV_CACHE_MODE", unset = NA) if (!is.na(mode)) { parent <- dirname(dirname(dirname(cache))) renv_system_exec( command = "chmod", args = c("-Rf", renv_shell_quote(mode), renv_shell_path(parent)), action = "chmoding cached package", quiet = TRUE, success = NULL ) } # finally, allow for an arbitrary callback if set callback <- getOption("renv.cache.callback") if (is.function(callback)) callback(cache) } TRUE } renv_cache_list <- function(cache = NULL, packages = NULL) { caches <- cache %||% renv_paths_cache() paths <- map(caches, renv_cache_list_impl, packages = packages) unlist(paths, recursive = TRUE, use.names = FALSE) } renv_cache_list_impl <- function(cache, packages) { # paths to packages in the cache have the following format: # # /// # # so find entries in the cache by listing files in each directory names <- file.path(cache, packages %||% list.files(cache)) versions <- list.files(names, full.names = TRUE) hashes <- list.files(versions, full.names = TRUE) paths <- list.files(hashes, full.names = TRUE) # only keep paths that appear to be valid valid <- grep(renv_regexps_package_name(), basename(paths)) paths[valid] } renv_cache_problems <- function(paths, reason) { data_frame( Package = renv_path_component(paths, 1L), Version = renv_path_component(paths, 3L), Path = paths, Reason = reason ) } renv_cache_diagnose_corrupt_metadata <- function(paths, problems, verbose) { # check for missing metadata files metapaths <- file.path(paths, "Meta/package.rds") ok <- file.exists(metapaths) bad <- paths[!ok] if (length(bad)) { # nocov start if (verbose) { bulletin( "The following package(s) are missing 'Meta/package.rds':", renv_cache_format_path(bad), "These packages should be purged and reinstalled." ) } # nocov end data <- renv_cache_problems( paths = bad, reason = "'Meta/package.rds' does not exist" ) problems$push(data) } # check for corrupt / unreadable metadata files ok <- map_lgl(metapaths, function(path) { rds <- catch(readRDS(path)) !inherits(rds, "error") }) bad <- paths[!ok] if (length(bad)) { # nocov start if (verbose) { bulletin( "The following package(s) have corrupt 'Meta/package.rds' files:", renv_cache_format_path(bad), "These packages should be purged and reinstalled." ) } # nocov end data <- renv_cache_problems( paths = bad, reason = "'Meta/package.rds' does not exist" ) problems$push(data) } paths } renv_cache_diagnose_missing_descriptions <- function(paths, problems, verbose) { descpaths <- file.path(paths, "DESCRIPTION") exists <- file.exists(descpaths) bad <- paths[!exists] if (empty(bad)) return(paths) # nocov start if (verbose) { bulletin( "The following packages are missing DESCRIPTION files in the cache:", renv_cache_format_path(bad), "These packages should be purged and reinstalled." ) } # nocov end data <- renv_cache_problems( paths = bad, reason = "'DESCRIPTION' file does not exist" ) problems$push(data) paths[exists] } renv_cache_diagnose_bad_hash <- function(paths, problems, verbose) { expected <- map_chr(paths, renv_cache_path) wrong <- paths != expected & !file.exists(expected) if (!any(wrong)) return(paths) # nocov start if (verbose) { lhs <- renv_cache_path_components(paths[wrong]) rhs <- renv_cache_path_components(expected[wrong]) fmt <- "%s %s [Hash: %s != %s]" entries <- sprintf(fmt, lhs$Package, lhs$Version, lhs$Hash, rhs$Hash) bulletin( "The following packages have incorrect hashes:", entries, "Consider using `renv::rehash()` to re-hash these packages." ) } # nocov end data <- renv_cache_problems( paths = paths[wrong], reason = "unexpected hash" ) problems$push(data) paths } renv_cache_diagnose_wrong_built_version <- function(paths, problems, verbose) { # form paths to DESCRIPTION files descpaths <- file.path(paths, "DESCRIPTION") # parse the version of R each was built for versions <- map_chr(descpaths, function(descpath) { tryCatch( renv_description_built_version(descpath), error = function(e) { warning(e) NA } ) }) # check for NAs, report and remove them isna <- is.na(versions) if (any(isna)) { # nocov start if (verbose) { bulletin( "The following packages have no 'Built' field recorded in their DESCRIPTION file:", paths[isna], "renv is unable to validate the version of R this package was built for." ) } # nocov end data <- renv_cache_problems( paths = paths[isna], reason = "missing Built field" ) problems$push(data) paths <- paths[!isna] versions <- versions[!isna] } # check for incompatible versions wrong <- map_lgl(versions, function(version) { tryCatch( renv_version_compare(version, getRversion(), 2L) != 0, error = function(e) { warning(e) TRUE } ) }) if (!any(wrong)) return(paths) # nocov start if (verbose) { bulletin( "The following packages in the cache were built for a different version of R:", renv_cache_format_path(paths[wrong]), "These packages will need to be purged and reinstalled." ) } # nocov end data <- renv_cache_problems( paths = paths[wrong], reason = "built for different version of R" ) problems$push(data) paths } renv_cache_diagnose <- function(verbose = NULL) { verbose <- verbose %||% renv_verbose() problems <- stack() paths <- renv_cache_list() paths <- renv_cache_diagnose_corrupt_metadata(paths, problems, verbose) paths <- renv_cache_diagnose_missing_descriptions(paths, problems, verbose) paths <- renv_cache_diagnose_bad_hash(paths, problems, verbose) paths <- renv_cache_diagnose_wrong_built_version(paths, problems, verbose) invisible(bind(problems$data())) } renv_cache_acls_reset <- function(target) { enabled <- Sys.getenv("RENV_CACHE_ACLS", unset = "TRUE") if (enabled) renv_acls_reset(target) } # copies a package at location 'source' to cache location 'target' renv_cache_copy <- function(source, target, overwrite = FALSE) { ensure_parent_directory(target) renv_file_copy(source, target, overwrite = overwrite) renv_cache_acls_reset(target) } # moves a package from location 'source' to cache location 'target', # and then links back from 'target' to 'source' renv_cache_move <- function(source, target, overwrite = FALSE) { # move package into the cache if requested if (overwrite || !file.exists(target)) { ensure_parent_directory(target) renv_file_move(source, target, overwrite = TRUE) } # try to reset ACLs on the cache directory renv_cache_acls_reset(target) # link from the cache back to the target location renv_file_link(target, source, overwrite = TRUE) } # nocov start renv_cache_format_path <- function(paths) { # extract path components names <- format(renv_path_component(paths, 1L)) hashes <- format(renv_path_component(paths, 2L)) versions <- format(renv_path_component(paths, 3L)) # format and write fmt <- "%s %s [Hash: %s]" sprintf(fmt, names, versions, hashes) } # nocov end renv_cache_clean_empty <- function(cache = NULL) { caches <- cache %||% renv_paths_cache() map(caches, renv_cleanse_empty) } renv_cache_package_validate <- function(path) { if (renv_project_type(path) == "package") return(TRUE) type <- renv_file_type(path, symlinks = FALSE) if (!nzchar(type)) return(FALSE) name <- if (type == "directory") "directory" else "file" fmt <- "%s %s exists but does not appear to be an R package" warningf(fmt, name, shQuote(path)) FALSE } renv_cache_config_enabled <- function(project) { config$cache.enabled() && settings$use.cache(project = project) } renv_cache_config_symlinks <- function(project) { usesymlinks <- config$cache.symlinks(default = NULL) %||% renv_cache_config_symlinks_default(project = project) usesymlinks && settings$use.cache(project = project) } renv_cache_config_symlinks_default <- function(project) { # on linux, we can always use symlinks if (renv_platform_unix()) return(TRUE) # on Windows, only try to use symlinks (junction points) if the cache # and the project library appear to live on the same drive libpath <- renv_paths_library(project = project) cachepath <- renv_paths_cache() # TODO: with this change, anyone using networks not mapped to a local drive # would need to opt-in to using symlinks, but that's probably okay? all( substring(libpath, 1L, 2L) == substring(cachepath, 1L, 2L), substring(libpath, 2L, 2L) == ":", substring(cachepath, 2L, 2L) == ":" ) } renv_cache_linkable <- function(project, library) { renv_cache_config_enabled(project = project) && renv_cache_config_symlinks(project = project) && getOption( "renv.cache.linkable", renv_path_same(library, renv_paths_library(project = project)) ) } renv/R/robocopy.R0000644000176200001440000000142614731330073013410 0ustar liggesusers renv_robocopy_exec <- function(source, target, flags = NULL) { source <- path.expand(source) target <- path.expand(target) # add other flags flags <- c(flags, "/E", "/Z", "/R:5", "/W:10") # https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/robocopy # > Any value greater than 8 indicates that there was at least one failure # > during the copy operation. renv_system_exec( command = "robocopy", args = c(flags, renv_shell_path(source), renv_shell_path(target)), action = "copying directory", success = 0:7, quiet = TRUE ) } renv_robocopy_copy <- function(source, target) { renv_robocopy_exec(source, target) } renv_robocopy_move <- function(source, target) { renv_robocopy_exec(source, target, "/MOVE") } renv/R/preflight.R0000644000176200001440000000457514731330073013550 0ustar liggesusers # returns TRUE if problems detected renv_preflight <- function(lockfile) { problems <- stack() # check that we can compile C programs renv_preflight_compiler(problems) # if rJava is being used, ensure that Java is properly configured renv_preflight_java(lockfile, problems) data <- problems$data() if (length(data)) { feedback <- lines( "The following problems were detected in your environment:", "", paste(data, collapse = "\n\n"), "", "The environment may not be restored correctly." ) caution(feedback) } length(data) == 0 } renv_preflight_compiler <- function(problems) { # try to compile a simple program program <- "void test() {}" file <- renv_scope_tempfile("renv-test-compile-", fileext = ".c") writeLines(program, con = file) args <- c("CMD", "SHLIB", renv_shell_path(file)) status <- system2(R(), args, stdout = FALSE, stderr = FALSE) if (!identical(status, 0L)) { feedback <- lines( "- Cannot compile C / C++ files from source.", " Please ensure you have a compiler toolchain installed." ) problems$push(feedback) } } renv_preflight_java <- function(lockfile, problems) { # no need to check if we're not using rJava records <- renv_lockfile_records(lockfile) if (is.null(records[["rJava"]])) return(TRUE) # TODO: no need to do anything if we're only installing binaries? switch( Sys.info()[["sysname"]], Windows = renv_preflight_java_windows(problems), renv_preflight_java_unix(problems) ) } renv_preflight_java_windows <- function(problems) { home <- Sys.getenv("JAVA_HOME", unset = NA) feedback <- case( is.na(home) ~ lines( "- JAVA_HOME is not set.", " Please ensure you have a Java Development Kit (JDK) installed." ), !file.exists(home) ~ lines( "- JAVA_HOME is set to a non-existent directory.", " Please ensure you have a Java Development Kit (JDK) installed." ) ) if (!is.null(feedback)) problems$push(feedback) } renv_preflight_java_unix <- function(problems) { args <- c("CMD", "javareconf", "--dry-run") status <- system2(R(), args, stdout = FALSE, stderr = FALSE) if (!identical(status, 0L)) { feedback <- lines( "- Cannot compile Java files from source.", " Please ensure you have a Java Development Kit (JDK) installed." ) problems$push(feedback) } } renv/R/git.R0000644000176200001440000000075314731330073012341 0ustar liggesusers git <- function() { gitpath <- Sys.which("git") if (!nzchar(gitpath)) stop("failed to find git executable on the PATH") gitpath } renv_git_preflight <- function() { if (!nzchar(Sys.which("git"))) stopf("'git' is not available on the PATH") } renv_git_root <- function(project) { project <- renv_path_normalize(project) renv_file_find(project, function(parent) { gitroot <- file.path(parent, ".git") if (file.exists(gitroot)) return(gitroot) }) } renv/R/description.R0000644000176200001440000001322514731330072014076 0ustar liggesusers renv_description_read <- function(path = NULL, package = NULL, subdir = NULL, field = NULL, ...) { # if given a package name, construct path to that package path <- path %||% find.package(package) # normalize non-absolute paths if (!renv_path_absolute(path)) path <- renv_path_normalize(path) # if 'path' refers to a directory, try to resolve the DESCRIPTION file if (dir.exists(path)) { components <- c(path, if (nzchar(subdir %||% "")) subdir, "DESCRIPTION") path <- paste(components, collapse = "/") } # if the DESCRIPTION file doesn't exist, bail if (!file.exists(path)) stopf("DESCRIPTION file %s does not exist", renv_path_pretty(path)) # read value with filebacked cache description <- filebacked( context = "renv_description_read", path = path, callback = renv_description_read_impl, subdir = subdir, ... ) if (!is.null(field)) return(description[[field]]) description } renv_description_read_impl <- function(path = NULL, subdir = NULL, ...) { # if we have an archive, attempt to unpack the DESCRIPTION type <- renv_archive_type(path) if (type != "unknown") { # list files within the archive files <- renv_archive_list(path) # find the DESCRIPTION file. note that for some source tarballs (e.g. # those from GitHub) the first entry may not be the package name, so # just consume everything up to the first slash subdir <- subdir %||% "" # tolerate leading './' components in the archive paths # https://github.com/rstudio/renv/issues/1852 prefix <- "^(?:\\./)*[^/]+" # build pattern looking for the DESCRIPTION file parts <- c(prefix, if (nzchar(subdir)) subdir, "DESCRIPTION$") pattern <- paste(parts, collapse = "/") descs <- grep(pattern, files, perl = TRUE, value = TRUE) if (empty(descs)) { fmt <- "archive '%s' does not appear to contain a DESCRIPTION file" stopf(fmt, renv_path_aliased(path)) } # choose the shortest DESCRPITION file matching file <- descs[[which.min(nchar(descs))]] # unpack into tempdir location exdir <- renv_scope_tempfile("renv-description-") renv_archive_decompress(path, files = file, exdir = exdir) # update path to extracted DESCRIPTION path <- file.path(exdir, file) } # read DESCRIPTION as dcf dcf <- renv_dcf_read(path, ...) if (empty(dcf)) stopf("DESCRIPTION file at '%s' is empty", path) dcf } renv_description_path <- function(path) { childpath <- file.path(path, "DESCRIPTION") indirect <- file.exists(childpath) path[indirect] <- childpath[indirect] path } # parse the dependency requirements normally presented in # Depends, Imports, Suggests, and so on renv_description_parse_field <- function(field) { # check for invalid / unexpected inputs if (is.null(field) || is.na(field) || !nzchar(field)) return(NULL) pattern <- paste0( "([a-zA-Z0-9._]+)", # package name "(?:\\s*\\(([><=]+)\\s*([0-9.-]+)\\))?" # optional version specification ) # split on commas parts <- strsplit(field, "\\s*,\\s*")[[1]] # drop any empty fields x <- parts[nzchar(parts)] # match to split on package name, version m <- regexec(pattern, x) matches <- regmatches(x, m) if (empty(matches)) return(NULL) data_frame( Package = extract_chr(matches, 2L), Require = extract_chr(matches, 3L), Version = extract_chr(matches, 4L) ) } renv_description_resolve <- function(path) { case( is.list(path) ~ path, is.character(path) ~ renv_description_read(path = path) ) } renv_description_built_version <- function(desc = NULL) { desc <- renv_description_resolve(desc) built <- desc[["Built"]] if (is.null(built)) return(NA) substring(built, 3L, regexpr(";", built, fixed = TRUE) - 1L) } renv_description_dependency_fields_expand <- function(fields) { expanded <- map(fields, function(field) { case( identical(field, FALSE) ~ NULL, identical(field, "strong") || is.na(field) ~ c("Depends", "Imports", "LinkingTo"), identical(field, "most") || identical(field, TRUE) ~ c("Depends", "Imports", "LinkingTo", "Suggests"), identical(field, "all") ~ c("Depends", "Imports", "LinkingTo", "Suggests", "Enhances"), field ) }) unique(unlist(expanded, recursive = FALSE, use.names = FALSE)) } renv_description_dependency_fields <- function(fields, project) { fields <- fields %||% settings$package.dependency.fields(project = project) renv_description_dependency_fields_expand(fields) } renv_description_remotes <- function(path) { desc <- catch(renv_description_read(path)) if (inherits(desc, "error")) return(list()) profile <- renv_profile_get() field <- if (is.null(profile)) "Remotes" else sprintf("Config/renv/profiles/%s/remotes", profile) remotes <- desc[[field]] if (is.null(remotes)) return(list()) # if possible, resolve remotes lazily splat <- strsplit(remotes, "[[:space:]]*,[[:space:]]*")[[1]] remotes <- lapply(splat, function(spec) { # if this is a named remote, we can resolve it lazily idx <- c(regexpr("=", spec, fixed = TRUE)) result <- if (idx == -1L) { remote <- renv_remotes_resolve(spec) list(Package = remote$Package, Remote = remote) } else { package <- substring(spec, 1L, idx - 1L) list(Package = package, Remote = function() renv_remotes_resolve(spec)) } }) # put together into named list records <- map(remotes, `[[`, 2L) names(records) <- map_chr(remotes, `[[`, 1L) records } renv/R/project.R0000644000176200001440000001527614740070207013232 0ustar liggesusers # The path to the currently-loaded project, if any. # NULL when no project is currently loaded. the$project_path <- NULL # Flag indicating whether we're checking if the project is synchronized. the$project_synchronized_check_running <- FALSE #' Retrieve the active project #' #' Retrieve the path to the active project (if any). #' #' @param default The value to return when no project is #' currently active. Defaults to `NULL`. #' #' @export #' #' @return The active project directory, as a length-one character vector. #' #' @examples #' \dontrun{ #' #' # get the currently-active renv project #' renv::project() #' #' } project <- function(default = NULL) { renv_project_get(default = default) } renv_project_get <- function(default = NULL) { the$project_path %||% default } # NOTE: 'RENV_PROJECT' kept for backwards compatibility with RStudio renv_project_set <- function(project) { the$project_path <- project # https://github.com/rstudio/renv/issues/2036 options(renv.project.path = project) Sys.setenv(RENV_PROJECT = project) } # NOTE: 'RENV_PROJECT' kept for backwards compatibility with RStudio renv_project_clear <- function() { the$project_path <- NULL # https://github.com/rstudio/renv/issues/2036 options(renv.project.path = NULL) Sys.unsetenv("RENV_PROJECT") } renv_project_resolve <- function(project = NULL, default = getwd()) { project <- project %||% renv_project_get(default = default) renv_path_normalize(project) } renv_project_initialized <- function(project) { lockfile <- renv_lockfile_path(project) if (file.exists(lockfile)) return(TRUE) library <- renv_paths_library(project = project) if (file.exists(library)) return(TRUE) FALSE } renv_project_type <- function(path) { if (!nzchar(path)) return("unknown") path <- renv_path_normalize(path) filebacked( context = "renv_project_type", path = file.path(path, "DESCRIPTION"), callback = renv_project_type_impl ) } renv_project_type_impl <- function(path) { if (!file.exists(path)) return("unknown") desc <- tryCatch( renv_dcf_read(path), error = identity ) if (inherits(desc, "error")) return("unknown") type <- desc$Type if (!is.null(type)) return(tolower(type)) package <- desc$Package if (!is.null(package)) return("package") "unknown" } renv_project_remotes <- function(project, filter = NULL, resolve = FALSE) { descpath <- file.path(project, "DESCRIPTION") if (!file.exists(descpath)) return(NULL) # find packages mentioned in the DESCRIPTION file deps <- renv_dependencies_discover_description( path = descpath, project = project ) if (empty(deps)) return(list()) # split according to package specs <- split(deps, deps$Package) # drop ignored specs ignored <- renv_project_ignored_packages(project = project) specs <- specs[setdiff(names(specs), c("R", ignored))] # if any Roxygen fields are included, # infer a dependency on roxygen2 and devtools desc <- renv_description_read(descpath) if (any(grepl("^Roxygen", names(desc)))) { for (package in c("devtools", "roxygen2")) { if (!package %in% ignored) { specs[[package]] <- specs[[package]] %||% renv_dependencies_list(descpath, package, dev = TRUE) } } } # parse remotes if available remotes <- renv_description_remotes(descpath) # apply filter if (!is.null(filter)) specs <- filter(specs, remotes) # now, try to resolve the packages records <- enumerate(specs, function(package, spec) { # return a function here, so we can lazily resolve these as needed # https://github.com/rstudio/renv/issues/1755 function() { # use remote if supplied if (!is.null(remotes[[package]])) return(remotes[[package]]) # check for explicit version requirement explicit <- spec[spec$Require == "==", ] if (nrow(explicit)) { version <- explicit$Version[[1L]] if (nzchar(version)) { entry <- paste(package, version, sep = "@") return(renv_remotes_resolve(entry)) } } # check if we're being invoked during restore or install # if so, we may want to re-use an already-existing package # https://github.com/rstudio/renv/issues/2071 packages <- renv_restore_state(key = "packages") renv_remotes_resolve(package, infer = !package %in% packages) } }) if (resolve) map(records, resolve) else records } renv_project_ignored_packages <- function(project) { # if we don't have a project, nothing to do if (is.null(project)) return(character()) # read base set of ignored packages ignored <- c( settings$ignored.packages(project = project), renv_project_ignored_packages_self(project) ) # return collected set of ignored packages ignored } renv_project_ignored_packages_self <- function(project) { # only ignore self in package projects if (renv_project_type(project) != "package") return(NULL) # read current package desc <- renv_description_read(project) package <- desc[["Package"]] # respect user preference if set ignore <- getOption("renv.snapshot.ignore.self", default = NULL) if (identical(ignore, TRUE)) return(package) else if (identical(ignore, FALSE)) return(NULL) # don't ignore self in golem projets golem <- file.path(project, "inst/golem-config.yml") if (file.exists(golem)) return(NULL) # hack for renv: don't depend on self if (identical(package, "renv")) return(NULL) # return the package name package } renv_project_id <- function(project) { idpath <- renv_id_path(project = project) if (!file.exists(idpath)) { id <- renv_id_generate() writeLines(id, con = idpath) } readLines(idpath, n = 1L, warn = FALSE) } # TODO: this gets really dicey once the user starts configuring where # renv places its project-local state ... renv_project_find <- function(path = NULL) { path <- path %||% getwd() anchors <- c("renv.lock", "renv/activate.R") resolved <- renv_file_find(path, function(parent) { for (anchor in anchors) if (file.exists(file.path(parent, anchor))) return(parent) }) if (is.null(resolved)) { fmt <- "couldn't resolve renv project associated with path %s" stopf(fmt, renv_path_pretty(path)) } resolved } renv_project_lock <- function(project = NULL) { if (!config$locking.enabled()) return() path <- the$project_path if (!identical(project, path)) return() project <- renv_project_resolve(project) path <- file.path(project, "renv/lock") ensure_parent_directory(path) renv_scope_lock(path, scope = parent.frame()) } renv_project_loaded <- function(project) { !is.null(project) && identical(project, the$project_path) } renv/R/profile.R0000644000176200001440000000047214731330073013214 0ustar liggesusers renv_profile_prefix <- function() { renv_bootstrap_profile_prefix() } renv_profile_get <- function() { renv_bootstrap_profile_get() } renv_profile_set <- function(profile) { renv_bootstrap_profile_set(profile) } renv_profile_normalize <- function(profile) { renv_bootstrap_profile_normalize(profile) } renv/R/progress.R0000644000176200001440000000131614731330073013416 0ustar liggesusers renv_progress_create <- function(max, wait = 1.0) { # local variables for closure count <- 0L max <- max message <- "" start <- Sys.time() function() { # check for and print progress count <<- count + 1L # if not enough time has elapsed yet, nothing to do if (Sys.time() - start < wait) return() # create message backspaces <- paste(rep("\b", nchar(message)), collapse = "") message <<- sprintf("[%i/%i] ", count, max) all <- paste(backspaces, message, sep = "") cat(all, file = stdout(), sep = "") } } renv_progress_callback <- function(callback, max, wait = 1.0) { tick <- renv_progress_create(max, wait) function(...) { tick(); callback(...) } } renv/R/utils-format.R0000644000176200001440000000114214731330073014175 0ustar liggesusers stopf <- function(fmt = "", ..., call. = FALSE) { stop(sprintf(fmt, ...), call. = call.) } warningf <- function(fmt = "", ..., call. = FALSE, immediate. = FALSE) { warning(sprintf(fmt, ...), call. = call., immediate. = immediate.) } printf <- function(fmt = "", ..., file = stdout(), sep = "") { if (!is.null(fmt) && renv_verbose()) cat(sprintf(fmt, ...), file = file, sep = sep) } writef <- function(fmt = "", ..., con = stdout()) { if (!is.null(fmt) && renv_verbose()) writeLines(sprintf(fmt, ...), con = con) } info_bullet <- function() { if (l10n_info()$`UTF-8`) "\u2139" else "i" } renv/R/watchdog.R0000644000176200001440000001404014731330073013350 0ustar liggesusers # whether or not the user has enabled the renv watchdog in this session the$watchdog_enabled <- FALSE # metadata related to the running watchdog process, if any the$watchdog_process <- NULL renv_watchdog_init <- function() { the$watchdog_enabled <- renv_watchdog_enabled_impl() } renv_watchdog_enabled <- function() { the$watchdog_enabled } renv_watchdog_check <- function() { if (!renv_watchdog_enabled()) return(FALSE) if (renv_watchdog_running()) return(TRUE) renv_watchdog_start() } renv_watchdog_enabled_impl <- function() { # skip in older versions of R; we require newer APIs if (getRversion() < "4.0.0") return(FALSE) # allow override via environment variable enabled <- Sys.getenv("RENV_WATCHDOG_ENABLED", unset = NA) if (!is.na(enabled)) return(truthy(enabled)) # disable on Windows; need to understand CI test failures # https://github.com/rstudio/renv/actions/runs/5273668333/jobs/9537353788#step:6:242 if (renv_platform_windows()) return(FALSE) # skip during R CMD check (but not when running tests) checking <- renv_envvar_exists("_R_CHECK_PACKAGE_NAME_") if (checking && !testing()) return(FALSE) # skip during R CMD build or R CMD INSTALL # ... unless we are running tests on CI building <- renv_envvar_exists("R_PACKAGE_NAME") || renv_envvar_exists("R_PACKAGE_DIR") if (building) { ci <- Sys.getenv("CI", unset = "FALSE") if (!truthy(ci)) return(FALSE) } # ok, we're enabled TRUE } renv_watchdog_start <- function() { the$watchdog_enabled <- tryCatch( renv_watchdog_start_impl(), error = function(e) { warning(conditionMessage(e)) FALSE } ) } renv_watchdog_start_impl <- function() { # create a socket server -- this is used so the watchdog process # can communicate what port it'll be listening on for messages dlog("watchdog", "launching watchdog") server <- renv_socket_server() socket <- server$socket defer(close(socket)) # generate script to invoke watchdog script <- renv_scope_tempfile("renv-watchdog-", fileext = ".R") # figure out library path -- need to dodge devtools::load_all() nspath <- renv_namespace_path(.packageName) library <- if (file.exists(file.path(nspath, "Meta/package.rds"))) dirname(nspath) else renv_libpaths_default() code <- expr({ client <- list(pid = !!Sys.getpid(), port = !!server$port) host <- loadNamespace(!!.packageName, lib.loc = !!library) renv <- if (!is.null(host$renv)) host$renv else host renv$renv_watchdog_server_start(client) }) writeLines(deparse(code), con = script) # debug logging debugging <- Sys.getenv("RENV_WATCHDOG_DEBUG", unset = "FALSE") stdout <- stderr <- if (truthy(debugging)) "" else FALSE # launch the watchdog local({ renv_scope_envvars(RENV_PROCESS_TYPE = "watchdog-server") system2( command = R(), args = c("--vanilla", "-s", "-f", renv_shell_path(script)), stdout = stdout, stderr = stderr, wait = FALSE ) }) # wait for connection from watchdog server dlog("watchdog", "watchdog process launched; waiting for message") conn <- catch(renv_socket_accept(socket, open = "rb", timeout = 10L)) if (inherits(conn, "error")) { dlog("watchdog", paste("error connecting to watchdog:", conditionMessage(conn))) return(FALSE) } # store information about the running process defer(close(conn)) the$watchdog_process <- unserialize(conn) # return TRUE to indicate process was started dlog("watchdog", "watchdog message received [pid == %i]", the$watchdog_process$pid) TRUE } renv_watchdog_notify <- function(method, data = list()) { tryCatch( renv_watchdog_notify_impl(method, data), error = warnify ) } renv_watchdog_notify_impl <- function(method, data = list()) { # make sure the watchdog is running if (!renv_watchdog_check()) return(FALSE) # connect to the running server port <- renv_watchdog_port() conn <- renv_socket_connect(port, open = "wb") # close the connection on exit defer(close(conn)) # write message message <- list(method = method, data = data) serialize(message, connection = conn) # TRUE indicates message was written TRUE } renv_watchdog_request <- function(method, data = list()) { tryCatch( renv_watchdog_request_impl(method, data), error = warnify ) } renv_watchdog_request_impl <- function(method, data = list()) { # make sure the watchdog is running if (!renv_watchdog_check()) return(FALSE) # connect to the running server port <- renv_watchdog_port() outgoing <- renv_socket_connect(port, open = "wb") defer(close(outgoing)) # create our own socket server server <- renv_socket_server() defer(close(server$socket)) # write message message <- list(method = method, data = data, port = server$port) serialize(message, connection = outgoing) # now, open a new connection to get the response incoming <- renv_socket_accept(server$socket, open = "rb") defer(close(incoming)) # read the response unserialize(connection = incoming) } renv_watchdog_pid <- function() { the$watchdog_process$pid } renv_watchdog_port <- function() { the$watchdog_process$port } renv_watchdog_running <- function() { pid <- renv_watchdog_pid() !is.null(pid) && renv_process_exists(pid) } renv_watchdog_unload <- function() { renv_watchdog_shutdown() } renv_watchdog_terminate <- function() { pid <- renv_watchdog_pid() renv_process_kill(pid) } renv_watchdog_shutdown <- function() { # nothing to do if watchdog isn't running if (!renv_watchdog_running()) return(TRUE) # tell watchdog to shutdown renv_watchdog_notify("Shutdown") # wait for process to exit (avoid RStudio bomb) clock <- timer() wait_until(function() { !renv_watchdog_running() || clock$elapsed() > 1 }) if (!renv_watchdog_running()) return(TRUE) # if it's still running, explicitly terminate it renv_watchdog_terminate() # wait for process to exit (avoid RStudio bomb) clock <- timer() wait_until(function() { !renv_watchdog_running() || clock$elapsed() > 1 }) } renv/R/consent.R0000644000176200001440000000536314731330072013230 0ustar liggesusers #' Consent to usage of renv #' #' Provide consent to renv, allowing it to write and update certain files #' on your filesystem. #' #' As part of its normal operation, renv will write and update some files #' in your project directory, as well as an application-specific cache #' directory. These paths are documented within [paths]. #' #' In accordance with the #' [CRAN Repository Policy](https://cran.r-project.org/web/packages/policies.html), #' renv must first obtain consent from you, the user, before these actions #' can be taken. Please call `renv::consent()` first to provide this consent. #' #' You can also set the \R option: #' #' ``` #' options(renv.consent = TRUE) #' ``` #' #' to implicitly provide consent for e.g. non-interactive \R sessions. #' #' @param provided The default provided response. If you need to provide #' consent from a non-interactive \R session, you can invoke #' `renv::consent(provided = TRUE)` explicitly. #' #' @return `TRUE` if consent is provided, or an \R error otherwise. #' #' @export consent <- function(provided = FALSE) { # assume consent if embedded if (renv_metadata_embedded()) return(TRUE) # compute path to root directory root <- renv_paths_root() if (renv_file_type(root) == "directory") { writef("- Consent to use renv has already been provided -- nothing to do.") return(invisible(TRUE)) } # write welcome message template <- system.file("resources/WELCOME", package = "renv") contents <- readLines(template) replacements <- list(RENV_PATHS_ROOT = renv_path_pretty(root)) welcome <- renv_template_replace(contents, replacements) writef(welcome) # ask user if they want to proceed response <- catchall(proceed(default = provided)) if (!identical(response, TRUE)) { msg <- "consent was not provided; operation aborted" stop(msg, call. = FALSE) } # cache the user response options(renv.consent = TRUE) ensure_directory(root) writef("- %s has been created.", renv_path_pretty(root)) invisible(TRUE) } renv_consent_check <- function() { # check for explicit consent consent <- getOption("renv.consent") if (identical(consent, TRUE)) return(TRUE) else if (identical(consent, FALSE)) stopf("consent has been explicitly withdrawn") # check for existence of root root <- renv_paths_root() if (renv_file_type(root) == "directory") return(TRUE) # check for implicit consent consented <- !interactive() || renv_envvar_exists("CI") || renv_envvar_exists("GITHUB_ACTION") || renv_envvar_exists("RENV_PATHS_ROOT") || file.exists("/.singularity.d") || renv_virtualization_type() != "native" if (consented) { ensure_directory(root) return(TRUE) } # looks like the user's first interactive use of renv consent() } renv/R/ensure.R0000644000176200001440000000223314731330072013051 0ustar liggesusers ensure_existing_path <- function(path) { if (!file.exists(path)) stopf("no file at path '%s'", path) invisible(path) } ensure_existing_file <- function(path) { info <- renv_file_info(path) if (is.na(info$isdir)) stopf("no file at path '%s'", path) else if (identical(info$isdir, TRUE)) stopf("file '%s' exists but is a directory") invisible(path) } ensure_directory <- function(paths, umask = NULL) { # handle zero-path case if (empty(paths)) return(invisible(paths)) # set umask if necessary if (!is.null(umask)) renv_scope_umask("0") # for each path, try to either create the directory, or assert that # the directory already exists. this should also help handle cases # where 'dir.create()' fails because another process created the # directory at the same time we attempted to do so for (path in paths) { ok <- dir.create(path, recursive = TRUE, showWarnings = FALSE) || dir.exists(path) if (!ok) stopf("failed to create directory at path '%s'", path) } # return the paths invisible(paths) } ensure_parent_directory <- function(path) { ensure_directory(unique(dirname(path))) } renv/R/pretty.R0000644000176200001440000000501414731330073013100 0ustar liggesusers renv_pretty_print_records <- function(preamble, records, postamble = NULL) { if (empty(records)) return(invisible(NULL)) if (!renv_verbose()) return(invisible(NULL)) # NOTE: use 'sort()' rather than 'csort()' here so that # printed output is sorted in the expected way in the users locale # https://github.com/rstudio/renv/issues/1289 names(records) <- names(records) %||% map_chr(records, `[[`, "Package") records <- records[sort(names(records))] packages <- names(records) descs <- map_chr(records, renv_record_format_short) text <- sprintf("- %s [%s]", format(packages), descs) all <- c(preamble, text, postamble, if (length(postamble)) "") renv_caution_impl(all) } renv_pretty_print_records_pair <- function(preamble, old, new, postamble = NULL, formatter = NULL) { formatter <- formatter %||% renv_record_format_pair all <- c( c(preamble, ""), renv_pretty_print_records_pair_impl(old, new, formatter), if (length(postamble)) c(postamble, "") ) renv_caution_impl(all) } renv_pretty_print_records_pair_impl <- function(old, new, formatter) { # NOTE: use 'sort()' rather than 'csort()' here so that # printed output is sorted in the expected way in the users locale # https://github.com/rstudio/renv/issues/1289 all <- sort(union(names(old), names(new))) # compute groups groups <- map_chr(all, function(package) { lhs <- old[[package]]; rhs <- new[[package]] case( is.null(lhs$Source) ~ rhs$Repository %||% rhs$Source, is.null(rhs$Source) ~ lhs$Repository %||% lhs$Source, !is.null(rhs$Repository) ~ rhs$Repository, !is.null(rhs$Source) ~ rhs$Source ) }) n <- max(nchar(all)) # iterate over each group and print uapply(csort(unique(groups)), function(group) { lhs <- renv_records_select(old, groups, group) rhs <- renv_records_select(new, groups, group) nms <- union(names(lhs), names(rhs)) text <- map_chr(nms, function(nm) { formatter(lhs[[nm]], rhs[[nm]]) }) if (group == "unknown") group <- "(Unknown Source)" c( header(group), paste("-", format(nms, width = n), " ", text), "" ) }) } # NOTE: Used by vetiver, so perhaps is part of the API. # We should think of a cleaner way of exposing this. # https://github.com/rstudio/renv/issues/1413 renv_pretty_print_impl <- renv_caution_impl renv/R/task.R0000644000176200001440000000173414731330073012520 0ustar liggesusers renv_task_create <- function(callback, name = NULL) { # create name for task callback name <- name %||% as.character(substitute(callback)) name <- paste("renv", name, sep = ":::") # remove an already-existing task of the same name removeTaskCallback(name) # otherwise, add our new task addTaskCallback( renv_task_callback(callback, name), name = name ) } renv_task_callback <- function(callback, name) { force(callback) force(name) function(...) { status <- tryCatch(callback(), error = identity) if (inherits(status, "error")) { caution("Error in background task '%s': %s", name, conditionMessage(status)) caution("Background task '%s' will be stopped.", name) return(FALSE) } TRUE } } renv_task_unload <- function() { callbacks <- getTaskCallbackNames() for (callback in callbacks) for (prefix in c("renv_", "renv:::")) if (startsWith(callback, prefix)) removeTaskCallback(callback) } renv/R/packages.R0000644000176200001440000000071614731330073013333 0ustar liggesusers the$packages_base <- NULL the$packages_recommended <- NULL renv_packages_base <- function() { the$packages_base <- the$packages_base %||% { db <- installed_packages(lib.loc = .Library, priority = "base") c("R", db$Package, "translations") } } renv_packages_recommended <- function() { the$packages_recommended <- the$packages_recommended %||% { db <- installed_packages(lib.loc = .Library, priority = "recommended") db$Package } } renv/R/abi.R0000644000176200001440000001330214761163114012306 0ustar liggesusers renv_abi_check <- function(packages = NULL, ..., libpaths = NULL, project = NULL) { if (renv_platform_windows()) { writef("- ABI conflict checks are not available on Windows.") return() } # disable via option if necessary enabled <- getOption("renv.abi.check", default = TRUE) if (identical(enabled, FALSE)) return() # resolve arguments project <- renv_project_resolve(project) libpaths <- libpaths %||% renv_libpaths_all() # read installed packages packages <- packages %||% renv_abi_packages(project, libpaths) # analyze each package problems <- stack() map(packages, function(package) { tryCatch( renv_abi_check_impl(package, problems), error = warnify ) }) # report problems data <- problems$data() if (empty(data)) { fmt <- "- No ABI problems were detected in the set of installed packages." writef(fmt) return(invisible(data)) } # combine everything together tbl <- bind(data) # make reports for each different type reasons <- unique(tbl$reason) if ("Rcpp_precious_list" %in% reasons) { packages <- sort(unique(tbl$package[tbl$reason == "Rcpp_precious_list"])) bulletin( "The following packages were built against a newer version of Rcpp than is currently available:", packages, c( paste( "These packages depend on Rcpp (>= 1.0.7);", "however, Rcpp", renv_package_version("Rcpp"), "is currently installed." ), "Consider installing a new version of Rcpp with 'install.packages(\"Rcpp\")'." ) ) } if ("missing" %in% reasons) { missing <- tbl[tbl$reason == "missing", ] bulletin( "The following required system libraries are unavailable:", unique(missing$dependency), c( "These system libraries may need to be re-installed.", "Alternatively, you may need to re-install the packages which depend on these libraries." ) ) # now, for each dependency, list the packages which require it for (dep in unique(missing$dependency)) { caution(header(sprintf("%s (required by)", dep))) caution(paste("-", sort(tbl$package[missing$dependency == dep]))) caution() } } invisible(tbl) } renv_abi_check_impl <- function(package, problems) { # find path to package pkgpath <- renv_package_find(package) # check for dependency issues if (renv_platform_macos()) renv_abi_deps_macos(package, problems) else if (renv_platform_linux()) renv_abi_deps_linux(package, problems) # look for an associated shared object shlib <- renv_package_shlib(pkgpath) if (!file.exists(shlib)) return() # read symbols from LinkingTo dependency packages pkgdesc <- renv_description_read(path = pkgpath) if (is.null(pkgdesc$LinkingTo)) return() # read symbols from the library symbols <- renv_abi_symbols(shlib) # handle Rcpp linkdeps <- renv_description_parse_field(pkgdesc$LinkingTo) if ("Rcpp" %in% linkdeps$Package) renv_abi_check_impl_rcpp(package, symbols, problems) # TODO: other checks? more direct symbol checks for other packages? } renv_abi_check_impl_rcpp <- function(package, symbols, problems) { # read Rcpp symbols rcpplib <- renv_package_shlib("Rcpp") rcppsyms <- renv_abi_symbols(rcpplib) # perform checks for different versions of Rcpp renv_abi_check_impl_rcpp_preciouslist(package, symbols, rcppsyms, problems) } renv_abi_check_impl_rcpp_preciouslist <- function(package, symbols, rcppsyms, problems) { # check for dependency on Rcpp_precious APIs required <- grep("Rcpp_precious", symbols$symbol, value = TRUE) if (empty(required)) return() # check for Rcpp_precious APIs being available available <- grep("Rcpp_precious", rcppsyms$symbol, value = TRUE) if (length(available)) return() problem <- renv_abi_problem( package = paste(package, renv_package_version(package)), dependency = paste("Rcpp", renv_package_version("Rcpp")), reason = "Rcpp_precious_list" ) problems$push(problem) } renv_abi_symbols <- function(path, args = NULL) { # invoke nm to read symbols output <- renv_system_exec( command = "nm", args = c(args, renv_shell_path(path)), action = "reading symbols" ) # parse output parts <- strsplit(output, "\\s+") data <- .mapply(c, parts, NULL) names(data) <- c("offset", "type", "symbol") # join into data.frame as_data_frame(data) } renv_abi_problem <- function(package, dependency, reason) { list( package = package, dependency = dependency, reason = reason ) } renv_abi_packages <- function(project, libpaths) { # create a lockfile lockfile <- snapshot( library = libpaths, lockfile = NULL, type = "all", project = project ) # return package names names(lockfile$Packages) } renv_abi_deps_macos <- function(package, problems) { # TODO } renv_abi_deps_linux <- function(package, problems) { # get shlib path, if any shlib <- renv_package_shlib(package) if (!file.exists(shlib)) return() # attempt to read dependencies output <- renv_system_exec("ldd", renv_shell_path(shlib)) # look for 'not found' entries idx <- regexpr(" => not found", output, fixed = TRUE) matches <- substring(output, 2L, idx - 1L) # drop duplicates, empty strings names <- unique(matches[nzchar(matches)]) if (empty(names)) return() # add problems for (name in names) { fmt <- "%s %s (%s)" package <- sprintf(fmt, package, renv_package_version(package), shlib) problem <- renv_abi_problem( package = package, dependency = name, reason = "missing" ) problems$push(problem) } } renv/R/envir.R0000644000176200001440000000055114731330072012674 0ustar liggesusers renv_envir_self <- function() { parent.env(environment()) } renv_envir_clear <- function(envir) { vars <- ls(envir = envir, all.names = TRUE) rm(list = vars, envir = envir, inherits = FALSE) } renv_envir_unwrap <- function(envir) { eapply(envir, function(node) { if (is.environment(node)) renv_envir_unwrap(node) else node }) } renv/R/install.R0000644000176200001440000006310014761163114013222 0ustar liggesusers # an explicitly-requested package type in a call to 'install()' the$install_pkg_type <- NULL # an explicitly-requested dependencies field in a call to 'install()' the$install_dependency_fields <- NULL # the formatted width of installation steps printed to the console the$install_step_width <- 48L #' Install packages #' #' @description #' Install one or more \R packages, from a variety of remote sources. #' `install()` uses the same machinery as [restore()] (i.e. it uses cached #' packages where possible) but it does not respect the lockfile, instead #' installing the latest versions available from CRAN. #' #' See `vignette("package-install")` for more details. #' #' # `Remotes` #' #' `install()` (called without arguments) will respect the `Remotes` field #' of the `DESCRIPTION` file (if present). This allows you to specify places #' to install a package other than the latest version from CRAN. #' See for details. #' #' # Bioconductor #' #' Packages from Bioconductor can be installed by using the `bioc::` prefix. #' For example, #' #' ``` #' renv::install("bioc::Biobase") #' ``` #' #' will install the latest-available version of Biobase from Bioconductor. #' #' renv depends on BiocManager (or, for older versions of \R, BiocInstaller) #' for the installation of packages from Bioconductor. If these packages are #' not available, renv will attempt to automatically install them before #' fulfilling the installation request. #' #' @inherit renv-params #' #' @param include Packages which should be installed. `include` can #' occasionally be useful when you'd like to call `renv::install()` with #' no arguments, but restrict package installation to only some subset #' of dependencies in the project. #' #' @param exclude Packages which should not be installed. `exclude` is useful #' when using `renv::install()` to install all dependencies in a project, #' except for a specific set of packages. #' #' @param verbose Boolean; report output from `R CMD build` and `R CMD INSTALL` #' during installation? When `NULL` (the default), the value of `config$install.verbose()` #' will be used. When `FALSE`, installation output will be emitted only if #' a package fails to install. #' #' @param lock Boolean; update the `renv.lock` lockfile after the successful #' installation of the requested packages? #' #' @return A named list of package records which were installed by renv. #' #' @export #' #' @examples #' \dontrun{ #' #' # install the latest version of 'digest' #' renv::install("digest") #' #' # install an old version of 'digest' (using archives) #' renv::install("digest@@0.6.18") #' #' # install 'digest' from GitHub (latest dev. version) #' renv::install("eddelbuettel/digest") #' #' # install a package from GitHub, using specific commit #' renv::install("eddelbuettel/digest@@df55b00bff33e945246eff2586717452e635032f") #' #' # install a package from Bioconductor #' # (note: requires the BiocManager package) #' renv::install("bioc::Biobase") #' #' # install a package, specifying path explicitly #' renv::install("~/path/to/package") #' #' # install packages as declared in the project DESCRIPTION file #' renv::install() #' #' } install <- function(packages = NULL, ..., include = NULL, exclude = NULL, library = NULL, type = NULL, rebuild = FALSE, repos = NULL, prompt = interactive(), dependencies = NULL, verbose = NULL, lock = FALSE, project = NULL) { renv_consent_check() renv_scope_error_handler() # allow user to provide additional package names as part of '...' if (!missing(...)) { dots <- list(...) names(dots) <- names(dots) %||% rep.int("", length(dots)) packages <- c(packages, dots[!nzchar(names(dots))]) } project <- renv_project_resolve(project) renv_project_lock(project = project) renv_scope_verbose_if(prompt) # handle 'dependencies' if (!is.null(dependencies)) { fields <- renv_description_dependency_fields(dependencies, project = project) renv_scope_binding(the, "install_dependency_fields", fields) } # handle 'verbose' verbose <- verbose %||% config$install.verbose() renv_scope_options(renv.config.install.verbose = verbose) # set up library paths libpaths <- renv_libpaths_resolve(library) renv_scope_libpaths(libpaths) # check for explicitly-provided type -- we handle this specially for PPM if (!is.null(type)) { renv_scope_binding(the, "install_pkg_type", type) renv_scope_options(pkgType = type) } # override repositories if requested repos <- repos %||% config$repos.override() if (length(repos)) renv_scope_options(repos = repos) # if users have requested the use of pak, delegate there if (config$pak.enabled() && !recursing()) { renv_pak_init() return( renv_pak_install( packages = packages, library = libpaths, type = type, rebuild = rebuild, prompt = prompt, project = project ) ) } # resolve remotes from explicitly-requested packages remotes <- if (length(packages)) { remotes <- map(packages, renv_remotes_resolve) names(remotes) <- map_chr(remotes, `[[`, "Package") remotes } # figure out which packages we should install packages <- names(remotes) %||% renv_snapshot_dependencies(project, dev = TRUE) # apply exclude parameter if (length(exclude)) packages <- setdiff(packages, exclude) # apply include parameter if (length(include)) packages <- intersect(packages, include) if (empty(packages)) { writef("- There are no packages to install.") return(invisible(list())) } # add bioconductor packages if necessary if (renv_bioconductor_required(remotes)) { bioc <- c(renv_bioconductor_manager(), "BiocVersion") packages <- unique(c(packages, bioc)) } # don't update renv unless it was explicitly requested if (!"renv" %in% names(remotes)) packages <- setdiff(packages, "renv") # start building a list of records; they should be resolved this priority: # # 1. explicit requests from the user # 2. remotes declarations from the DESCRIPTION file # 3. existing version in library, if any # 4. fallback to package repositories # # we overlay 1 and 2 here, and then do 3 and 4 dynamically if required # during the retrieve + install stages records <- overlay(renv_project_remotes(project), remotes) # run install preflight checks if (!renv_install_preflight(project, libpaths, records)) cancel_if(prompt && !proceed()) # we're now ready to start installation renv_scope_restore( project = project, library = renv_libpaths_active(), packages = names(remotes), records = records, rebuild = rebuild ) # retrieve packages records <- renv_retrieve_impl(packages) if (empty(records)) { writef("- There are no packages to install.") return(invisible(list())) } if (prompt || renv_verbose()) { renv_install_report(records, library = renv_libpaths_active()) cancel_if(prompt && !proceed()) } # check for installed dependencies if (config$sysreqs.check(default = renv_platform_linux())) { paths <- map(records, `[[`, "Path") sysreqs <- map(paths, renv_sysreqs_read) renv_sysreqs_check(sysreqs, prompt = prompt) } # install retrieved records before <- Sys.time() renv_install_impl(records) after <- Sys.time() time <- renv_difftime_format(difftime(after, before)) n <- length(records) writef("Successfully installed %s in %s.", nplural("package", n), time) # check loaded packages and inform user if out-of-sync renv_install_postamble(names(records)) # update lockfile if requested if (lock && length(records)) { # avoid next automatic snapshot renv_snapshot_auto_suppress_next() # re-compute the records, to ensure they're normalized in the same # way as they might be in snapshot() # https://github.com/rstudio/renv/issues/1828 updates <- renv_lockfile_create( project = project, libpaths = libpaths, packages = names(records), exclude = exclude, prompt = FALSE, force = TRUE ) # overlay these records onto the existing lockfile lockfile <- renv_lockfile_load(project = project) lockfile <- renv_lockfile_modify(lockfile, renv_lockfile_records(updates)) renv_lockfile_save(lockfile, project = project) } invisible(records) } renv_install_impl <- function(records) { staged <- renv_config_install_staged() writef(header("Installing packages")) if (staged) renv_install_staged(records) else renv_install_default(records) invisible(TRUE) } renv_install_staged <- function(records) { # get current libpaths libpaths <- renv_libpaths_all() # set up a dummy library path for installation templib <- renv_install_staged_library_path() defer(unlink(templib, recursive = TRUE)) renv_scope_libpaths(c(templib, libpaths)) # perform the install renv_install_default(records) # migrate packages into true library library <- nth(libpaths, 1L) sources <- list.files(templib, full.names = TRUE) targets <- file.path(library, basename(sources)) names(targets) <- sources enumerate(targets, renv_file_move, overwrite = TRUE) # clear filebacked cache entries descpaths <- file.path(targets, "DESCRIPTION") renv_filebacked_clear("renv_description_read", descpaths) renv_filebacked_clear("renv_hash_description", descpaths) invisible(targets) } renv_install_staged_library_path_impl <- function() { # get current library path libpath <- renv_libpaths_active() # retrieve current project, library path stagedlib <- local({ # allow user configuration of staged library location override <- Sys.getenv("RENV_PATHS_LIBRARY_STAGING", unset = NA) if (!is.na(override)) return(override) # if we have an active project, use that path project <- renv_project_get(default = NULL) if (!is.null(project)) return(renv_paths_renv("staging", project = project)) # otherwise, stage within library path file.path(libpath, ".renv") }) # attempt to create it ok <- catch(ensure_directory(stagedlib)) if (inherits(ok, "error")) return(tempfile("renv-staging-")) # resolve a unique staging directory in this path # we want to keep paths short just in case; it's easy to blow up the # path length limit (hence we don't use tempfile below) for (i in 1:100) { path <- file.path(stagedlib, i) if (dir.create(path, showWarnings = FALSE)) return(path) } # all else fails, use tempfile tempfile("renv-staging-") } # NOTE: on Windows, installing packages into very long paths # can fail, as R's internal unzip utility does not handle # long Windows paths well. in addition, an renv project's # library path tends to be long, exasperating the issue. # for that reason, we try to use a shorter staging directory # # part of the challenge here is that the R temporary directory # and R library path might reside on different mounts, and so # we may want to try and avoid installing on one mount and then # copying to another mount (as that could be slow). # # note that using the renv folder might be counter-productive, # since users will want to use renv in projects sync'ed via # OneDrive and friends, and we don't want those to lock files # in the staging directory renv_install_staged_library_path <- function() { # compute path path <- renv_install_staged_library_path_impl() # create library directory ensure_directory(path) # try to make sure it has the same permissions as the library itself if (!renv_platform_windows()) { libpath <- renv_libpaths_active() umask <- Sys.umask("0") defer(Sys.umask(umask)) info <- renv_file_info(libpath) Sys.chmod(path, info$mode) } # return the created path return(path) } renv_install_default <- function(records) { state <- renv_restore_state() handler <- state$handler for (record in records) { package <- record$Package handler(package, renv_install_package(record)) } } renv_install_package <- function(record) { # get active project (if any) state <- renv_restore_state() project <- state$project # figure out whether we can use the cache during install # use library path recorded in restore state as staged installs will have # mutated the library path, placing a staging library at the front library <- renv_restore_state("library") linkable <- renv_cache_linkable(project = project, library = library) linker <- if (linkable) renv_file_link else renv_file_copy cacheable <- renv_cache_config_enabled(project = project) && renv_record_cacheable(record) && !renv_restore_rebuild_required(record) if (cacheable) { # check for cache entry and install if there path <- renv_cache_find(record) if (renv_cache_package_validate(path)) return(renv_install_package_cache(record, path, linker)) } # install the package before <- Sys.time() withCallingHandlers( renv_install_package_impl(record), error = function(e) writef("FAILED") ) after <- Sys.time() path <- record$Path type <- renv_package_type(path, quiet = TRUE) feedback <- renv_install_package_feedback(path, type) # link into cache if (renv_cache_config_enabled(project = project)) { cached <- renv_cache_synchronize(record, linkable = linkable) if (cached) feedback <- paste(feedback, "and cached") } verbose <- config$install.verbose() elapsed <- difftime(after, before, units = "auto") renv_install_step_ok(feedback, elapsed = elapsed, verbose = verbose) invisible() } renv_install_package_feedback <- function(path, type) { if (identical(type, "source")) return("built from source") if (renv_file_type(path, symlinks = FALSE) == "directory") return("copied local binary") "installed binary" } renv_install_package_cache <- function(record, cache, linker) { if (renv_install_package_cache_skip(record, cache)) return(TRUE) library <- renv_libpaths_active() target <- file.path(library, record$Package) # back up the previous installation if needed callback <- renv_file_backup(target) defer(callback()) # report successful link to user renv_install_step_start("Installing", record$Package, verbose = FALSE) before <- Sys.time() linker(cache, target) after <- Sys.time() type <- case( identical(linker, renv_file_copy) ~ "copied from cache", identical(linker, renv_file_link) ~ "linked from cache" ) elapsed <- difftime(after, before, units = "auto") renv_install_step_ok(type, elapsed = elapsed) return(TRUE) } renv_install_package_cache_skip <- function(record, cache) { # don't skip if installation was explicitly requested if (record$Package %in% renv_restore_state("packages")) return(FALSE) # check for matching cache + target paths library <- renv_restore_state("library") %||% renv_libpaths_active() target <- file.path(library, record$Package) renv_file_same(cache, target) } renv_install_package_impl_prebuild <- function(record, path, quiet) { # check whether user wants us to build before install if (!identical(config$install.build(), TRUE)) return(path) # if this package already appears to be built, nothing to do if (renv_package_built(path)) return(path) # if this is an archive, we'll need to unpack it first info <- renv_file_info(path) if (identical(info$isdir, FALSE)) { # find the package directory files <- renv_archive_list(path) descpath <- grep("(?:^|/)DESCRIPTION$", files, value = TRUE) pkgpath <- dirname(descpath)[nchar(descpath) == min(nchar(descpath))] # extract to temporary directory exdir <- tempfile("renv-build-") ensure_directory(exdir) renv_archive_decompress(path, exdir = exdir) # update path to package path <- file.path(exdir, pkgpath) # and ensure we build in this directory renv_scope_wd(path) } # if this package depends on a VignetteBuilder that is not installed, # then we can't proceed descpath <- file.path(path, "DESCRIPTION") desc <- renv_description_read(descpath) builder <- desc[["VignetteBuilder"]] if (!is.null(builder) && !renv_package_installed(builder)) { fmt <- "Skipping package build: vignette builder '%s' is not installed" writef(fmt, builder) return(path) } verbose <- config$install.verbose() renv_install_step_start("Building", record$Package, verbose = verbose) before <- Sys.time() package <- record$Package newpath <- r_cmd_build(package, path) after <- Sys.time() elapsed <- difftime(after, before, units = "auto") renv_install_step_ok("from source", elapsed = elapsed) newpath } renv_install_package_impl <- function(record, quiet = TRUE) { package <- record$Package # get path for package path <- record$Path # check if it's an archive (versus an unpacked directory) info <- renv_file_info(path) isarchive <- identical(info$isdir, FALSE) subdir <- record$RemoteSubdir %||% "" if (isarchive) { # re-pack archives if they appear to have their package # sources contained as part of a sub-directory path <- renv_package_unpack(package, path, subdir = subdir) } else if (nzchar(subdir)) { # for directories, we may need to use subdir to find the package path components <- c(path, subdir) path <- paste(components, collapse = "/") } # check whether we should build before install path <- renv_install_package_impl_prebuild(record, path, quiet) # report start of installation to user verbose <- config$install.verbose() renv_install_step_start("Installing", record$Package, verbose = verbose) # run user-defined hooks before, after install options <- renv_install_package_options(package) before <- options$before.install %||% identity after <- options$after.install %||% identity before(package) defer(after(package)) # backup an existing installation of the package if it exists library <- renv_libpaths_active() destination <- file.path(library, package) callback <- renv_file_backup(destination) defer(callback()) # normalize paths path <- renv_path_normalize(path, mustWork = TRUE) # get library path library <- renv_libpaths_active() # if a package already exists at that path, back it up first # this avoids problems with older versions of R attempting to # overwrite a pre-existing symlink # # https://github.com/rstudio/renv/issues/611 installpath <- file.path(library, package) callback <- renv_file_backup(installpath) defer(callback()) # if this failed for some reason, just remove it if (renv_file_broken(installpath)) renv_file_remove(installpath) # if this is the path to an unpacked binary archive, # we can just copy the folder over isdir <- renv_file_type(path, symlinks = FALSE) == "directory" isbin <- renv_package_type(path, quiet = TRUE) == "binary" copyable <- isdir && isbin # shortcut via copying a binary directory if possible, # otherwise, install the package if (copyable) renv_file_copy(path, installpath, overwrite = TRUE) else r_cmd_install(package, path) # if we just installed a binary package, check that it can be loaded # (source packages are checked by default on install) withCallingHandlers( if (isbin) renv_install_test(package), error = function(err) unlink(installpath, recursive = TRUE) ) # augment package metadata after install renv_package_augment(installpath, record) # return the path to the package invisible(installpath) } renv_install_test <- function(package) { # add escape hatch, just in case # (test binaries by default on Linux, but not Windows or macOS) enabled <- Sys.getenv("RENV_INSTALL_TEST_LOAD", unset = renv_platform_linux()) if (!truthy(enabled)) return(TRUE) # check whether we should skip installation testing opts <- r_cmd_install_option(package, c("install.opts", "INSTALL_opts"), FALSE) if (is.character(opts)) { flags <- unlist(strsplit(opts, "\\s+", perl = TRUE)) if ("--no-test-load" %in% flags) return(TRUE) } # make sure we use the current library paths in the launched process rlibs <- paste(renv_libpaths_all(), collapse = .Platform$path.sep) renv_scope_envvars(R_LIBS = rlibs, R_LIBS_USER = "NULL", R_LIBS_SITE = "NULL") # also hide from user .Renviron files # https://github.com/wch/r-source/blob/1c0a2dc8ce6c05f68e1959ffbe6318a309277df3/src/library/tools/R/check.R#L273-L276 renv_scope_envvars(R_ENVIRON_USER = "NULL") # make sure R_TESTS is unset here, just in case # https://github.com/wch/r-source/blob/1c0a2dc8ce6c05f68e1959ffbe6318a309277df3/src/library/tools/R/install.R#L76-L79 renv_scope_envvars(R_TESTS = NULL) # the actual code we'll run in the other process # we use 'loadNamespace()' rather than 'library()' because some packages might # intentionally throw an error in their .onAttach() hooks # https://github.com/rstudio/renv/issues/1611 code <- expr({ .libPaths(!!.libPaths()) options(warn = 1L) loadNamespace(!!package) }) # write it to a tempfile script <- renv_scope_tempfile("renv-install-", fileext = ".R") writeLines(deparse(code), con = script) # check that the package can be loaded in a separate process renv_system_exec( command = R(), args = c("--vanilla", "-s", "-f", renv_shell_path(script)), action = sprintf("testing if '%s' can be loaded", package) ) # return TRUE to indicate successful validation TRUE } renv_install_package_options <- function(package) { options <- getOption("renv.install.package.options") options[[package]] } # nocov start renv_install_preflight_requirements <- function(records) { deps <- bapply(records, function(record) { renv_dependencies_discover_description(record$Path) }, index = "ParentPackage") splat <- split(deps, deps$Package) bad <- enumerate(splat, function(package, requirements) { # skip NULL records (should be handled above) record <- records[[package]] if (is.null(record)) return(NULL) version <- record$Version # drop packages without explicit version requirement requirements <- requirements[nzchar(requirements$Require), ] if (nrow(requirements) == 0) return(NULL) # add in requested version requirements$RequestedVersion <- version # generate expressions to evaluate fmt <- "package_version('%s') %s package_version('%s')" code <- with(requirements, sprintf(fmt, RequestedVersion, Require, Version)) parsed <- parse(text = code) ok <- map_lgl(parsed, eval, envir = baseenv()) # return requirements that weren't satisfied requirements[!ok, ] }) bad <- bind(unname(bad)) if (empty(bad)) return(TRUE) package <- bad$ParentPackage requires <- sprintf("%s (%s %s)", bad$Package, bad$Require, bad$Version) actual <- sprintf("%s %s", bad$Package, bad$RequestedVersion) fmt <- "Package '%s' requires '%s', but '%s' will be installed" text <- sprintf(fmt, format(package), format(requires), format(actual)) if (renv_verbose()) { bulletin( "The following issues were discovered while preparing for installation:", text, "Installation of these packages may not succeed." ) } if (interactive() && !proceed()) return(FALSE) TRUE } # nocov end renv_install_postamble <- function(packages) { # only diagnose packages currently loaded packages <- renv_vector_intersect(packages, loadedNamespaces()) installed <- map_chr(packages, renv_package_version) loaded <- map_chr(packages, renv_namespace_version) bulletin( c("", "The following loaded package(s) have been updated:"), packages[installed != loaded], "Restart your R session to use the new versions." ) TRUE } renv_install_preflight_unknown_source <- function(records) { renv_check_unknown_source(records) } renv_install_preflight_permissions <- function(library) { # try creating and deleting a directory in the library folder file <- renv_scope_tempfile(".renv-write-test-", tmpdir = library) dir.create(file, recursive = TRUE, showWarnings = FALSE) # check if we created the directory successfully info <- renv_file_info(file) if (identical(info$isdir, TRUE)) return(TRUE) # nocov start if (renv_verbose()) { # construct header for message preamble <- "renv appears to be unable to access the requested library path:" # construct footer for message info <- as.list(Sys.info()) fmt <- "Check that the '%s' user has read / write access to this directory." postamble <- sprintf(fmt, info$effective_user %||% info$user) # print it bulletin( preamble = preamble, values = library, postamble = postamble ) } # nocov end FALSE } renv_install_preflight <- function(project, libpaths, records) { library <- nth(libpaths, 1L) records <- filter(records, Negate(is.function)) all( renv_install_preflight_unknown_source(records), renv_install_preflight_permissions(library) ) } renv_install_report <- function(records, library) { renv_pretty_print_records( "The following package(s) will be installed:", records, sprintf("These packages will be installed into %s.", renv_path_pretty(library)) ) } renv_install_step_start <- function(action, package, verbose = FALSE) { if (verbose) return(writef("- %s %s ...", action, package)) message <- sprintf("- %s %s ... ", action, package) printf(format(message, width = the$install_step_width)) } renv_install_step_ok <- function(..., elapsed = NULL, verbose = FALSE) { renv_report_ok( message = paste(..., collapse = ""), elapsed = elapsed, verbose = verbose ) } renv/R/stack.R0000644000176200001440000000157414731330073012665 0ustar liggesusers stack <- function(mode = "list") { .data <- list() storage.mode(.data) <- mode list( push = function(...) { dots <- list(...) for (data in dots) { if (is.null(data)) .data[length(.data) + 1] <<- list(NULL) else .data[[length(.data) + 1]] <<- data } }, pop = function() { item <- .data[[length(.data)]] length(.data) <<- length(.data) - 1 item }, peek = function() { .data[[length(.data)]] }, contains = function(data) { data %in% .data }, empty = function() { length(.data) == 0 }, get = function(index) { if (index <= length(.data)) .data[[index]] }, set = function(index, value) { .data[[index]] <<- value }, clear = function() { .data <<- list() }, data = function() { .data } ) } renv/R/equip-macos.R0000644000176200001440000000551114761163114014001 0ustar liggesusers renv_equip_macos_specs <- function() { list( "4.0" = list( url = "https://cran.r-project.org/bin/macosx/tools/clang-8.0.0.pkg", dst = "/usr/local/clang8" ), "3.7" = list( url = "https://cran.r-project.org/bin/macosx/tools/clang-8.0.0.pkg", dst = "/usr/local/clang8" ), "3.6" = list( url = "https://cran.r-project.org/bin/macosx/tools/clang-7.0.0.pkg", dst = "/usr/local/clang7" ), "3.5" = list( url = "https://cran.r-project.org/bin/macosx/tools/clang-6.0.0.pkg", dst = "/usr/local/clang6" ) ) } renv_equip_macos_spec <- function(version = getRversion()) { renv_equip_macos_specs()[[renv_version_maj_min(version)]] } renv_equip_macos <- function() { renv_equip_macos_sdk() renv_equip_macos_toolchain() } renv_equip_macos_sdk <- function() { sdk <- "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk" if (file.exists(sdk) || file.exists("/usr/include")) return(TRUE) system("/usr/bin/xcode-select --install") # give the user some time to respond to the dialog) Sys.sleep(5) } renv_equip_macos_toolchain <- function() { if (getRversion() >= "4.1.0") return() spec <- renv_equip_macos_spec() if (is.null(spec)) { fmt <- "no known toolchain recorded in renv for R %s" warningf(fmt, getRversion()) return(FALSE) } url <- spec$url dst <- spec$dst clang <- file.path(dst, "bin/clang") if (file.exists(clang)) { fmt <- "- LLVM toolchain for R %s is already installed at %s." writef(fmt, getRversion(), shQuote(dst)) return(TRUE) } destfile <- file.path(tempdir(), basename(url)) download(url, destfile = destfile) if (renv_equip_macos_rstudio(spec, destfile)) return(TRUE) command <- paste("sudo /usr/sbin/installer -pkg", shQuote(destfile), "-target /") bulletin( "The R LLVM toolchain has been successfully downloaded. Please execute:", command, "in a separate terminal to complete installation." ) TRUE } renv_equip_macos_rstudio <- function(spec, destfile) { rstudio <- renv_rstudio_available() && requireNamespace("rstudioapi", quietly = TRUE) if (!rstudio) return(FALSE) command <- paste("sudo -kS /usr/sbin/installer -pkg", shQuote(destfile), "-target /") prompt <- paste( "Installation of the R LLVM toolchain requires sudo.", "Please enter your account password.", sep = "\n" ) installed <- local({ password <- rstudioapi::askForPassword(prompt) if (is.null(password)) return(FALSE) status <- system(command, input = password) if (status != 0L) return(FALSE) TRUE }) if (!installed) return(FALSE) bulletin( "The R LLVM toolchain has been downloaded and installed to:", spec$dst, "This toolchain will be used by renv when installing packages from source." ) return(TRUE) } renv/R/parse.R0000644000176200001440000000274014731330073012666 0ustar liggesusers renv_parse_file <- function(file = "", ...) { if (nzchar(file)) { renv_scope_options(warn = -1L) text <- readLines(file, warn = FALSE, encoding = "UTF-8") renv_parse_impl(text, srcfile = file, ...) } } renv_parse_text <- function(text = NULL, ...) { if (is.character(text)) { renv_parse_impl(text, ...) } } renv_parse_impl <- function(text, ...) { # save default encoding enc <- Encoding(text) # disable warnings + encoding conversions renv_scope_options( warn = 1L, encoding = "native.enc" ) # attempt multiple parse methods methods <- list( renv_parse_impl_asis, renv_parse_impl_native, renv_parse_impl_utf8 ) # attempt with different guessed encodings encodings <- c("UTF-8", "unknown") for (encoding in encodings) { Encoding(text) <- encoding for (method in methods) { parsed <- catch(method(text, ...)) if (!inherits(parsed, "error")) return(parsed) } } # if these all fail, then just try the default # parse and let the error propagate defer(Sys.setlocale()) Encoding(text) <- enc parse(text = text, ...) } renv_parse_impl_asis <- function(text, ...) { defer(Sys.setlocale()) parse(text = text, ...) } renv_parse_impl_native <- function(text, ...) { defer(Sys.setlocale()) parse(text = enc2native(text), encoding = "unknown", ...) } renv_parse_impl_utf8 <- function(text, ...) { defer(Sys.setlocale()) parse(text = enc2utf8(text), encoding = "UTF-8", ...) } renv/R/equip.R0000644000176200001440000000154214731330072012675 0ustar liggesusers #' Install required system libraries #' #' Equip your system with libraries commonly-used during compilation of #' base and recommended \R packages. This was previously useful with older #' versions of R on windows, but is no longer terribly helpful. #' #' @return This function is normally called for its side effects. #' @export #' @keywords internal #' @examples #' \dontrun{ #' #' # download useful build tools #' renv::equip() #' #' } equip <- function() { renv_scope_error_handler() case( renv_platform_windows() ~ renv_equip_windows(), renv_platform_macos() ~ renv_equip_macos(), renv_platform_linux() ~ renv_equip_linux() ) invisible(NULL) } renv_equip_windows <- function() { invisible(renv_extsoft_install() && renv_extsoft_use()) } renv_equip_linux <- function() { stopf("renv::equip() not yet implemented for Linux") } renv/R/version.R0000644000176200001440000000426014731330073013240 0ustar liggesusers renv_version_compare <- function(lhs, rhs, n = NULL) { # retrieve versions as integer vector lhs <- unlist(unclass(numeric_version(lhs))) rhs <- unlist(unclass(numeric_version(rhs))) # compute number of components to compare n <- n %||% max(length(lhs), length(rhs)) # pad each vector with zeroes up to the requested length lhs <- c(lhs, rep.int(0L, max(0L, n - length(lhs)))) rhs <- c(rhs, rep.int(0L, max(0L, n - length(rhs)))) # iterate through each component and compare for (i in seq_len(n)) { if (lhs[[i]] < rhs[[i]]) return(-1L) else if (lhs[[i]] > rhs[[i]]) return(+1L) } # if we got here, then all components compared equal 0L } renv_version_le <- function(lhs, rhs, n = NULL) { renv_version_compare(lhs, rhs, n) <= 0L } renv_version_lt <- function(lhs, rhs, n = NULL) { renv_version_compare(lhs, rhs, n) < 0L } renv_version_eq <- function(lhs, rhs, n = NULL) { renv_version_compare(lhs, rhs, n) == 0L } renv_version_gt <- function(lhs, rhs, n = NULL) { renv_version_compare(lhs, rhs, n) > 0L } renv_version_ge <- function(lhs, rhs, n = NULL) { renv_version_compare(lhs, rhs, n) >= 0L } renv_version_match <- function(versions, request) { nrequest <- unclass(numeric_version(request))[[1L]] for (i in rev(seq_along(nrequest))) { matches <- which(map_lgl(versions, function(version) { renv_version_eq(version, request, n = i) })) if (!length(matches)) next # TODO: should '3.1' match the closest match (e.g. '3.2') or # highest match (e.g. '3.6')? sorted <- matches[sort(names(matches), decreasing = TRUE)] return(names(sorted)[[1L]]) } versions[[1L]] } renv_version_parts <- function(version, n) { # split version into parts parts <- unclass(as.numeric_version(version))[[1L]] # extend parts to size of n diff <- max(n) - length(parts) if (diff > 0) parts <- c(parts, rep.int(0L, diff)) # retrieve possibly-extended parts parts[1:n] } renv_version_maj_min <- function(version) { parts <- renv_version_parts(version, 2L) paste(parts, collapse = ".") } renv_version_length <- function(version) { nv <- as.numeric_version(version) length(unclass(nv)[[1L]]) } renv/R/paths.R0000644000176200001440000003112314746256150012701 0ustar liggesusers the$root <- NULL renv_paths_override <- function(name) { # # check for value from option # optname <- paste("renv.paths", name, sep = ".") # optval <- getOption(optname) # if (!is.null(optval)) # return(optval) # check for value from envvar envname <- paste("RENV_PATHS", toupper(name), sep = "_") envval <- Sys.getenv(envname, unset = NA) if (!is.na(envval)) return(envval) } renv_paths_common <- function(name, prefixes = NULL, ...) { # check for single absolute path supplied by user # TODO: handle multiple? end <- file.path(...) if (length(end) == 1 && renv_path_absolute(end)) return(end) # check for path provided via option root <- renv_paths_override(name) %||% renv_paths_root(name) # split path entries containing a separator if (name %in% c("cache", "local", "cellar")) { pattern <- if (renv_platform_windows()) "[;]" else "[;:]" root <- strsplit(root, pattern)[[1L]] } # form rest of path prefixed <- if (length(prefixes)) file.path(root, paste(prefixes, collapse = "/")) else root path <- file.path(prefixed, ...) if (length(path)) path else "" } renv_paths_library_root <- function(project) { renv_bootstrap_library_root(project) } renv_paths_library <- function(..., project = NULL) { project <- renv_project_resolve(project) root <- renv_paths_library_root(project) file.path(root, renv_platform_prefix(), ...) %||% "" } renv_paths_lockfile <- function(project = NULL) { # allow override # TODO: profiles? override <- Sys.getenv("RENV_PATHS_LOCKFILE", unset = NA) if (!is.na(override)) { last <- substr(override, nchar(override), nchar(override)) if (last %in% c("/", "\\")) override <- paste0(override, "renv.lock") return(override) } # otherwise, use default location (location relative to renv folder) project <- renv_project_resolve(project) renv <- renv_paths_renv(project = project) file.path(dirname(renv), "renv.lock") } renv_paths_settings <- function(project = NULL) { renv_paths_renv("settings.json", project = project) } renv_paths_activate <- function(project = NULL) { renv_paths_renv("activate.R", profile = FALSE, project = project) } renv_paths_sandbox <- function(project = NULL) { # construct a platform prefix path <- R() hash <- memoize(path, renv_hash_text(path), scope = "renv_paths_sandbox") parts <- c(renv_platform_prefix(), substring(hash, 1L, 8L)) prefix <- paste(parts, collapse = "/") # check for override root <- Sys.getenv("RENV_PATHS_SANDBOX", unset = NA) if (!is.na(root)) return(paste(c(root, prefix), collapse = "/")) # otherwise, build path in user data directory userdir <- renv_bootstrap_user_dir() paste(c(userdir, "sandbox", prefix), collapse = "/") } renv_paths_renv <- function(..., profile = TRUE, project = NULL) { renv_bootstrap_paths_renv(..., profile = profile, project = project) } renv_paths_cellar <- function(...) { renv_paths_common("cellar", c(), ...) } renv_paths_local <- function(...) { renv_paths_common("local", c(), ...) } renv_paths_source <- function(...) { renv_paths_common("source", c(), ...) } renv_paths_binary <- function(...) { renv_paths_common("binary", c(renv_platform_prefix()), ...) } renv_paths_cache <- function(..., version = NULL) { platform <- renv_platform_prefix() version <- version %||% renv_cache_version() renv_paths_common("cache", c(version, platform), ...) } renv_paths_rtools <- function() { root <- renv_paths_override("rtools") if (is.null(root)) { spec <- renv_rtools_find() root <- spec$root } root %||% "" } renv_paths_extsoft <- function(...) { renv_paths_common("extsoft", c(), ...) } renv_paths_p3m <- function(...) { renv_paths_common("p3m", c(), ...) } renv_paths_index <- function(...) { renv_paths_common("index", c(renv_platform_prefix()), ...) } renv_paths_root <- function(...) { root <- renv_paths_override("root") %||% renv_paths_root_default() file.path(root, ...) %||% "" } # nocov start renv_paths_root_default <- function() { the$root <- the$root %||% { # use tempdir for cache when running tests # this check is necessary here to support packages which might use renv # during testing (and we don't want those to try to use the user dir) if (checking()) renv_paths_root_default_tempdir() else renv_paths_root_default_impl() } } renv_paths_root_default_impl <- function() { # compute known root directories roots <- c( renv_paths_root_default_impl_v2(), renv_paths_root_default_impl_v1() ) # iterate through those roots, finding the first existing for (root in roots) if (file.exists(root)) return(root) # if none exist, choose the most recent definition roots[[1L]] } renv_paths_root_default_impl_v2 <- function() { # try using tools to get the user directory tools <- renv_namespace_load("tools") if (is.function(tools$R_user_dir)) return(tools$R_user_dir("renv", "cache")) renv_paths_root_default_impl_v2_fallback() } renv_paths_root_default_impl_v2_fallback <- function() { # try using our own backfill for older versions of R envvars <- c("R_USER_CACHE_DIR", "XDG_CACHE_HOME") for (envvar in envvars) { root <- Sys.getenv(envvar, unset = NA) if (!is.na(root)) { path <- file.path(root, "R/renv") return(path) } } # use platform-specific default fallbacks if (renv_platform_windows()) file.path(Sys.getenv("LOCALAPPDATA"), "R/cache/R/renv") else if (renv_platform_macos()) "~/Library/Caches/org.R-project.R/R/renv" else "~/.cache/R/renv" } renv_paths_root_default_impl_v1 <- function() { base <- switch( Sys.info()[["sysname"]], Darwin = Sys.getenv("XDG_DATA_HOME", "~/Library/Application Support"), Windows = Sys.getenv("LOCALAPPDATA", Sys.getenv("APPDATA")), Sys.getenv("XDG_DATA_HOME", "~/.local/share") ) file.path(base, "renv") } renv_paths_root_default_tempdir <- function() { temp <- file.path(tempdir(), "renv") ensure_directory(temp) return(temp) } # nocov end #' Path for storing global state #' #' @description #' By default, renv stores global state in the following OS-specific folders: #' #' \tabular{ll}{ #' **Platform** \tab **Location** \cr #' Linux \tab `~/.cache/R/renv` \cr #' macOS \tab `~/Library/Caches/org.R-project.R/R/renv` \cr #' Windows \tab `%LOCALAPPDATA%/R/cache/R/renv` \cr #' } #' #' If desired, this path can be customized by setting the `RENV_PATHS_ROOT` #' environment variable. This can be useful if you'd like, for example, multiple #' users to be able to share a single global cache. #' #' # Customising individual paths #' #' The various state sub-directories can also be individually adjusted, if so #' desired (e.g. you'd prefer to keep the cache of package installations on a #' separate volume). The various environment variables that can be set are #' enumerated below: #' #' \tabular{ll}{ #' \strong{Environment Variable} \tab \strong{Description} \cr #' \code{RENV_PATHS_ROOT} \tab The root path used for global state storage. \cr #' \code{RENV_PATHS_LIBRARY} \tab The path to the project library. \cr #' \code{RENV_PATHS_LIBRARY_ROOT} \tab The parent path for project libraries. \cr #' \code{RENV_PATHS_LIBRARY_STAGING} \tab The parent path used for staged package installs. \cr #' \code{RENV_PATHS_SANDBOX} \tab The path to the sandboxed \R system library. \cr #' \code{RENV_PATHS_LOCKFILE} \tab The path to the [lockfile]. \cr #' \code{RENV_PATHS_CELLAR} \tab The path to the cellar, containing local package binaries and sources. \cr #' \code{RENV_PATHS_SOURCE} \tab The path containing downloaded package sources. \cr #' \code{RENV_PATHS_BINARY} \tab The path containing downloaded package binaries. \cr #' \code{RENV_PATHS_CACHE} \tab The path containing cached package installations. \cr #' \code{RENV_PATHS_PREFIX} \tab An optional prefix to prepend to the constructed library / cache paths. \cr #' \code{RENV_PATHS_RENV} \tab The path to the project's renv folder. For advanced users only. \cr #' \code{RENV_PATHS_RTOOLS} \tab (Windows only) The path to [Rtools](https://cran.r-project.org/bin/windows/Rtools/). \cr #' \code{RENV_PATHS_EXTSOFT} \tab (Windows only) The path containing external software needed for compilation of Windows source packages. \cr #' } #' #' (If you want these settings to persist in your project, it is recommended that #' you add these to an appropriate \R startup file. For example, these could be #' set in: a project-local `.Renviron`, the user-level `.Renviron`, or a #' site-wide file at `file.path(R.home("etc"), "Renviron.site")`. See #' [Startup] for more details). #' #' Note that renv will append platform-specific and version-specific entries #' to the set paths as appropriate. For example, if you have set: #' #' ``` #' Sys.setenv(RENV_PATHS_CACHE = "/mnt/shared/renv/cache") #' ``` #' #' then the directory used for the cache will still depend on the renv cache #' version (e.g. `v2`), the \R version (e.g. `3.5`) and the platform (e.g. #' `x86_64-pc-linux-gnu`). For example: #' #' ``` #' /mnt/shared/renv/cache/v2/R-3.5/x86_64-pc-linux-gnu #' ``` #' #' This ensures that you can set a single `RENV_PATHS_CACHE` environment variable #' globally without worry that it may cause collisions or errors if multiple #' versions of \R needed to interact with the same cache. #' #' If reproducibility of a project is desired on a particular machine, it is #' highly recommended that the renv cache of installed packages + binary #' packages is backed up and persisted, so that packages can be easily restored #' in the future -- installation of packages from source can often be arduous. #' #' # Sharing state across operating systems #' #' If you need to share the same cache with multiple different Linux operating #' systems, you may want to set the `RENV_PATHS_PREFIX` environment variable #' to help disambiguate the paths used on Linux. For example, setting #' `RENV_PATHS_PREFIX = "ubuntu-bionic"` would instruct renv to construct a #' cache path like: #' #' ``` #' /mnt/shared/renv/cache/v2/ubuntu-bionic/R-3.5/x86_64-pc-linux-gnu #' ``` #' #' If this is required, it's strongly recommended that this environment #' variable is set in your \R installation's `Renviron.site` file, typically #' located at `file.path(R.home("etc"), "Renviron.site")`, so that it can be #' active for any \R sessions launched on that machine. #' #' Starting from `renv 0.13.0`, you can also instruct renv to auto-generate #' an OS-specific component to include as part of library and cache paths, #' by setting the environment variable: #' #' ``` #' RENV_PATHS_PREFIX_AUTO = TRUE #' ``` #' #' The prefix will be constructed based on fields within the system's #' `/etc/os-release` file. Note that this is the default behavior with #' `renv 1.0.6` when using R 4.4.0 or later. #' #' # Package cellar #' #' If your project depends on one or more \R packages that are not available in any #' remote location, you can still provide a locally-available tarball for renv #' to use during restore. By default, these packages should be made available in #' the folder as specified by the `RENV_PATHS_CELLAR` environment variable. The #' package sources should be placed in a file at one of these locations: #' #' - `${RENV_PATHS_CELLAR}/_.` #' - `${RENV_PATHS_CELLAR}//_.` #' - `/renv/cellar/_.` #' - `/renv/cellar//_.` #' #' where `.` is `.tar.gz` for source packages, or `.tgz` for binaries on #' macOS and `.zip` for binaries on Windows. During `restore()`, renv will #' search the cellar for a compatible package, and prefer installation with #' that copy of the package if appropriate. #' #' # Older versions #' #' Older version of renv used a different default cache location. #' Those cache locations are: #' #' \tabular{ll}{ #' **Platform** \tab **Location** \cr #' Linux \tab `~/.local/share/renv` \cr #' macOS \tab `~/Library/Application Support/renv` \cr #' Windows \tab `%LOCALAPPDATA%/renv` \cr #' } #' #' If an renv root directory has already been created in one of the old #' locations, that will still be used. This change was made to comply with the #' CRAN policy requirements of \R packages. #' #' @rdname paths #' @name paths #' #' @format NULL #' #' @export #' #' @examples #' # get the path to the project library #' path <- renv::paths$library() paths <- list( root = renv_paths_root, library = renv_paths_library, lockfile = renv_paths_lockfile, settings = renv_paths_settings, cache = renv_paths_cache, sandbox = renv_paths_sandbox ) renv/R/index.R0000644000176200001440000001101614731330073012657 0ustar liggesusers the$index <- new.env(parent = emptyenv()) index <- function(scope, key = NULL, value = NULL, limit = 3600L) { enabled <- renv_index_enabled(scope, key) if (!enabled) return(value) # resolve the root directory root <- renv_paths_index(scope) # make sure the directory we're indexing exists memoize( key = root, value = ensure_directory(root, umask = "0") ) # make sure the directory is readable / writable # otherwise, attempts to lock will fail # https://github.com/rstudio/renv/issues/1171 if (!renv_index_writable(root)) return(value) # resolve other variables key <- if (!is.null(key)) renv_index_encode(key) now <- as.integer(Sys.time()) # acquire index lock lockfile <- file.path(root, "index.lock") renv_scope_lock(lockfile) # load the index file index <- tryCatch(renv_index_load(root, scope), error = identity) if (inherits(index, "error")) return(value) # return index as-is when key is NULL if (is.null(key)) return(index) # check for an index entry, and return it if it exists item <- renv_index_get(root, scope, index, key, now, limit) if (!is.null(item)) return(item) # otherwise, update the index renv_index_set(root, scope, index, key, value, now, limit) } renv_index_load <- function(root, scope) { filebacked( context = "renv_index_load", path = file.path(root, "index.json"), callback = renv_index_load_impl ) } renv_index_load_impl <- function(path) { json <- tryCatch( withCallingHandlers( renv_json_read(path), warning = function(w) invokeRestart("muffleWarning") ), error = identity ) if (inherits(json, "error")) { unlink(path) return(list()) } json } renv_index_get <- function(root, scope, index, key, now, limit) { # check for index entry entry <- index[[key]] if (is.null(entry)) return(NULL) # see if it's expired if (renv_index_expired(entry, now, limit)) return(NULL) # check for in-memory cached value value <- the$index[[scope]][[key]] if (!is.null(value)) return(value) # otherwise, try to read from disk data <- file.path(root, entry$data) if (!file.exists(data)) return(NULL) # read data from disk value <- readRDS(data) # add to in-memory cache the$index[[scope]] <- the$index[[scope]] %||% new.env(parent = emptyenv()) the$index[[scope]][[key]] <- value # return value value } renv_index_set <- function(root, scope, index, key, value, now, limit) { # force promises force(value) # files being written here should be shared renv_scope_umask("0") # write data into index data <- tempfile("data-", tmpdir = root, fileext = ".rds") ensure_parent_directory(data) saveRDS(value, file = data, version = 2L) # clean up stale entries index <- renv_index_clean(root, scope, index, now, limit) # add index entry index[[key]] <- list(time = now, data = basename(data)) # update index file path <- file.path(root, "index.json") ensure_parent_directory(path) # write to tempfile and then copy to minimize risk of collisions tempfile <- tempfile(".index-", tmpdir = dirname(path), fileext = ".json") renv_json_write(index, file = tempfile) file.rename(tempfile, path) # return value value } renv_index_encode <- function(key) { key <- stringify(key) memoize(key, renv_hash_text(key)) } renv_index_clean <- function(root, scope, index, now, limit) { # figure out what cache entries have expired ok <- enum_lgl( index, renv_index_clean_impl, root = root, scope = scope, index = index, now = now, limit = limit ) # return the existing cache entries index[ok] } renv_index_clean_impl <- function(key, entry, root, scope, index, now, limit) { # check if cache entry has expired expired <- renv_index_expired(entry, now, limit) if (!expired) return(TRUE) # remove from in-memory cache cache <- the$index[[scope]] cache[[key]] <- NULL # remove from disk unlink(file.path(root, entry$data), force = TRUE) FALSE } renv_index_expired <- function(entry, now, limit) { now - entry$time >= limit } renv_index_enabled <- function(scope, key) { getOption("renv.index.enabled", default = TRUE) } renv_index_writable <- function(root) { memoize( key = root, value = unname(file.access(root, 7L) == 0L) ) } # in case of emergency, break glass renv_index_reset <- function(root = NULL) { root <- root %||% renv_paths_index() lockfiles <- list.files(root, pattern = "^index\\.lock$", full.names = TRUE) unlink(lockfiles) } renv/R/dynamic.R0000644000176200001440000000506214731330072013177 0ustar liggesusers # # Tools for so-called 'dynamic' values. These are values which are computed # once, and then memoized for the rest of the currently-executing call. # # An exit handler placed in the top-most (renv) environment is then responsible # for cleaning up any objects cached for the duration of that frame. # # This is a useful way to cache results for repeatedly-computed values # that one can reasonably expect not to change in the duration of a # particular call. # the$dynamic_envir <- NULL the$dynamic_objects <- new.env(parent = emptyenv()) dynamic <- function(key, value, envir = NULL, force = FALSE) { # allow opt-out just in case enabled <- getOption("renv.dynamic.enabled", default = TRUE) if (!enabled) return(value) # get a unique id for the scope where this function was invoked caller <- sys.call(sys.parent())[[1L]] if (renv_call_matches(caller, ":::")) caller <- caller[[3L]] # handle cases like FUN if (is.null(the$envir_self[[as.character(caller)]])) { if (!renv_tests_running()) { fmt <- "internal error: dynamic() received unexpected call '%s'" stopf(fmt, stringify(sys.call(sys.parent()))) } } # just return value if this isn't a valid dynamic scope if (!is.symbol(caller)) { dlog("dynamic", "invalid dynamic scope '%s'", stringify(sys.call(sys.parent()))) return(value) } # make sure we have a dynamic scope active the$dynamic_envir <- the$dynamic_envir %||% renv_dynamic_envir(envir) # resolve key from variables in the parent frame key <- paste(names(key), map_chr(key, stringify), sep = " = ", collapse = ", ") # put it together id <- sprintf("%s(%s)", as.character(caller), key) # if we're forcing, clear the memoized value if (force) the$dynamic_objects[[id]] <- NULL # memoize the result of the expression the$dynamic_objects[[id]] <- the$dynamic_objects[[id]] %||% { dlog("dynamic", "memoizing dynamic value for '%s'", id) value } } renv_dynamic_envir <- function(envir = NULL) { envir <- envir %||% renv_dynamic_envir_impl() defer(renv_dynamic_reset(), scope = envir) dlog("dynamic", "using dynamic environment '%s'", format(envir)) envir } renv_dynamic_envir_impl <- function() { frames <- sys.frames() for (i in seq_along(frames)) { envir <- frames[[i]] if (identical(parent.env(envir), the$envir_self)) return(envir) } stop("internal error: no renv frame available for dynamic call") } renv_dynamic_reset <- function() { dlog("dynamic", "resetting dynamic objects") the$dynamic_envir <- NULL renv_envir_clear(the$dynamic_objects) } renv/R/difftime.R0000644000176200001440000000210014731330072013330 0ustar liggesusers renv_difftime_format <- function(time, digits = 2L) { if (testing()) return("XXXX seconds") units <- attr(time, "units") %||% "" if (units == "secs" && time < 0.1) { time <- time * 1000 units <- "milliseconds" } units <- switch( units, secs = "seconds", mins = "minutes", hours = "hours", days = "days", weeks = "weeks", units ) elapsed <- format(unclass(signif(time, digits = digits))) if (elapsed %in% c("1", "1.0")) units <- substring(units, 1L, nchar(units) - 1L) paste(elapsed, units) } renv_difftime_format_short <- function(time, digits = 2L) { if (testing()) return("XXs") units <- attr(time, "units") %||% "" if (units == "secs" && time < 0.1) { time <- time * 1000 units <- "ms" } elapsed <- signif(time, digits = digits) if (nchar(elapsed) == 1L) elapsed <- paste(elapsed, ".0", sep = "") units <- switch( attr(time, "units"), secs = "s", mins = "m", hours = "h", days = "d", weeks = "w", units ) paste(elapsed, units, sep = "") } renv/R/json-write.R0000644000176200001440000000471314751450352013664 0ustar liggesusers # @param box A vector of names, whose values should be boxed. By default, # scalar values are unboxed. renv_json_config <- function(box = character()) { list(box = box) } renv_json_write <- function(object, config = NULL, file = stdout()) { config <- config %||% renv_json_config() json <- renv_json_convert_impl(NULL, object, config, 0L) if (is.null(file)) return(json) writeLines(json, con = file) } renv_json_convert <- function(object, config = renv_json_config()) { renv_json_convert_impl(NULL, object, config, 0L) } renv_json_convert_impl <- function(key, value, config, depth) { if (is.list(value) || !is.null(names(value))) return(renv_json_convert_list(key, value, config, depth)) json <- renv_json_convert_atom(key, value, config, depth) indent <- renv_json_convert_indent(depth) paste0(indent, json) } renv_json_convert_list <- function(key, value, config, depth) { indent <- renv_json_convert_indent(depth) if (empty(value)) { json <- if (is.null(names(value))) "[]" else "{}" paste0(indent, json) } else if (is.null(names(value))) { json <- enum_chr(value, renv_json_convert_impl, config = config, depth = depth + 1L) paste0(indent, "[", "\n", paste(json, collapse = ",\n"), "\n", indent, "]") } else { keys <- renv_json_quote(names(value)) vals <- enum_chr(value, renv_json_convert_impl, config = config, depth = depth + 1L) idx <- regexpr("[^[:space:]]", vals) json <- paste0(substring(vals, 1L, idx - 1L), keys, ": ", substring(vals, idx)) paste0(indent, "{", "\n", paste(json, collapse = ",\n"), "\n", indent, "}") } } renv_json_convert_atom <- function(key, value, config, depth) { unbox <- is.null(key) || !key %in% config$box || inherits(value, "AsIs") if (is.null(value)) return(if (unbox) "null" else "[]") n <- length(value) if (n == 0L) return("[]") if (is.character(value)) { value <- renv_json_quote(value) value[value %in% c("NA")] <- "null" } if (is.logical(value)) { value <- ifelse(value, "true", "false") value[is.na(value)] <- "null" } if (unbox && n == 1L) return(if (is.na(value)) "null" else paste0(value)) indent <- renv_json_convert_indent(depth) json <- paste0(renv_json_convert_indent(depth + 1L), value) paste0("[", "\n", paste(json, collapse = ",\n"), "\n", indent, "]") } renv_json_convert_indent <- function(level) { paste(rep(" ", level), collapse = "") } renv/R/library.R0000644000176200001440000000167314761163114013227 0ustar liggesusers # check for problems in the project's private library (e.g. broken symlinks # to the cache or similar) renv_library_diagnose <- function(project, libpath) { children <- list.files(libpath, full.names = TRUE) if (empty(children)) return(TRUE) # if all symlinks are broken, assume the cache is missing or has been moved missing <- !file.exists(children) if (all(missing)) { msg <- lines( "The project library's symlinks to the cache are all broken.", "Has the cache been removed, or is it otherwise inaccessible?", paste("Cache root:", shQuote(renv_paths_cache()[[1L]])) ) warning(msg, call. = FALSE) return(FALSE) } # if only some symlinks are broken, report to user if (any(missing)) { bulletin( "The following package(s) are missing entries in the cache:", basename(children[missing]), "These packages will need to be reinstalled." ) return(FALSE) } TRUE } renv/R/id.R0000644000176200001440000000664414731330073012157 0ustar liggesusers renv_id_path <- function(project) { file.path(project, "renv/project-id") } renv_id_generate <- function() { methods <- list( renv_id_generate_r, renv_id_generate_kernel, renv_id_generate_uuidgen, renv_id_generate_cscript, renv_id_generate_powershell, renv_id_generate_csc ) for (method in methods) { id <- catch(method()) if (is.character(id) && length(id) == 1 && nzchar(id)) { id <- toupper(id) return(id) } } stop("could not generate project id for this system") } renv_id_generate_kernel <- function() { uuidpath <- "/proc/sys/kernel/random/uuid" if (!file.exists(uuidpath)) { fmt <- "%s does not exist on this operating system" stopf(fmt, renv_path_pretty(uuidpath)) } readLines(uuidpath, n = 1L, warn = FALSE) } renv_id_generate_uuidgen <- function() { if (!nzchar(Sys.which("uuidgen"))) { fmt <- "program %s does not exist on this system" stopf(fmt, shQuote("uuidgen")) } system("uuidgen", intern = TRUE) } renv_id_generate_cscript <- function() { if (!renv_platform_windows()) { fmt <- "this method is only available on Windows" stopf(fmt) } if (!nzchar(Sys.which("cscript.exe"))) { fmt <- "could not find cscript.exe" stopf(fmt) } # create temporary directory dir <- renv_scope_tempfile("renv-id-") dir.create(dir) # move to it renv_scope_wd(dir) # write helper script script <- c( "set object = CreateObject(\"Scriptlet.TypeLib\")", "WScript.StdOut.WriteLine object.GUID" ) # invoke it writeLines(script, con = "uuid.vbs") args <- c("//NoLogo", "uuid.vbs") id <- renv_system_exec("cscript.exe", args, "generating UUID") # remove braces gsub("(?:^\\{|\\}$)", "", id) } renv_id_generate_powershell <- function() { if (!renv_platform_windows()) { fmt <- "this method is only available on Windows" stopf(fmt) } if (!nzchar(Sys.which("powershell.exe"))) { fmt <- "could not find powershell.exe" stopf(fmt) } command <- "[guid]::NewGuid().ToString()" args <- c("-Command", shQuote(command)) renv_system_exec("powershell.exe", args, "generating UUID") } renv_id_generate_r <- function() { if ("uuid" %in% loadedNamespaces()) return(uuid::UUIDgenerate()) libpaths <- c( .libPaths(), renv_libpaths_user(), renv_libpaths_site(), renv_libpaths_system() ) if (!requireNamespace("uuid", lib.loc = libpaths, quietly = TRUE)) stop("could not load package 'uuid'") id <- uuid::UUIDgenerate() catchall(unloadNamespace("uuid")) id } renv_id_generate_csc <- function() { csc <- local({ csc <- Sys.which("csc.exe") if (nzchar(csc)) return(csc) frameworks <- file.path( Sys.getenv("SYSTEMDRIVE", unset = "C:"), "Windows/Microsoft.NET", c("Framework", "Framework64") ) versions <- list.files(frameworks, full.names = TRUE) candidates <- file.path(versions, "csc.exe") candidates[file.exists(candidates)] }) if (empty(csc) || !file.exists(csc)) stop("could not find csc.exe") code <- " class GenerateUUID { static void Main(string[] args) { System.Console.WriteLine(System.Guid.NewGuid().ToString()); } } " renv_scope_tempdir("renv-uuid-") writeLines(code, con = "program.cs") renv_system_exec( csc[[1]], c("/nologo", "/out:program.exe", "program.cs"), "compiling uuid helper" ) renv_system_exec("program.exe", character(), "generating uuid") } renv/R/scope.R0000644000176200001440000002536714731330073012677 0ustar liggesusers renv_scope_tempdir <- function(pattern = "renv-tempdir-", tmpdir = tempdir(), umask = NULL, scope = parent.frame()) { dir <- renv_scope_tempfile(pattern = pattern, tmpdir = tmpdir, scope = scope) ensure_directory(dir, umask = umask) renv_scope_wd(dir, scope = scope) dir } renv_scope_auth <- function(record, scope = parent.frame()) { package <- if (is.list(record)) record$Package else record auth <- renv_options_override("renv.auth", package, extra = record) if (empty(auth)) return(FALSE) envvars <- catch({ if (is.function(auth)) auth(record) else auth }) # warn user if auth appears invalid if (inherits(envvars, "error")) { warning(envvars) return(FALSE) } if (empty(envvars)) return(FALSE) renv_scope_envvars(list = as.list(envvars), scope = scope) return(TRUE) } renv_scope_libpaths <- function(new = .libPaths(), scope = parent.frame()) { old <- renv_libpaths_set(new) defer(renv_libpaths_set(old), scope = scope) } renv_scope_options <- function(..., scope = parent.frame()) { new <- list(...) old <- options(new) defer(options(old), scope = scope) } renv_scope_locale <- function(category = "LC_ALL", locale = "", scope = parent.frame()) { saved <- Sys.getlocale(category) Sys.setlocale(category, locale) defer(Sys.setlocale(category, saved), scope = scope) } renv_scope_envvars <- function(..., list = NULL, scope = parent.frame()) { dots <- list %||% list(...) old <- as.list(Sys.getenv(names(dots), unset = NA)) names(old) <- names(dots) unset <- map_lgl(dots, is.null) Sys.unsetenv(names(dots[unset])) if (length(dots[!unset])) do.call(Sys.setenv, dots[!unset]) defer({ na <- is.na(old) Sys.unsetenv(names(old[na])) if (length(old[!na])) do.call(Sys.setenv, old[!na]) }, scope = scope) } renv_scope_error_handler <- function(scope = parent.frame()) { error <- getOption("error") if (!is.null(error)) return(FALSE) call <- renv_error_handler_call() options(error = call) defer({ if (identical(getOption("error"), call)) options(error = error) }, scope = scope) TRUE } # used to enforce usage of curl 7.64.1 within the # renv_paths_extsoft folder when available on Windows # nocov start renv_scope_downloader <- function(scope = parent.frame()) { if (!renv_platform_windows()) return(FALSE) if (nzchar(Sys.which("curl"))) return(FALSE) curlroot <- sprintf("curl-%s-win32-mingw", renv_extsoft_curl_version()) curl <- renv_paths_extsoft(curlroot, "bin/curl.exe") if (!file.exists(curl)) return(FALSE) old <- Sys.getenv("PATH", unset = NA) if (is.na(old)) return(FALSE) new <- paste(renv_path_normalize(dirname(curl)), old, sep = .Platform$path.sep) renv_scope_envvars(PATH = new, scope = scope) } # nocov end # nocov start renv_scope_rtools <- function(scope = parent.frame()) { if (!renv_platform_windows()) return(FALSE) # check for Rtools root <- renv_paths_rtools() if (!file.exists(root)) return(FALSE) # get environment variables appropriate for version of Rtools vars <- renv_rtools_envvars(root) # scope envvars in parent renv_scope_envvars(list = vars, scope = scope) } # nocov end # nocov start renv_scope_install <- function(scope = parent.frame()) { if (renv_platform_macos()) renv_scope_install_macos(scope) if (renv_platform_wsl()) renv_scope_install_wsl(scope) } renv_scope_install_macos <- function(scope = parent.frame()) { # check that we have command line tools available before invoking # R CMD config, as this might fail otherwise if (once()) { if (!renv_xcode_available()) { message("") message("- macOS is reporting that command line tools (CLT) are not installed.") message("- Run 'xcode-select --install' to install command line tools.") message("- Without CLT, attempts to install packages from sources may fail.") message("") } } # get the current compiler args <- c("CMD", "config", "CC") cc <- renv_system_exec(command = R(), args = args, action = "executing R CMD config CC") # just in case cc <- tail(cc, n = 1L) if (is.null(cc) || !nzchar(cc)) cc <- "clang" # check to see if we're using the system toolchain # (need to be careful since users might put e.g. ccache or other flags # into the CC variable) # helper for creating regex matching compiler bits matches <- function(pattern) { regex <- paste("(?:[[:space:]]|^)", pattern, "(?:[[:space:]]|$)", sep = "") grepl(regex, cc) } sysclang <- case( matches("/usr/bin/clang") ~ TRUE, matches("clang") ~ Sys.which("clang") == "/usr/bin/clang", FALSE ) # check for an appropriate LLVM toolchain -- if it exists, use it spec <- renv_equip_macos_spec() if (sysclang && !is.null(spec) && file.exists(spec$dst)) { path <- paste(file.path(spec$dst, "bin"), Sys.getenv("PATH"), sep = ":") renv_scope_envvars(PATH = path, scope = scope) } # generate a custom makevars that should better handle compilation # with the system toolchain (or other toolchains) makevars <- stack() # if we don't have an LLVM toolchain available, then try to generate # a Makeconf that shields compilation from usages of '-fopenmp' if (sysclang) { makeconf <- readLines(file.path(R.home("etc"), "Makeconf"), warn = FALSE) mplines <- grep(" -fopenmp", makeconf, fixed = TRUE, value = TRUE) # read a user makevars (if any) contents <- character() mvsite <- Sys.getenv( "R_MAKEVARS_SITE", unset = file.path(R.home("etc"), "Makevars.site") ) if (file.exists(mvsite)) contents <- readLines(mvsite, warn = FALSE) # override usages of '-fopenmp' replaced <- gsub(" -fopenmp", "", mplines, fixed = TRUE) amended <- unique(c(contents, replaced)) makevars$push(amended) } # write makevars to file path <- tempfile("Makevars-") contents <- unlist(makevars$data(), recursive = TRUE, use.names = FALSE) if (length(contents)) { writeLines(contents, con = path) renv_scope_envvars(R_MAKEVARS_SITE = path, scope = scope) } TRUE } renv_scope_install_wsl <- function(scope = parent.frame()) { renv_scope_envvars(R_INSTALL_STAGED = "FALSE", scope = scope) } # nocov end renv_scope_restore <- function(..., scope = parent.frame()) { state <- renv_restore_begin(...) defer(renv_restore_end(state), scope = scope) } renv_scope_git_auth <- function(scope = parent.frame()) { # try and tell git to be non-interactive by default if (renv_platform_windows()) { renv_scope_envvars( GIT_TERMINAL_PROMPT = "0", scope = scope ) } else { renv_scope_envvars( GIT_TERMINAL_PROMPT = "0", GIT_ASKPASS = "/bin/echo", scope = scope ) } # use GIT_PAT when provided pat <- Sys.getenv("GIT_PAT", unset = NA) if (!is.na(pat)) { renv_scope_envvars( GIT_USERNAME = pat, GIT_PASSWORD = "x-oauth-basic", scope = scope ) } # only set askpass when GIT_USERNAME + GIT_PASSWORD are set user <- Sys.getenv("GIT_USERNAME", unset = NA) %NA% Sys.getenv("GIT_USER", unset = NA) pass <- Sys.getenv("GIT_PASSWORD", unset = NA) %NA% Sys.getenv("GIT_PASS", unset = NA) if (is.na(user) || is.na(pass)) return(FALSE) askpass <- if (renv_platform_windows()) system.file("resources/scripts-git-askpass.cmd", package = "renv") else system.file("resources/scripts-git-askpass.sh", package = "renv") renv_scope_envvars(GIT_ASKPASS = askpass, scope = scope) return(TRUE) } renv_scope_bioconductor <- function(project = NULL, version = NULL, scope = parent.frame()) { # get current repository repos <- getOption("repos") # remove old / stale bioc repositories stale <- grepl("Bioc", names(repos)) repos <- repos[!stale] # retrieve bioconductor repositories appropriate for this project biocrepos <- renv_bioconductor_repos(project = project, version = version) # put it all together allrepos <- c(repos, biocrepos) # activate repositories in this context renv_scope_options(repos = renv_vector_unique(allrepos), scope = scope) } renv_scope_lock <- function(path = NULL, scope = parent.frame()) { renv_lock_acquire(path) defer(renv_lock_release(path), scope = scope) } renv_scope_trace <- function(what, tracer, scope = parent.frame()) { call <- sys.call() call[[1L]] <- base::trace call[["print"]] <- FALSE defer(suppressMessages(untrace(substitute(what))), scope = scope) suppressMessages(eval(call, envir = parent.frame())) } renv_scope_binding <- function(envir, symbol, replacement, scope = parent.frame()) { if (exists(symbol, envir, inherits = FALSE)) { old <- renv_binding_replace(envir, symbol, replacement) defer(renv_binding_replace(envir, symbol, old), scope = scope) } else { assign(symbol, replacement, envir) defer(rm(list = symbol, envir = envir, inherits = FALSE), scope = scope) } } renv_scope_tempfile <- function(pattern = "renv-tempfile-", tmpdir = tempdir(), fileext = "", scope = parent.frame()) { tmpdir <- normalizePath(tmpdir, winslash = "/", mustWork = TRUE) path <- renv_path_normalize(tempfile(pattern, tmpdir, fileext)) defer(unlink(path, recursive = TRUE, force = TRUE), scope = scope) invisible(path) } renv_scope_umask <- function(umask, scope = parent.frame()) { oldmask <- Sys.umask(umask) defer(Sys.umask(oldmask), scope = scope) invisible(oldmask) } renv_scope_wd <- function(dir = getwd(), scope = parent.frame()) { owd <- setwd(dir) defer(setwd(owd), scope = scope) invisible(owd) } renv_scope_sandbox <- function(scope = parent.frame()) { sandbox <- renv_sandbox_activate() defer(renv_sandbox_deactivate(), scope = scope) invisible(sandbox) } renv_scope_biocmanager <- function(scope = parent.frame()) { # silence BiocManager messages when setting repositories renv_scope_options(BiocManager.check_repositories = FALSE, scope = scope) # R-devel (4.4.0) warns when BiocManager calls .make_numeric_version() without # a character argument, so just suppress those warnings in this scope # # https://github.com/wch/r-source/commit/1338a95618ddcc8a0af77dc06e4018625de06ec3 renv_scope_options(warn = -1L, scope = scope) # return reference to BiocManager namespace renv_namespace_load("BiocManager") } renv_scope_caution <- function(value) { renv_scope_options( renv.caution.verbose = value, scope = parent.frame() ) } renv_scope_verbose_if <- function(value, scope = parent.frame()) { if (value) { renv_scope_options( renv.verbose = TRUE, scope = scope ) } } renv/R/type.R0000644000176200001440000000132314731330073012531 0ustar liggesusers renv_type_check <- function(value, type) { # quietly convert NAs to requested type if (is.null(value) || is.na(value)) return(convert(value, type)) # if the value already matches the expected type, return success if (inherits(value, type)) return(value) # create error object fmt <- "parameter '%s' is not of expected type '%s'" msg <- sprintf(fmt, deparse(substitute(value)), type) error <- simpleError(msg, sys.call(sys.parent())) # report error stop(error) } renv_type_unexpected <- function(value) { fmt <- "parameter '%s' has unexpected type '%s'" msg <- sprintf(fmt, deparse(substitute(value)), typeof(value)) error <- simpleError(msg, sys.call(sys.parent())) stop(error) } renv/R/hydrate.R0000644000176200001440000003135714761163114013225 0ustar liggesusers #' Copy packages from user libraries to a project library #' #' @description #' `hydrate()` installs missing packages from a user library into the project #' library. `hydrate()` is called automatically by [init()], and it is rare #' that you should need it otherwise, as it can easily get your project into #' an inconsistent state. #' #' It may very occasionally be useful to call `hydrate(update = "all")` if you #' want to update project packages to match those installed in your global #' library (as opposed to using [update()] which will get the latest versions #' from CRAN). In this case, you should verify that your code continues to work, #' then call [snapshot()] to record updated package versions in the lockfile. #' #' @inherit renv-params #' #' @param packages The set of \R packages to install. When `NULL`, the #' packages found by [dependencies()] are used. #' #' @param library The \R library to be hydrated. When `NULL`, the active #' library as reported by `.libPaths()` is used. #' #' @param repos The \R repositories to be used. If the project depends on any #' \R packages which cannot be found within the user library paths, then #' those packages will be installed from these repositories instead. #' #' @param update Boolean; should `hydrate()` attempt to update already-installed #' packages if the requested package is already installed in the project #' library? Set this to `"all"` if you'd like _all_ packages to be refreshed #' from the source library if possible. #' #' @param sources A vector of library paths where renv should look for packages. #' When `NULL` (the default), `hydrate()` will look in the system libraries #' (the user library, the site library and the default library) then the #' renv cache. #' #' If a package is not found in any of these locations, `hydrate()` #' will try to install it from the active R repositories. #' #' @param prompt Boolean; prompt the user before taking any action? Ignored #' when `report = FALSE`. #' #' @param report Boolean; display a report of what packages will be installed #' by `renv::hydrate()`? #' #' @return A named \R list, giving the packages that were used for hydration #' as well as the set of packages which were not found. #' #' @export #' #' @keywords internal #' #' @examples #' \dontrun{ #' #' # hydrate the active library #' renv::hydrate() #' #' } hydrate <- function(packages = NULL, ..., library = NULL, repos = getOption("repos"), update = FALSE, sources = NULL, prompt = interactive(), report = TRUE, project = NULL) { renv_scope_error_handler() renv_dots_check(...) project <- renv_project_resolve(project) renv_project_lock(project = project) renv_scope_verbose_if(prompt) renv_activate_prompt("hydrate", library, prompt, project) renv_scope_options(repos = repos) library <- renv_path_normalize(library %||% renv_libpaths_active()) packages <- packages %||% renv_hydrate_packages(project) # find packages used in this project, and the dependencies of those packages deps <- renv_hydrate_dependencies(project, packages, sources) # remove 'renv' since it's managed separately deps$renv <- NULL # figure out required packages which aren't installed missing <- deps[!nzchar(deps)] # also consider remotes; if a package is listed within Remotes, # then choose to install that package instead of linking it filter <- function(specs, remotes) { packages <- enum_chr(remotes, function(package, remote) { # if we have a package name, use it if (is.character(package) && nzchar(package)) return(package) # otherwise, resolve the remote and use the package field remote <- resolve(remote) remote[["Package"]] }) keep(specs, packages) } remotes <- renv_project_remotes(project, filter = filter, resolve = TRUE) missing[map_chr(remotes, `[[`, "Package")] <- "" # remove base + missing packages base <- renv_packages_base() packages <- deps[renv_vector_diff(names(deps), c(names(missing), base))] # figure out if we will copy or link linkable <- renv_cache_linkable(project = project, library = library) # get and construct path to library ensure_directory(library) # only hydrate with packages that are either not currently installed, # or (if update = TRUE) the version in the library is newer packages <- renv_hydrate_filter(packages, library, update) # inform user about changes if (report) { renv_hydrate_report(packages, missing, linkable) if (length(packages) || length(missing)) cancel_if(prompt && !proceed()) } # check for nothing to be done if (empty(packages) && empty(missing)) { if (report) writef("- No new packages were discovered in this project; nothing to do.") return(invisible(list(packages = list(), missing = list()))) } # copy packages from user library to cache before <- Sys.time() if (length(packages)) { if (linkable) renv_hydrate_link_packages(packages, library, project) else renv_hydrate_copy_packages(packages, library, project) } after <- Sys.time() if (report) { time <- difftime(after, before, units = "auto") fmt <- "- Hydrated %s packages in %s." writef(fmt, length(packages), renv_difftime_format(time)) } # attempt to install missing packages (if any) missing <- renv_hydrate_resolve_missing(project, library, remotes, missing) # we're done! result <- list(packages = packages, missing = missing) invisible(result) } renv_hydrate_filter <- function(packages, library, update) { # run filter keep <- enumerate(packages, function(package, path) { renv_hydrate_filter_impl(package, path, library, update) }) # filter based on kept packages packages[as.logical(keep)] } renv_hydrate_filter_impl <- function(package, path, library, update) { # if user has requested hydration of all packages, respect that if (identical(update, "all")) return(TRUE) # is the package already installed in the requested library? # if not, then we'll want to hydrate this package # if so, we'll want to compare the version first and # hydrate only if the requested version is newer than the current descpath <- file.path(library, package, "DESCRIPTION") if (file.exists(descpath)) { desc <- catch(renv_description_read(path = descpath)) if (inherits(desc, "error")) return(TRUE) } # get the current package version current <- catch(numeric_version(desc[["Version"]])) if (inherits(current, "error")) return(TRUE) # if the package is already installed and we're not updating, stop here if (identical(update, FALSE)) return(FALSE) # check to-be-copied package version requested <- catch({ desc <- renv_description_read(path = path) numeric_version(desc[["Version"]]) }) # only hydrate with a newer version requested > current } renv_hydrate_packages <- function(project) { renv_snapshot_dependencies(project, dev = TRUE) } renv_hydrate_dependencies <- function(project, packages = NULL, libpaths = NULL) { ignored <- renv_project_ignored_packages(project = project) packages <- renv_vector_diff(packages, ignored) libpaths <- libpaths %||% renv_hydrate_libpaths() renv_package_dependencies(packages, libpaths = libpaths, project = project) } # NOTE: we don't want to look in user / site libraries when testing # on CRAN, as we may accidentally find versions of packages available # on CRAN but not that we want to use during tests renv_hydrate_libpaths <- function() { conf <- config$hydrate.libpaths() if (is.character(conf) && length(conf)) conf <- unlist(strsplit(conf, ":", fixed = TRUE)) libpaths <- case( renv_tests_running() ~ character(), length(conf) ~ conf, ~ c( renv_libpaths_default(), renv_libpaths_user(), renv_libpaths_site(), renv_libpaths_system() ) ) libpaths <- .expand_R_libs_env_var(libpaths) unique(renv_path_normalize(libpaths)) } # takes a package called 'package' installed at location 'location', # copies that package into the cache, and then links from the cache # to the (private) library 'library' renv_hydrate_link_package <- function(package, location, library) { # construct path to cache record <- catch(renv_snapshot_description(location)) if (inherits(record, "error")) return(FALSE) cache <- renv_cache_find(record) if (!nzchar(cache)) return(FALSE) # copy package into the cache if (!file.exists(cache)) { ensure_parent_directory(cache) renv_file_copy(location, cache) } # link package back from cache to library target <- file.path(library, package) ensure_parent_directory(target) renv_file_link(cache, target, overwrite = TRUE) } renv_hydrate_link_packages <- function(packages, library, project) { if (renv_path_same(library, renv_paths_library(project = project))) printf("- Linking packages into the project library ... ") else printf("- Linking packages into %s ... ", renv_path_pretty(library)) callback <- renv_progress_callback(renv_hydrate_link_package, length(packages)) cached <- enumerate(packages, callback, library = library) writef("Done!") cached } # takes a package called 'package' installed at location 'location', # and copies it to the library 'library' renv_hydrate_copy_package <- function(package, location, library) { target <- file.path(library, package) renv_file_copy(location, target, overwrite = TRUE) } renv_hydrate_copy_packages <- function(packages, library, project) { if (renv_path_same(library, renv_paths_library(project = project))) printf("- Copying packages into the project library ... ") else printf("- Copying packages into %s ... ", renv_path_pretty(library)) callback <- renv_progress_callback(renv_hydrate_copy_package, length(packages)) copied <- enumerate(packages, callback, library = library) writef("Done!") copied } renv_hydrate_resolve_missing <- function(project, library, remotes, missing) { # make sure requested library is made active # # note that we only want to place the requested library on the library path; # we want to ensure that all required packages are hydrated into the # requested library # # https://github.com/rstudio/renv/issues/1177 ensure_directory(library) renv_scope_libpaths(library) packages <- names(missing) if (empty(packages)) return() writef("- Resolving missing dependencies ... ") # define a custom error handler for packages which we cannot retrieve errors <- stack() handler <- function(package, action) { error <- catch(action) if (inherits(error, "error")) errors$push(list(package = package, error = error)) } # perform the restore renv_scope_restore( project = project, library = library, packages = packages, records = remotes, handler = handler ) records <- renv_retrieve_impl(packages) renv_install_impl(records) # if we failed to restore anything, warn the user data <- errors$data() if (empty(data)) return() if (renv_verbose()) { text <- map_chr(data, function(item) { package <- item$package message <- conditionMessage(item$error) short <- trunc(paste(message, collapse = ";"), 60L) sprintf("[%s]: %s", package, short) }) bulletin( "The following package(s) were not installed successfully:", text, "You may need to manually download and install these packages." ) } invisible(data) } renv_hydrate_report <- function(packages, na, linkable) { if (renv_bootstrap_tests_running()) return() if (length(packages)) { # this is mostly a hacky way to get a list of records that the existing # record pretty-printer can handle in a clean way records <- enumerate(packages, function(package, library) { descpath <- file.path(library, "DESCRIPTION") record <- renv_snapshot_description(descpath) record$Repository <- NULL record$Source <- renv_path_aliased(dirname(library)) record }) preamble <- "The following packages were discovered:" postamble <- sprintf( "They will be %s into the project library.", if (linkable) "linked" else "copied" ) formatter <- function(lhs, rhs) { renv_record_format_short(rhs, versioned = TRUE) } renv_pretty_print_records_pair( preamble = preamble, old = list(), new = records, postamble = postamble, formatter = formatter ) } if (length(na)) { bulletin( "The following packages are used in this project, but not available locally:", csort(names(na)), "renv will attempt to download and install these packages." ) } } renv/R/session.R0000644000176200001440000000036514731330073013240 0ustar liggesusers renv_session_quiet <- function() { args <- commandArgs(trailingOnly = FALSE) index <- match("--args", args) if (!is.na(index)) args <- head(args, n = index - 1L) quiet <- c("-s", "--slave", "--no-echo") any(quiet %in% args) } renv/R/refresh.R0000644000176200001440000000271614731330073013215 0ustar liggesusers #' Refresh the local cache of available packages #' #' Query the active R package repositories for available packages, and #' update the in-memory cache of those packages. #' #' Note that \R also maintains its own on-disk cache of available packages, #' which is used by `available.packages()`. Calling `refresh()` will force #' an update of both types of caches. renv prefers using an in-memory #' cache as on occasion the temporary directory can be slow to access (e.g. #' when it is a mounted network filesystem). #' #' @return A list of package databases, invisibly -- one for each repository #' currently active in the \R session. Note that this function is normally #' called for its side effects. #' #' @export #' #' @examples #' \dontrun{ #' #' # check available packages #' db <- available.packages() #' #' # wait some time (suppose packages are uploaded / changed in this time) #' Sys.sleep(5) #' #' # refresh the local available packages database #' # (the old locally cached db will be removed) #' db <- renv::refresh() #' #' } refresh <- function() { pkgtype <- getOption("pkgType", default = "source") srcok <- pkgtype %in% c("both", "source") || getOption("install.packages.check.source", default = "yes") %in% "yes" binok <- pkgtype %in% "both" || grepl("binary", pkgtype, fixed = TRUE) list( binary = if (binok) available_packages(type = "binary", limit = 0L), source = if (srcok) available_packages(type = "source", limit = 0L) ) } renv/R/download.R0000644000176200001440000005633714760767437013422 0ustar liggesusers # download a file from 'url' to file 'destfile'. the 'type' # argument tells us the remote type, which is used to motivate # what form of authentication is appropriate; the 'quiet' # argument is used to display / suppress output. use 'headers' # (as a named character vector) to supply additional headers download <- function(url, destfile, preamble = NULL, type = NULL, quiet = FALSE, headers = NULL) { # allow for user-defined overrides override <- getOption("renv.download.override") if (is.function(override)) { result <- catch( override( url = url, destfile = destfile, quiet = quiet, mode = "wb", headers = headers ) ) if (inherits(result, "error")) renv_download_error(result, "%s", conditionMessage(result)) if (!file.exists(destfile)) renv_download_error(url, "%s does not exist", renv_path_pretty(destfile)) return(destfile) } if (quiet) renv_scope_options(renv.verbose = FALSE) # normalize separators (file URIs should normally use forward # slashes, even on Windows where the native separator is backslash) url <- chartr("\\", "/", url) destfile <- chartr("\\", "/", destfile) # notify user we're about to try downloading preamble <- preamble %||% sprintf("- Downloading '%s' ... ", url) printf(preamble) # add custom headers as appropriate for the URL custom <- renv_download_custom_headers(url) headers[names(custom)] <- custom # handle local files by just copying the file if (renv_download_local(url, destfile, headers)) return(destfile) # on Windows, try using our local curl binary if available renv_scope_downloader() # if the file already exists, compare its size with # the server's reported size for that file info <- renv_file_info(destfile) if (identical(info$isdir, FALSE)) { size <- renv_download_size(url, type, headers) if (info$size == size) { writef("OK [file is up to date]") return(destfile) } } # back up a pre-existing file if necessary callback <- renv_file_backup(destfile) defer(callback()) # form path to temporary file tempfile <- renv_scope_tempfile(tmpdir = dirname(destfile)) # request the download before <- Sys.time() status <- renv_download_impl( url = url, destfile = tempfile, type = type, request = "GET", headers = headers ) after <- Sys.time() # check for failure if (inherits(status, "condition")) renv_download_error(url, "%s", conditionMessage(status)) if (status != 0L) renv_download_error(url, "error code %i", status) if (!file.exists(tempfile)) renv_download_error(url, "%s", "unknown reason") # double-check archives are readable status <- renv_download_check_archive(tempfile) if (inherits(status, "error")) renv_download_error(url, "%s", "archive cannot be read") # everything looks ok: report success elapsed <- difftime(after, before, units = "auto") renv_download_report(elapsed, tempfile) # move the file to the requested location renv_file_move(tempfile, destfile) # one final sanity check if (!file.exists(destfile)) { fmt <- "could not move %s to %s" msg <- sprintf(fmt, renv_path_pretty(tempfile), renv_path_pretty(destfile)) renv_download_error(url, msg) } # and return path to successfully retrieved file destfile } # NOTE: only 'GET' and 'HEAD' are supported # # each downloader should return 0 on success renv_download_impl <- function(url, destfile, type = NULL, request = "GET", headers = NULL) { # normalize separators (file URIs should normally use forward # slashes, even on Windows where the native separator is backslash) url <- chartr("\\", "/", url) destfile <- chartr("\\", "/", destfile) # check that the destination file is writable if (!renv_file_writable(destfile)) { fmt <- "destination path '%s' is not writable; cannot proceed" stopf(fmt, renv_path_pretty(destfile)) } # select the appropriate downloader downloader <- switch( renv_download_method(), curl = renv_download_curl, wget = renv_download_wget, renv_download_default ) # disable warnings in this scope; it is not safe to try and catch # warnings as R will try to clean up open sockets after emitting # warnings, and catching a warning would hence prevent that # https://bugs.r-project.org/show_bug.cgi?id=18634 catch( withCallingHandlers( downloader(url, destfile, type, request, headers), warning = function(cnd) invokeRestart("muffleWarning") ) ) } renv_download_default_mode <- function(url, method) { mode <- "wb" fixup <- renv_platform_windows() && identical(method, "wininet") && substring(url, 1L, 5L) == "file:" if (fixup) mode <- "w+b" mode } renv_download_default <- function(url, destfile, type, request, headers) { # custom request types are not supported with the default downloader if (request != "GET") stopf("the default downloader does not support %s requests", request) # try and ensure headers are set for older versions of R auth <- renv_download_auth(url, type) headers[names(auth)] <- auth renv_download_default_agent_scope(headers) # on Windows, prefer 'wininet' as most users will have already configured # authentication etc. to work with this protocol methods <- c( Sys.getenv("RENV_DOWNLOAD_METHOD", unset = NA), Sys.getenv("RENV_DOWNLOAD_FILE_METHOD", unset = NA), if (renv_platform_windows()) "wininet" else "auto" ) method <- Find(Negate(is.na), methods) # headers _must_ be NULL rather than zero-length character if (length(headers) == 0) headers <- NULL mode <- renv_download_default_mode(url, method) # handle absence of 'headers' argument in older versions of R args <- list(url = url, destfile = destfile, method = method, headers = headers, mode = mode, quiet = TRUE) fmls <- formals(download.file) args <- keep(args, names(fmls)) renv_download_trace_begin(url, method) if (renv_download_trace()) str(args) do.call(download.file, args) } renv_download_default_agent_scope <- function(headers, scope = parent.frame()) { if (empty(headers)) return(FALSE) if (getRversion() >= "3.6.0") return(FALSE) renv_download_default_agent_scope_impl(headers, scope) } renv_download_default_agent_scope_impl <- function(headers, scope = parent.frame()) { utils <- asNamespace("utils") makeUserAgent <- utils$makeUserAgent ok <- is.function(makeUserAgent) && identical(formals(makeUserAgent), pairlist(format = TRUE)) if (!ok) return(FALSE) agent <- makeUserAgent(FALSE) all <- c("User-Agent" = agent, headers) headertext <- paste0(names(all), ": ", all, "\r\n", collapse = "") renv_scope_binding(utils, "makeUserAgent", function(format = TRUE) { if (format) headertext else agent }, scope = scope) return(TRUE) } renv_download_curl <- function(url, destfile, type, request, headers) { renv_download_trace_begin(url, "curl") configfile <- renv_scope_tempfile("renv-download-config-") fields <- c( "user-agent" = renv_http_useragent(), "url" = url, "output" = destfile ) # set connect timeout timeout <- config$connect.timeout() if (is.numeric(timeout)) fields[["connect-timeout"]] <- timeout # set number of retries retries <- config$connect.retry() if (is.numeric(retries)) fields[["retry"]] <- retries # set up authentication headers auth <- renv_download_auth(url, type) if (length(auth)) { authtext <- paste(names(auth), auth, sep = ": ") names(authtext) <- rep.int("header", times = length(authtext)) fields <- c(fields, authtext) } # add other custom headers if (length(headers)) { lines <- paste(names(headers), headers, sep = ": ") names(lines) <- rep.int("header", times = length(lines)) fields <- c(fields, lines) } # join together keys <- names(fields) vals <- renv_json_quote(fields) text <- paste(keys, vals, sep = " = ") # remove duplicated authorization headers dupes <- startsWith(text, "header =") & duplicated(text) text <- text[!dupes] # add in stand-along flags flags <- c("location", "fail", "silent", "show-error") if (request == "HEAD") flags <- c(flags, "head", "include") # put it all together text <- c(flags, text) writeLines(text, con = configfile) renv_download_trace_request(text) # generate the arguments to be passed to 'curl' args <- stack() # include anything provided explicitly in 'download.file.extra' here if (identical(getOption("download.file.method"), "curl")) { extra <- getOption("download.file.extra") if (length(extra)) args$push(extra) } # https://github.com/rstudio/renv/issues/1739 if (renv_platform_windows()) { enabled <- Sys.getenv("R_LIBCURL_SSL_REVOKE_BEST_EFFORT", unset = "TRUE") if (truthy(enabled)) args$push("--ssl-revoke-best-effort") } # add in any user configuration files userconfig <- getOption( "renv.curl.config", renv_download_curl_config() ) for (entry in userconfig) if (file.exists(entry)) args$push("--config", renv_shell_path(entry)) # add in our own config file (the actual request) args$push("--config", renv_shell_path(configfile)) # perform the download curl <- renv_curl_exe() output <- suppressWarnings( system2(curl, args$data(), stdout = TRUE, stderr = TRUE) ) renv_download_trace_result(output) # report non-zero status as warning status <- attr(output, "status", exact = TRUE) %||% 0L if (status != 0L) warning(output, call. = FALSE) status } renv_download_curl_config <- function() { rc <- if (renv_platform_windows()) "_curlrc" else ".curlrc" homes <- c( Sys.getenv("CURL_HOME"), Sys.getenv("HOME"), Sys.getenv("R_USER"), path.expand("~/") ) # nocov start if (renv_platform_windows()) { extra <- c( Sys.getenv("APPDATA"), file.path(Sys.getenv("USERPROFILE"), "Application Data"), dirname(Sys.which("curl")) ) homes <- c(homes, extra) } # nocov end homes <- Filter(nzchar, homes) for (home in homes) { path <- file.path(home, rc) if (file.exists(path)) return(path) } NULL } # nocov start renv_download_wget <- function(url, destfile, type, request, headers) { renv_download_trace_begin(url, "wget") configfile <- renv_scope_tempfile("renv-download-config-") fields <- c( "user-agent" = renv_http_useragent(), "quiet" = "on" ) auth <- renv_download_auth(url, type) if (length(auth)) { authtext <- paste(names(auth), auth, sep = ": ") names(authtext) <- "header" fields <- c(fields, authtext) } if (length(headers)) { lines <- paste(names(headers), headers, sep = ": ") names(lines) <- "header" fields <- c(fields, lines) } keys <- names(fields) vals <- unlist(fields) text <- paste(keys, vals, sep = " = ") writeLines(text, con = configfile) renv_download_trace_request(text) args <- stack() if (identical(getOption("download.file.method"), "wget")) { extra <- getOption("download.file.extra") if (length(extra)) args$push(extra) } args$push("--config", renv_shell_path(configfile)) # NOTE: '-O' does not write headers to file; we need to manually redirect # in that case status <- if (request == "HEAD") { args$push("--server-response", "--spider") args$push(">", renv_shell_path(destfile), "2>&1") cmdline <- paste("wget", paste(args$data(), collapse = " ")) return(suppressWarnings(system(cmdline))) } args$push("-O", renv_shell_path(destfile)) args$push(renv_shell_quote(url)) output <- suppressWarnings( system2("wget", args$data(), stdout = TRUE, stderr = TRUE) ) renv_download_trace_result(output) status <- attr(output, "status", exact = TRUE) %||% 0L if (status != 0L) warning(output, call. = FALSE) status } # nocov end renv_download_auth_type <- function(url) { github_hosts <- c( "https://api.github.com/", "https://raw.githubusercontent.com/" ) for (host in github_hosts) if (startsWith(url, host)) return("github") gitlab_hosts <- c( "https://gitlab.com/" ) for (host in gitlab_hosts) if (startsWith(url, host)) return("gitlab") bitbucket_hosts <- c( "https://api.bitbucket.org/", "https://bitbucket.org/" ) for (host in bitbucket_hosts) if (startsWith(url, host)) return("bitbucket") "unknown" } renv_download_auth <- function(url, type) { type <- tolower(type %||% renv_download_auth_type(url)) switch( type, bitbucket = renv_download_auth_bitbucket(), github = renv_download_auth_github(url), gitlab = renv_download_auth_gitlab(), character() ) } renv_download_auth_bitbucket <- function() { user <- Sys.getenv("BITBUCKET_USER", unset = NA) %NA% Sys.getenv("BITBUCKET_USERNAME", unset = NA) pass <- Sys.getenv("BITBUCKET_PASS", unset = NA) %NA% Sys.getenv("BITBUCKET_PASSWORD", unset = NA) if (is.na(user) || is.na(pass)) return(character()) userpass <- paste(user, pass, sep = ":") c("Authorization" = paste("Basic", renv_base64_encode(userpass))) } renv_download_auth_github <- function(url) { token <- renv_download_auth_github_token(url) if (is.null(token)) return(character()) c("Authorization" = paste("token", token)) } renv_download_auth_github_token <- function(url) { # check for an existing token from environment variable token <- renv_bootstrap_github_token() if (length(token)) return(token) # if gitcreds is available, try to use it gitcreds <- getOption("renv.gitcreds.enabled", default = TRUE) && requireNamespace("gitcreds", quietly = TRUE) if (gitcreds) { # ensure URL has protocol pre-pended url <- renv_retrieve_origin(url) # use 'github.com' for credentials instead of 'api.github.com' url <- sub("https://api.github.com", "https://github.com", url, fixed = TRUE) # request credentials for URL dlog("download", "requesting git credentials for url '%s'", url) creds <- tryCatch( gitcreds::gitcreds_get(url), error = function(cnd) { warning(conditionMessage(cnd)) NULL } ) # use if available if (!is.null(creds)) return(creds$password) } # ask the user to set a GITHUB_PAT if (once()) { writeLines(c( "- GitHub authentication credentials are not available.", "- Please set GITHUB_PAT, or ensure the 'gitcreds' package is installed.", "- See https://usethis.r-lib.org/articles/git-credentials.html for more details." )) } } renv_download_auth_gitlab <- function() { pat <- Sys.getenv("GITLAB_PAT", unset = NA) if (is.na(pat)) return(character()) c("Private-Token" = pat) } renv_download_headers <- function(url, type = NULL, headers = NULL) { # check for compatible download method method <- renv_download_method() if (!method %in% c("libcurl", "curl", "wget")) return(list()) # perform the download file <- renv_scope_tempfile("renv-headers-") status <- renv_download_impl( url = url, destfile = file, type = type, request = "HEAD", headers = headers %||% renv_download_custom_headers(url) ) # check for failure failed <- inherits(status, "error") || !identical(status, 0L) || !file.exists(file) if (failed) { unlink(file) return(list()) } # read the downloaded headers contents <- read(file) # if redirects were required, each set of headers will # be reported separately, so just report the final set # of headers (ie: ignore redirects) splat <- strsplit(contents, "\n\n", fixed = TRUE)[[1]] text <- strsplit(splat[[length(splat)]], "\n", fixed = TRUE)[[1]] # keep only header lines lines <- grep(":", text, fixed = TRUE, value = TRUE) headers <- catch(renv_properties_read(text = lines)) names(headers) <- tolower(names(headers)) if (inherits(headers, "error")) return(list()) headers } renv_download_size <- function(url, type = NULL, headers = NULL) { memoize( key = url, value = renv_download_size_impl(url, type, headers) ) } renv_download_size_impl <- function(url, type = NULL, headers = NULL) { headers <- catch(renv_download_headers(url, type, headers)) if (inherits(headers, "error")) return(-1L) size <- headers[["x-gitlab-size"]] if (!is.null(size)) return(as.numeric(size)) size <- headers[["content-length"]] if (!is.null(size)) return(as.numeric(size)) return(-1L) } # select an appropriate download file method. we prefer curl # when available as it's the most user-customizable of all the # download methods; when not available, we fall back to libcurl # and wget (in that order). note that we don't want to use the # internal or wininet downloaders as we cannot set custom headers # with those methods. users can force a method with the # RENV_DOWNLOAD_FILE_METHOD environment variable but we generally # want to override a user-specified 'download.file.method' renv_download_method <- function() { method <- Sys.getenv("RENV_DOWNLOAD_METHOD", unset = NA) if (!is.na(method)) return(method) method <- Sys.getenv("RENV_DOWNLOAD_FILE_METHOD", unset = NA) if (!is.na(method)) return(method) # prefer curl if available if (nzchar(Sys.which("curl"))) return("curl") # if curl is not available, use libcurl if available libcurl <- capabilities("libcurl") if (length(libcurl) && libcurl) return("libcurl") # on windows, just use wininet here if (renv_platform_windows()) return("wininet") # if neither curl nor libcurl is available, prefer wget if (nzchar(Sys.which("wget"))) return("wget") # all else fails, use the internal downloader "internal" } renv_download_report <- function(elapsed, file) { if (!renv_verbose()) return() info <- renv_file_info(file) size <- if (testing()) "XXXX bytes" else structure(info$size, class = "object_size") renv_report_ok( message = format(size, units = "auto"), elapsed = elapsed ) } renv_download_check_archive <- function(destfile) { # validate the file exists if (!file.exists(destfile)) return(FALSE) # validate archive type type <- renv_archive_type(destfile) if (type == "unknown") return(FALSE) # try listing files in the archive tryCatch({renv_archive_list(destfile); TRUE}, error = identity) } renv_download_local <- function(url, destfile, headers) { # only ever used for downloads from file URIs and server URIs ok <- grepl("^file:", url) || !grepl("^[a-zA-Z]+://", url) if (!ok) return(FALSE) methods <- list( renv_download_local_copy, renv_download_local_default ) for (method in methods) { # perform the copy before <- Sys.time() status <- catch(method(url, destfile, headers)) after <- Sys.time() # check for success if (!identical(status, TRUE)) next # report download summary elapsed <- difftime(after, before, units = "auto") renv_download_report(elapsed, destfile) return(TRUE) } FALSE } renv_download_local_copy <- function(url, destfile, headers) { # remove file prefix (to get path to local / server file) url <- case( startsWith(url, "file:///") ~ substring(url, 8L), startsWith(url, "file://") ~ substring(url, 6L), startsWith(url, "file:") ~ substring(url, 6L), TRUE ~ url ) # fix up file URIs to local paths on Windows if (renv_platform_windows()) { badpath <- grepl("^/[a-zA-Z]:", url) if (badpath) url <- substring(url, 2L) } # attempt to copy ensure_parent_directory(destfile) status <- catchall(renv_file_copy(url, destfile, overwrite = TRUE)) if (!identical(status, TRUE)) return(FALSE) TRUE } renv_download_local_default <- function(url, destfile, headers) { status <- renv_download_impl( url = url, destfile = destfile, headers = headers ) identical(status, 0L) } renv_download_custom_headers <- function(url) { renv_bootstrap_download_custom_headers(url) } renv_download_available <- function(url) { # normalize separators (file URIs should normally use forward # slashes, even on Windows where the native separator is backslash) url <- chartr("\\", "/", url) # on Windows, try using our local curl binary if available renv_scope_downloader() # if we're not using curl, then use fallback method method <- renv_download_method() if (!identical(method, "curl")) return(renv_download_available_fallback(url)) # otherwise, try a couple candidate methods methods <- list( renv_download_available_headers, renv_download_available_range ) for (method in methods) { result <- catch(method(url)) if (identical(result, TRUE)) return(TRUE) } FALSE } renv_download_available_headers <- function(url) { status <- catchall( renv_download_headers( url = url, type = NULL, headers = renv_download_custom_headers(url) ) ) if (inherits(status, "condition")) return(FALSE) is.list(status) && length(status) } renv_download_available_range <- function(url) { destfile <- renv_scope_tempfile("renv-download-") # instruct curl to request only first byte extra <- c( if (identical(getOption("download.file.method"), "curl")) getOption("download.file.extra"), "-r 0-0" ) renv_scope_options(download.file.extra = paste(extra, collapse = " ")) # perform the download status <- catchall( renv_download_curl( url = url, destfile = destfile, type = NULL, request = "GET", headers = renv_download_custom_headers(url) ) ) if (inherits(status, "condition")) return(FALSE) # check for success identical(status, 0L) } renv_download_available_fallback <- function(url) { destfile <- renv_scope_tempfile("renv-download-") # just try downloading the requested URL status <- catchall( renv_download_impl( url = url, destfile = destfile, type = NULL, request = "GET", headers = renv_download_custom_headers(url) ) ) if (inherits(status, "condition")) return(FALSE) identical(status, 0L) } renv_download_error <- function(url, fmt, ...) { msg <- sprintf(fmt, ...) writef("\tERROR [%s]", msg) stopf("error downloading '%s' [%s]", url, msg, call. = FALSE) } renv_download_trace <- function() { getOption("renv.download.trace", default = FALSE) } renv_download_trace_begin <- function(url, type) { if (!renv_download_trace()) return() fmt <- "Downloading '%s' [%s]" msg <- sprintf(fmt, url, type) title <- header(msg, n = 78L) writef(c("", title, "")) } renv_download_trace_request <- function(text) { if (!renv_download_trace()) return() title <- header("Request", n = 78L, prefix = "##") writef(c(title, text, "")) } renv_download_trace_result <- function(output) { if (!renv_download_trace()) return() title <- header("Output", prefix = "##", n = 78L) text <- if (empty(output)) "[no output generated]" else output all <- c(title, text, "") writef(all) status <- attr(output, "status", exact = TRUE) %||% 0L title <- header("Status", prefix = "##", n = 78L) all <- c(title, status, "") writef(all) } renv/R/extsoft.R0000644000176200001440000000753614761163114013263 0ustar liggesusers renv_extsoft_curl_version <- function() { Sys.getenv("RENV_EXTSOFT_CURL_VERSION", unset = "7.77.0") } renv_extsoft_install <- function(quiet = FALSE) { extsoft <- renv_paths_extsoft() ensure_directory(extsoft) ensure_directory(file.path(extsoft, "lib/i386")) ensure_directory(file.path(extsoft, "lib/x64")) root <- "https://s3.amazonaws.com/rstudio-buildtools/extsoft" files <- c( sprintf("curl-%s-win32-mingw.zip", renv_extsoft_curl_version()), "glpk32.zip", "glpk64.zip", "local323.zip", "nlopt-2.4.2.zip", "spatial324.zip" ) # check for missing installs files <- Filter(renv_extsoft_install_required, files) if (empty(files)) { if (!quiet) writef("- External software is up to date.") return(TRUE) } if (interactive()) { bulletin( "The following external software tools will be installed:", files, sprintf("Tools will be installed into %s.", renv_path_pretty(extsoft)) ) cancel_if(!proceed()) } for (file in files) { # download the file url <- file.path(root, file) destfile <- renv_scope_tempfile("renv-archive-", fileext = ".zip") download(url, destfile = destfile, quiet = quiet) # write manifest manifest <- renv_extsoft_manifest_path(file) ensure_parent_directory(manifest) before <- list.files(extsoft, recursive = TRUE) # unpack archive if (file == "glpk32.zip") { unzip(destfile, files = "include/glpk.h", exdir = extsoft) unzip(destfile, exdir = file.path(extsoft, "lib/i386"), junkpaths = TRUE) } else if (file == "glpk64.zip") { unzip(destfile, files = "include/glpk.h", exdir = extsoft) unzip(destfile, exdir = file.path(extsoft, "lib/x64"), junkpaths = TRUE) } else if (file == "nlopt-2.4.2.zip") { unzip(destfile, exdir = extsoft) file.copy(file.path(extsoft, "nlopt-2.4.2/include"), extsoft, recursive = TRUE) file.copy(file.path(extsoft, "nlopt-2.4.2/lib"), extsoft, recursive = TRUE) unlink(file.path(extsoft, "nlopt-2.4.2"), recursive = TRUE) } else { unzip(destfile, exdir = extsoft) } after <- list.files(extsoft, recursive = TRUE) writeLines(setdiff(after, before), con = manifest) } writef("- External software successfully updated.") TRUE } renv_extsoft_install_required <- function(file) { manifest <- renv_extsoft_manifest_path(file) if (!file.exists(manifest)) return(TRUE) files <- catch(readLines(manifest, warn = FALSE)) if (inherits(files, "error")) return(FALSE) paths <- renv_paths_extsoft(files) !all(file.exists(paths)) } renv_extsoft_use <- function(quiet = FALSE) { extsoft <- renv_paths_extsoft() path <- "~/.R/Makevars" ensure_parent_directory(path) original <- if (file.exists(path)) readLines(path, warn = FALSE) else character() contents <- original localsoft <- paste("LOCAL_SOFT", extsoft, sep = " = ") contents <- insert(contents, "^#?LOCAL_SOFT", localsoft) localcpp <- "LOCAL_CPPFLAGS = -I\"$(LOCAL_SOFT)/include\"" contents <- insert(contents, "^#?LOCAL_CPPFLAGS", localcpp) locallibs <- "LOCAL_LIBS = -L\"$(LOCAL_SOFT)/lib$(R_ARCH)\" -L\"$(LOCAL_SOFT)/lib\"" contents <- insert(contents, "^#?LOCAL_LIBS", locallibs) libxml <- paste("LIB_XML", extsoft, sep = " = ") contents <- insert(contents, "^#?LIB_XML", libxml) if (identical(original, contents)) return(TRUE) if (interactive()) { bulletin( "The following entries will be added to ~/.R/Makevars:", c(localsoft, libxml, localcpp, locallibs), "These tools will be used when compiling R packages from source." ) cancel_if(!proceed()) } if (!quiet) writef("- '%s' has been updated.", path) writeLines(contents, con = path) TRUE } renv_extsoft_manifest_path <- function(file) { name <- paste(file, "manifest", sep = ".") renv_paths_extsoft("manifests", name) } renv/R/cleanse.R0000644000176200001440000000316214731330072013164 0ustar liggesusers # tools for cleaning up renv's cached data cleanse <- function() { enabled <- Sys.getenv("RENV_CLEANSE_ENABLED", unset = "TRUE") if (!truthy(enabled)) return(invisible(FALSE)) # remove unused sandbox directories renv_cleanse_sandbox(path = renv_paths_sandbox()) # remove empty directories in the root directory # we can't do this on Windows, as some empty directories # might also be broken junctions, and we want to keep # those around so we can inform the user that they need # to repair that if (!renv_platform_windows()) renv_cleanse_empty(path = renv_paths_root()) invisible(TRUE) } renv_cleanse_sandbox <- function(path) { # get sandbox root path root <- dirname(path) if (!file.exists(root)) return(FALSE) # list directories within dirs <- list.files(root, full.names = TRUE) # look for apparently-unused sandbox directories info <- suppressWarnings(file.info(dirs, extra_cols = FALSE)) age <- difftime(Sys.time(), info$mtime, units = "days") old <- age >= 7 # remove the old sandbox directories unlink(dirs[old], recursive = TRUE, force = TRUE) } renv_cleanse_empty <- function(path) { # no-op for Solaris if (renv_platform_solaris()) return(FALSE) if (!file.exists(path)) return(FALSE) renv_scope_wd(path) # execute system command for removing empty directories action <- "removing empty directories" if (renv_platform_windows()) { args <- c(".", ".", "/S", "/MOVE") renv_system_exec("robocopy", args, action, 0:8) } else { args <- c(".", "-type", "d", "-empty", "-delete") renv_system_exec("find", args, action) } TRUE } renv/R/deactivate.R0000644000176200001440000000110114731330072013652 0ustar liggesusers#' @rdname activate #' @param clean If `TRUE`, will also remove the `renv/` directory and the #' lockfile. #' @export deactivate <- function(project = NULL, clean = FALSE) { renv_scope_error_handler() project <- renv_project_resolve(project) renv_project_lock(project = project) renv_infrastructure_remove_rprofile(project) unload(project) if (clean) { unlink(file.path(project, "renv.lock")) unlink(file.path(project, "renv"), recursive = TRUE, force = TRUE) } renv_restart_request(project, reason = "renv deactivated") invisible(project) } renv/R/python-conda.R0000644000176200001440000000500114731330073014150 0ustar liggesusers renv_python_conda_select <- function(name, version = NULL) { # get python package version <- version %||% Sys.getenv("RENV_CONDA_PYTHON_VERSION", unset = "3.7") packages <- paste("python", version, sep = "=") # handle paths (as opposed to environment names) if (grepl("[/\\\\]", name)) { if (!file.exists(name)) return(reticulate::conda_create(envname = name, packages = packages)) return(renv_python_exe(name)) } # check for an existing conda environment envs <- reticulate::conda_list() idx <- which(name == envs$name) if (length(idx)) return(envs$python[[idx]]) # no environment exists; create it reticulate::conda_create(envname = name, packages = packages) } renv_python_conda_export_path <- function(project) { # check override override <- renv_paths_override("CONDA_EXPORT") if (!is.null(override)) return(override) # use default file.path(project, "environment.yml") } # TODO: support prompt renv_python_conda_snapshot <- function(project, prompt, python) { renv_scope_wd(project) path <- renv_python_conda_export_path(project = project) # find the root of the associated conda environment lockfile <- renv_lockfile_load(project = project) name <- lockfile$Python$Name %||% renv_python_envpath(project, "conda", version) python <- renv_python_conda_select(name) info <- renv_python_info(python) prefix <- info$root conda <- reticulate::conda_binary() args <- c( "env", "export", "--prefix", renv_shell_path(prefix), "--file", renv_shell_path(path) ) output <- if (renv_tests_running()) FALSE else "" system2(conda, args, stdout = output, stderr = output) writef("- Wrote Python packages to '%s'.", renv_path_aliased(path)) return(TRUE) } # TODO: support prompt renv_python_conda_restore <- function(project, prompt, python) { renv_scope_wd(project) path <- renv_python_conda_export_path(project = project) # find the root of the associated conda environment lockfile <- renv_lockfile_load(project = project) name <- lockfile$Python$Name %||% renv_python_envpath(project, "conda", version) python <- renv_python_conda_select(name) info <- renv_python_info(python) prefix <- info$root conda <- reticulate::conda_binary() cmd <- if (file.exists(prefix)) "update" else "create" args <- c( "env", cmd, "--prefix", renv_shell_path(prefix), "--file", renv_shell_path(path) ) output <- if (renv_tests_running()) FALSE else "" system2(conda, args, stdout = output, stderr = output) return(TRUE) } renv/R/namespace.R0000644000176200001440000000130314731330073013502 0ustar liggesusers renv_namespace_spec <- function(package) { namespace <- asNamespace(package) .getNamespaceInfo(namespace, "spec") } renv_namespace_version <- function(package) { spec <- renv_namespace_spec(package) spec[["version"]] } renv_namespace_path <- function(package) { namespace <- asNamespace(package) .getNamespaceInfo(namespace, "path") } renv_namespace_load <- function(package) { suppressPackageStartupMessages(getNamespace(package)) } renv_namespace_unload <- function(package) { unloadNamespace(package) } renv_namespace_parse <- function(package) { parseNamespaceFile( package = package, package.lib = dirname(renv_package_find(package)), mustExist = TRUE ) } renv/R/aaa.R0000644000176200001440000000261514742242046012303 0ustar liggesusers # global variables the <- new.env(parent = emptyenv()) the$paths <- new.env(parent = emptyenv()) # detect if we're running on CI ci <- function() { !is.na(Sys.getenv("CI", unset = NA)) } # check if the renv autoloader is running autoloading <- function() { getOption("renv.autoloader.running", default = FALSE) } # detect if we're running within R CMD build building <- function() { nzchar(Sys.getenv("R_CMD")) && grepl("Rbuild", basename(dirname(getwd())), fixed = TRUE) } # detect if we're running within R CMD INSTALL installing <- function() { nzchar(Sys.getenv("R_INSTALL_PKG")) } # are we running code within R CMD check? checking <- function() { "CheckExEnv" %in% search() || renv_envvar_exists("_R_CHECK_PACKAGE_NAME_") || renv_envvar_exists("_R_CHECK_SIZE_OF_TARBALL_") } # NOTE: Prefer using 'testing()' to 'renv_tests_running()' for behavior # that should apply regardless of the package currently being tested. # # 'renv_tests_running()' is appropriate when running renv's own tests. testing <- function() { identical(Sys.getenv("TESTTHAT"), "true") } devel <- function() { identical(R.version[["status"]], "Under development (unstable)") } devmode <- function() { if ("devtools" %in% loadedNamespaces()) { if (.packageName %in% devtools::dev_packages()) { return(TRUE) } } if (Sys.getenv("DEVTOOLS_LOAD") == .packageName) return(TRUE) FALSE } renv/R/config-defaults.R0000644000176200001440000002114314761167232014634 0ustar liggesusers # Auto-generated by renv_zzz_bootstrap_config() #' @rdname config #' @export #' @format NULL config <- list( activate.prompt = function(..., default = TRUE) { renv_config_get( name = "activate.prompt", type = "logical[1]", default = default, args = list(...) ) }, autoloader.enabled = function(..., default = TRUE) { renv_config_get( name = "autoloader.enabled", type = "logical[1]", default = default, args = list(...) ) }, auto.snapshot = function(..., default = FALSE) { renv_config_get( name = "auto.snapshot", type = "logical[1]", default = default, args = list(...) ) }, bitbucket.host = function(..., default = "api.bitbucket.org/2.0") { renv_config_get( name = "bitbucket.host", type = "character[1]", default = default, args = list(...) ) }, copy.method = function(..., default = "auto") { renv_config_get( name = "copy.method", type = "*", default = default, args = list(...) ) }, connect.timeout = function(..., default = 20L) { renv_config_get( name = "connect.timeout", type = "integer[1]", default = default, args = list(...) ) }, connect.retry = function(..., default = 3L) { renv_config_get( name = "connect.retry", type = "integer[1]", default = default, args = list(...) ) }, cache.enabled = function(..., default = TRUE) { renv_config_get( name = "cache.enabled", type = "logical[1]", default = default, args = list(...) ) }, cache.symlinks = function(..., default = .Platform$OS.type == "unix") { renv_config_get( name = "cache.symlinks", type = "logical[1]", default = default, args = list(...) ) }, dependency.errors = function(..., default = "reported") { renv_config_get( name = "dependency.errors", type = "character[1]", default = default, args = list(...) ) }, dependencies.limit = function(..., default = 1000L) { renv_config_get( name = "dependencies.limit", type = "integer[1]", default = default, args = list(...) ) }, exported.functions = function(..., default = "*") { renv_config_get( name = "exported.functions", type = "character[*]", default = default, args = list(...) ) }, external.libraries = function(..., default = NULL) { renv_config_get( name = "external.libraries", type = "character[*]", default = default, args = list(...) ) }, filebacked.cache = function(..., default = TRUE) { renv_config_get( name = "filebacked.cache", type = "logical[1]", default = default, args = list(...) ) }, github.host = function(..., default = "api.github.com") { renv_config_get( name = "github.host", type = "character[1]", default = default, args = list(...) ) }, gitlab.host = function(..., default = "gitlab.com") { renv_config_get( name = "gitlab.host", type = "character[1]", default = default, args = list(...) ) }, hydrate.libpaths = function(..., default = NULL) { renv_config_get( name = "hydrate.libpaths", type = "character[*]", default = default, args = list(...) ) }, install.build = function(..., default = FALSE) { renv_config_get( name = "install.build", type = "logical[1]", default = default, args = list(...) ) }, install.remotes = function(..., default = TRUE) { renv_config_get( name = "install.remotes", type = "logical[1]", default = default, args = list(...) ) }, install.shortcuts = function(..., default = TRUE) { renv_config_get( name = "install.shortcuts", type = "logical[1]", default = default, args = list(...) ) }, install.staged = function(..., default = TRUE) { renv_config_get( name = "install.staged", type = "logical[1]", default = default, args = list(...) ) }, install.transactional = function(..., default = TRUE) { renv_config_get( name = "install.transactional", type = "logical[1]", default = default, args = list(...) ) }, install.verbose = function(..., default = FALSE) { renv_config_get( name = "install.verbose", type = "logical[1]", default = default, args = list(...) ) }, locking.enabled = function(..., default = FALSE) { renv_config_get( name = "locking.enabled", type = "logical[1]", default = default, args = list(...) ) }, mran.enabled = function(..., default = FALSE) { renv_config_get( name = "mran.enabled", type = "logical[1]", default = default, args = list(...) ) }, pak.enabled = function(..., default = FALSE) { renv_config_get( name = "pak.enabled", type = "logical[1]", default = default, args = list(...) ) }, ppm.enabled = function(..., default = TRUE) { renv_config_get( name = "ppm.enabled", type = "logical[1]", default = default, args = list(...) ) }, ppm.default = function(..., default = TRUE) { renv_config_get( name = "ppm.default", type = "logical[1]", default = default, args = list(...) ) }, ppm.url = function(..., default = "https://packagemanager.posit.co/cran/latest") { renv_config_get( name = "ppm.url", type = "character[1]", default = default, args = list(...) ) }, repos.override = function(..., default = NULL) { renv_config_get( name = "repos.override", type = "character[*]", default = default, args = list(...) ) }, rspm.enabled = function(..., default = TRUE) { renv_config_get( name = "rspm.enabled", type = "logical[1]", default = default, args = list(...) ) }, sandbox.enabled = function(..., default = TRUE) { renv_config_get( name = "sandbox.enabled", type = "logical[1]", default = default, args = list(...) ) }, shims.enabled = function(..., default = TRUE) { renv_config_get( name = "shims.enabled", type = "logical[1]", default = default, args = list(...) ) }, snapshot.inference = function(..., default = TRUE) { renv_config_get( name = "snapshot.inference", type = "logical[1]", default = default, args = list(...) ) }, snapshot.validate = function(..., default = TRUE) { renv_config_get( name = "snapshot.validate", type = "logical[1]", default = default, args = list(...) ) }, startup.quiet = function(..., default = NULL) { renv_config_get( name = "startup.quiet", type = "logical[1]", default = default, args = list(...) ) }, synchronized.check = function(..., default = TRUE) { renv_config_get( name = "synchronized.check", type = "logical[1]", default = default, args = list(...) ) }, sysreqs.check = function(..., default = TRUE) { renv_config_get( name = "sysreqs.check", type = "logical[1]", default = default, args = list(...) ) }, updates.check = function(..., default = FALSE) { renv_config_get( name = "updates.check", type = "logical[1]", default = default, args = list(...) ) }, updates.parallel = function(..., default = 2L) { renv_config_get( name = "updates.parallel", type = "*", default = default, args = list(...) ) }, user.environ = function(..., default = TRUE) { renv_config_get( name = "user.environ", type = "logical[1]", default = default, args = list(...) ) }, user.library = function(..., default = FALSE) { renv_config_get( name = "user.library", type = "logical[1]", default = default, args = list(...) ) }, user.profile = function(..., default = FALSE) { renv_config_get( name = "user.profile", type = "logical[1]", default = default, args = list(...) ) } ) renv/R/archive.R0000644000176200001440000000606714731330072013202 0ustar liggesusers renv_archive_type <- function(archive) { ext <- fileext(archive) if (ext %in% c(".tgz", ".tar", ".tar.gz")) return("tar") else if (ext %in% c(".zip")) return("zip") else return("unknown") } renv_archive_list <- function(archive) { suppressWarnings(renv_archive_list_impl(archive)) } renv_archive_list_impl <- function(archive) { switch( renv_archive_type(archive), tar = untar(archive, list = TRUE), zip = unzip(archive, list = TRUE)[["Name"]], stopf("don't know how to list files in archive '%s'", basename(archive)) ) } renv_archive_decompress <- function(archive, files = NULL, exdir = ".", ...) { switch( renv_archive_type(archive), tar = renv_archive_decompress_tar(archive, files = files, exdir = exdir, ...), zip = renv_archive_decompress_zip(archive, files = files, exdir = exdir, ...), stopf("don't know how to decompress archive '%s'", basename(archive)) ) } renv_archive_decompress_tar <- function(archive, files = NULL, exdir = ".", ...) { # if an appropriate system tar is available, use it tar <- renv_tar_exe() if (nzchar(tar)) return(renv_tar_decompress(tar, archive = archive, files = files, exdir = exdir, ...)) # when using internal TAR, we want to suppress warnings # (otherwise we get noise about global PAX headers) suppressWarnings(untar(archive, files = files, exdir = exdir, tar = "internal", ...)) return(TRUE) } renv_archive_decompress_zip <- function(archive, files = NULL, exdir = ".", ...) { # the default unzip tool will give warnings rather than # errors if R was unable to extract from a zip archive status <- tryCatch( unzip(archive, files = files, exdir = exdir, ...), condition = identity ) if (inherits(status, "condition")) { fmt <- "failed to decompress '%s' [%s]" stopf(fmt, basename(archive), conditionMessage(status)) } TRUE } renv_archive_find <- function(archive, pattern) { files <- renv_archive_list(archive) grep(pattern, files, value = TRUE) } renv_archive_read <- function(archive, file) { type <- renv_archive_type(archive) case( type == "tar" ~ renv_archive_read_tar(archive, file), type == "zip" ~ renv_archive_read_zip(archive, file), ~ stopf("don't know how to read file from archive %s", renv_path_pretty(archive)) ) } renv_archive_read_tar <- function(archive, file) { # if an appropriate tar is available, use it tar <- renv_tar_exe() if (nzchar(tar)) { args <- c("xf", renv_shell_path(archive), "-O", renv_shell_path(file)) return(renv_system_exec(tar, args, action = "reading file from archive")) } # create extraction directory exdir <- renv_scope_tempfile("renv-archive-") ensure_directory(exdir) # unpack the requested file suppressWarnings(untar(archive, files = file, exdir = exdir, tar = "internal")) # and read it archive <- file.path(exdir, file) readLines(archive, warn = FALSE) } renv_archive_read_zip <- function(archive, file) { renv_scope_tempdir() conn <- unz(archive, file, encoding = "native.enc") defer(close(conn)) readLines(conn, warn = FALSE) } renv/R/filebacked.R0000644000176200001440000000533214731330073013625 0ustar liggesusers # tools for caching values read from a file, and invalidating those values if # the file mtime changes. use `renv_filebacked_set()` to associate some value # with a file at a particular point in time; `renv_filebacked_get()` will return # that value, or NULL of the file mtime has changed the$filebacked_cache <- new.env(parent = emptyenv()) renv_filebacked_clear <- function(context, path = NULL) { # get cache associated with this context envir <- renv_filebacked_envir(context) # list all available cached results existing <- ls(envir = envir, all.names = TRUE) # if path is set, use it; otherwise remove everything path <- path %||% existing # validate the requested paths exist in the environment removable <- renv_vector_intersect(path, existing) # remove them rm(list = removable, envir = envir) } renv_filebacked_set <- function(context, path, value) { # validate the path stopifnot(renv_path_absolute(path)) # create our cache entry info <- renv_file_info(path) entry <- list(value = value, info = info) # store it envir <- renv_filebacked_envir(context) assign(path, entry, envir = envir) invisible(value) } renv_filebacked_get <- function(context, path) { # validate the path if (!renv_path_absolute(path)) stopf("internal error: '%s' is not an absolute path", path) # get contextd sub-environment envir <- renv_filebacked_envir(context) # check for entry in the cache entry <- envir[[path]] if (is.null(entry)) return(NULL) # extract pieces of interest value <- entry$value oldinfo <- entry$info newinfo <- renv_file_info(path) # if the file didn't exist when we set the entry, # check and see if it's still not there if (is.na(oldinfo$isdir) && is.na(newinfo$isdir)) return(value) # compare on fields of interest fields <- c("size", "isdir", "mtime") if (!identical(oldinfo[fields], newinfo[fields])) return(NULL) # looks good value } renv_filebacked_envir <- function(context) { the$filebacked_cache[[context]] <- the$filebacked_cache[[context]] %||% new.env(parent = emptyenv()) } filebacked <- function(context, path, callback, ...) { # don't use filebacked cache when disabled config <- config$filebacked.cache() if (identical(config, FALSE)) return(callback(path, ...)) # check for cache entry -- if available, use it cache <- renv_filebacked_get(context, path) if (!is.null(cache)) return(cache) # otherwise, generate our value and cache it result <- callback(path, ...) renv_filebacked_set(context, path, result) result } renv_filebacked_invalidate <- function(path) { renv_scope_options(warn = -1L) eapply(the$filebacked_cache, function(context) { rm(list = path, envir = context) }) } renv/R/lockfiles.R0000644000176200001440000001340314731330073013525 0ustar liggesusers #' Lockfiles #' #' A **lockfile** records the state of a project at some point in time. #' #' A lockfile captures the state of a project's library at some point in time. #' In particular, the package names, their versions, and their sources (when #' known) are recorded in the lockfile. #' #' Projects can be restored from a lockfile using the [restore()] function. This #' implies reinstalling packages into the project's private library, as encoded #' within the lockfile. #' #' While lockfiles are normally generated and used with [snapshot()] / #' [restore()], they can also be edited by hand if so desired. Lockfiles are #' written as `.json`, to allow for easy consumption by other tools. #' #' An example lockfile follows: #' #' ``` #' { #' "R": { #' "Version": "3.6.1", #' "Repositories": [ #' { #' "Name": "CRAN", #' "URL": "https://cloud.r-project.org" #' } #' ] #' }, #' "Packages": { #' "markdown": { #' "Package": "markdown", #' "Version": "1.0", #' "Source": "Repository", #' "Repository": "CRAN", #' "Hash": "4584a57f565dd7987d59dda3a02cfb41" #' }, #' "mime": { #' "Package": "mime", #' "Version": "0.7", #' "Source": "Repository", #' "Repository": "CRAN", #' "Hash": "908d95ccbfd1dd274073ef07a7c93934" #' } #' } #' } #' ``` #' #' The sections used within a lockfile are described next. #' #' ## renv #' #' Information about the version of renv used to manage this project. #' #' \tabular{ll}{ #' \strong{Version} \tab The version of the renv package used with this project. \cr #' } #' #' ## R #' #' Properties related to the version of \R associated with this project. #' #' \tabular{ll}{ #' \strong{Version} \tab The version of \R used. \cr #' \strong{Repositories} \tab The \R repositories used in this project. \cr #' } #' #' ## Packages #' #' \R package records, capturing the packages used or required by a project #' at the time when the lockfile was generated. #' #' \tabular{ll}{ #' \strong{Package} \tab The package name. \cr #' \strong{Version} \tab The package version. \cr #' \strong{Source} \tab The location from which this package was retrieved. \cr #' \strong{Repository} \tab The name of the repository (if any) from which this package was retrieved. \cr #' \strong{Hash} \tab (Optional) A unique hash for this package, used for package caching. \cr #' } #' #' Additional remote fields, further describing how the package can be #' retrieved from its corresponding source, will also be included as #' appropriate (e.g. for packages installed from GitHub). #' #' ## Python #' #' Metadata related to the version of Python used with this project (if any). #' #' \tabular{ll}{ #' \strong{Version} \tab The version of Python being used. \cr #' \strong{Type} \tab The type of Python environment being used ("virtualenv", "conda", "system") \cr #' \strong{Name} \tab The (optional) name of the environment being used. #' } #' #' Note that the `Name` field may be empty. In that case, a project-local Python #' environment will be used instead (when not directly using a system copy of Python). #' #' # Caveats #' #' These functions are primarily intended for expert users -- in most cases, #' [snapshot()] and [restore()] are the primariy tools you will need when #' creating and using lockfiles. #' #' @inheritParams snapshot #' @inheritParams renv-params #' #' @param lockfile An `renv` lockfile; typically created by either #' `lockfile_create()` or `lockfile_read()`. #' #' @param file A file path, or \R connection. #' #' @family reproducibility #' @name lockfiles #' @rdname lockfiles NULL #' @param libpaths The library paths to be used when generating the lockfile. #' @rdname lockfiles #' @export lockfile_create <- function(type = settings$snapshot.type(project = project), libpaths = .libPaths(), packages = NULL, exclude = NULL, prompt = interactive(), force = FALSE, ..., project = NULL) { renv_dots_check(...) project <- renv_project_resolve(project) renv_scope_verbose_if(prompt) renv_lockfile_create( project = project, type = type, libpaths = libpaths, packages = packages, exclude = exclude, prompt = prompt, force = force ) } #' @rdname lockfiles #' @export lockfile_read <- function(file = NULL, ..., project = NULL) { project <- renv_project_resolve(project) file <- file %||% renv_paths_lockfile(project = project) renv_lockfile_read(file = file) } #' @rdname lockfiles #' @export lockfile_write <- function(lockfile, file = NULL, ..., project = NULL) { project <- renv_project_resolve(project) file <- file %||% renv_paths_lockfile(project = project) renv_lockfile_write(lockfile, file = file) } #' @param remotes An \R vector of remote specifications. #' #' @param repos A named vector, mapping \R repository names to their URLs. #' #' @rdname lockfiles #' @export lockfile_modify <- function(lockfile = NULL, ..., remotes = NULL, repos = NULL, project = NULL) { renv_dots_check(...) project <- renv_project_resolve(project) lockfile <- lockfile %||% renv_lockfile_load(project, strict = TRUE) if (!is.null(repos)) lockfile$R$Repositories <- as.list(repos) if (!is.null(remotes)) { remotes <- renv_records_resolve(remotes, latest = TRUE) names(remotes) <- map_chr(remotes, `[[`, "Package") enumerate(remotes, function(package, remote) { record <- renv_remotes_resolve(remote) renv_lockfile_records(lockfile)[[package]] <<- record }) } lockfile } renv/R/debuggify.R0000644000176200001440000000164614731330072013524 0ustar liggesusers debuggify <- function(expr) { withCallingHandlers(expr, interrupt = renv_debuggify_dump) } renv_debuggify_dump <- function(cnd) { # print a backtrace status <- sys.status() calls <- head(status$sys.calls, n = -2L) frames <- head(status$sys.frames, n = -2L) traceback <- renv_error_format(calls, frames) caution(traceback) # print information about each frame n <- length(calls) for (i in seq_along(calls)) { renv_debuggify_dump_impl( index = n - i + 1, call = calls[[i]], frame = frames[[i]] ) } } renv_debuggify_dump_impl <- function(index, call, frame) { writeLines(header(paste("Frame", index))) vars <- ls(envir = frame, all.names = TRUE) lapply(vars, renv_debuggify_dump_impl_one, call = call, frame = frame) writeLines("") } renv_debuggify_dump_impl_one <- function(var, call, frame) { if (var %in% c("expr")) return("") str(frame[[var]]) } renv/R/memoize.R0000644000176200001440000000110014731330073013206 0ustar liggesusers the$memoize <- new.env(parent = emptyenv()) memo <- function(value, scope = NULL) { scope <- scope %||% stringify(sys.call(sys.parent())[[1L]]) (the$memoize[[scope]] <- the$memoize[[scope]] %||% value) } memoize <- function(key, value, scope = NULL) { # figure out scope to use scope <- scope %||% stringify(sys.call(sys.parent())[[1L]]) # initialize memoized environment envir <- the$memoize[[scope]] <- the$memoize[[scope]] %||% new.env(parent = emptyenv()) # retrieve, or compute, memoized value envir[[key]] <- envir[[key]] %||% value } renv/R/envvar.R0000644000176200001440000000065014731330072013052 0ustar liggesusers renv_envvar_path_add <- function(envvar, value, prepend = TRUE) { old <- Sys.getenv(envvar, unset = "") old <- strsplit(old, .Platform$path.sep)[[1]] parts <- if (prepend) union(value, old) else union(old, value) new <- paste(parts, collapse = .Platform$path.sep) names(new) <- envvar do.call(Sys.setenv, as.list(new)) new } renv_envvar_exists <- function(key) { !is.na(Sys.getenv(key, unset = NA)) } renv/R/tar.R0000644000176200001440000000202114731330073012332 0ustar liggesusers renv_tar_exe <- function(default = "") { # allow override tar <- getOption("renv.tar.exe") if (!is.null(tar)) return(tar) # on unix, just use default if (renv_platform_unix()) { tar <- Sys.which("tar") if (nzchar(tar)) return(tar) } # on Windows, use system tar.exe if available root <- Sys.getenv("SystemRoot", unset = NA) if (is.na(root)) root <- "C:/Windows" # use tar if it exists tarpath <- file.path(root, "System32/tar.exe") if (file.exists(tarpath)) return(tarpath) # otherwise, give up (don't trust the arbitrary tar on PATH) default } renv_tar_decompress <- function(tar, archive, files = NULL, exdir = ".", ...) { # build argument list args <- c( "xf", renv_shell_path(archive), if (!identical(exdir, ".")) c("-C", renv_shell_path(exdir)), if (length(files)) renv_shell_path(files) ) # make sure exdir exists ensure_directory(exdir) # perform decompress return(renv_system_exec(tar, args, action = "decompressing archive")) } renv/R/dcf.R0000644000176200001440000000602214731330072012304 0ustar liggesusers # similar to base::read.dcf(), but: # - allows for whitespace between fields # - allows for non-indented field continuations # - always keeps whitespace renv_dcf_read <- function(file, text = NULL, ...) { # read file contents <- text %||% renv_dcf_read_impl(file, ...) # split on newlines parts <- strsplit(contents, "\\r?\\n(?=\\S)", perl = TRUE)[[1L]] # remove embedded newlines parts <- gsub("\\r?\\n\\s*", " ", parts, perl = TRUE) # split into key / value pairs index <- regexpr(":", parts, fixed = TRUE) keys <- substring(parts, 1L, index - 1L) vals <- substring(parts, index + 1L) # trim whitespace vals <- trimws(vals) # return early if everything looks fine ok <- nzchar(keys) if (all(ok)) { storage.mode(vals) <- "list" names(vals) <- keys return(vals) } # otherwise, fix up bad continuations starts <- which(ok) ends <- c(tail(starts - 1L, n = -1L), length(keys)) vals <- .mapply( function(start, end) paste(vals[start:end], collapse = " "), list(starts, ends), NULL ) # set up names names(vals) <- keys[ok] # done vals } renv_dcf_read_impl_encoding <- function(bytes) { # try to find encoding -- if none is declared, assume native encoding? start <- 0L while (TRUE) { # find 'Encoding' start <- grepRaw("Encoding:", bytes, fixed = TRUE, offset = start + 1L) if (length(start) == 0L) return(NULL) # check for preceding newline, or start of file if (start == 1L || bytes[[start - 1L]] == 0x0A) { start <- start + 9L break } } # find the end of the encoding field end <- grepRaw("\\r?\\n", bytes, offset = start + 1L) if (length(end) == 0L) end <- length(bytes) # pull it out field <- rawToChar(bytes[start:end]) trimws(field) } renv_dcf_read_impl <- function(file, ...) { # suppress warnings in this scope renv_scope_options(warn = -1L) # first, read the file as bytes to get encoding # use a guess for the file size to avoid expensive lookup, but fallback # if necessary bytes <- readBin(file, what = "raw", n = 8192L) if (length(bytes) == 8192L) { n <- renv_file_size(file) bytes <- readBin(con = file, what = "raw", n = n) } # try to guess the encoding encoding <- renv_dcf_read_impl_encoding(bytes) # try a bunch of candidate encodings encodings <- c(encoding, "UTF-8", "latin1", "") for (encoding in unique(encodings)) { result <- iconv(list(bytes), from = encoding, to = "UTF-8") if (!is.na(result)) return(result) } # all else fails, just pretend it's in the native encoding rawToChar(bytes) } renv_dcf_write <- function(x, file = "") { # NOTE: Older versions of write.dcf() will coerce the value into a data.frame # without setting 'optional = TRUE'; make sure we do this ourselves first value <- as_data_frame(x) keep.white <- c("Description", "Authors@R", "Author", "Built", "Packaged") result <- write.dcf(value, file = file, indent = 4L, width = 80L, keep.white = keep.white) renv_filebacked_invalidate(file) invisible(result) } renv/R/imports.R0000644000176200001440000000074614742242046013261 0ustar liggesusers #' @importFrom tools #' file_ext md5sum package_dependencies pskill psnice write_PACKAGES #' #' @importFrom utils #' adist available.packages browseURL citation contrib.url download.file #' download.packages file.edit getCRANmirrors head help install.packages #' installed.packages modifyList old.packages packageDescription #' packageVersion read.table remove.packages Rprof sessionInfo summaryRprof #' str tail tar toBibtex untar update.packages unzip URLencode zip NULL renv/R/revdeps.R0000644000176200001440000000677614742247414013251 0ustar liggesusers renv_revdeps_check <- function(project = NULL) { project <- renv_project_resolve(project) renv_scope_wd(project) renv_scope_options(repos = c(renv_bioconductor_repos(project))) case( startsWith(R.version$platform, "aarch64-apple-darwin") ~ { renv_scope_envvars( CPPFLAGS = "-I/opt/homebrew/include", LDFLAGS = "-L/opt/homebrew/lib", LIBS = "-L/opt/homebrew/lib" ) } ) package <- renv_description_read("DESCRIPTION", field = "Package") renv_infrastructure_write_entry_impl( add = "revdeps", remove = character(), file = file.path(project, ".gitignore"), create = FALSE ) blueprints <- list( list(package = project, root = "revdeps/develop"), list(package = package, root = "revdeps/current") ) zmap(blueprints, function(package, root) { writef(header("Installing %s [%s]", package, root)) renv_revdeps_check_preflight(package, "revdeps/develop", project) writef() }) revdeps <- package_dependencies(package, reverse = TRUE)[[1L]] result <- zmap(blueprints, function(package, root) { map(revdeps, function(revdep) { writef(header("Checking %s [%s]", revdep, root)) result <- catch(renv_revdeps_check_impl(revdep, root, project)) if (inherits(result, "error")) { message <- paste("Error:", conditionMessage(result)) writef(message, con = stderr()) } writef() result }) }) } renv_revdeps_check_preflight <- function(package, root, project) { dir.create(root, recursive = TRUE, showWarnings = FALSE) renv_scope_wd(root) dir.create("library.noindex", showWarnings = FALSE) dir.create("results.noindex", showWarnings = FALSE) dir.create("sources.noindex", showWarnings = FALSE) dir.create("cache.noindex", showWarnings = FALSE) renv_scope_envvars(RENV_PATHS_CACHE = "cache.noindex") renv_scope_libpaths("library.noindex") install(package, project = project) job(function() { renv::install("BiocManager") renv::install("bioc::BiocVersion") }) } renv_revdeps_check_impl <- function(revdep, root, project) { ensure_directory(root) root <- normalizePath(root, winslash = "/") renv_scope_envvars(RENV_PATHS_SOURCE = file.path(root, "sources.noindex")) renv_scope_envvars(RENV_PATHS_CACHE = file.path(root, "cache.noindex")) renv_scope_libpaths(file.path(root, "library.noindex")) checkpath <- sprintf("%s/results.noindex/%s.Rcheck/00check.log", root, revdep) if (file.exists(checkpath)) { contents <- readLines(checkpath, warn = FALSE) if (startsWith(tail(contents, 1L), "Status:")) { writef("- Package was already checked; skipping") return() } } record <- renv_remotes_resolve(revdep, latest = TRUE) path <- renv_retrieve_path(record) job(function() { Sys.setenv(RENV_LOG_LEVEL = "error") options(renv.cache.linkable = TRUE) setwd(!!file.path(root, "results.noindex")) result <- install(!!revdep, type = "source", dependencies = "all") system2(!!R(), c("CMD", "check", !!path)) }) } renv_revdeps_status <- function(packages, root) { develop <- map_chr(packages, function(package) { checkdir <- sprintf("revdeps/develop/results.noindex/%s.Rcheck", package) installfile <- file.path(checkdir, "00install.out") if (!file.exists(installfile)) return(list(failed = TRUE)) checkfile <- file.path(checkdir, "00check.log") if (!file.exists(checkfile)) return(list(failed = TRUE)) contents <- readLines(checkfile) tail(contents, n = 1L) }) } renv/R/roxygen.R0000644000176200001440000001016214731330073013244 0ustar liggesusers #' @param project The project directory. If `NULL`, then the active project will #' be used. If no project is currently active, then the current working #' directory is used instead. #' #' @param type The type of package to install ("source" or "binary"). Defaults #' to the value of `getOption("pkgType")`. #' #' @param lockfile Path to a lockfile. When `NULL` (the default), the #' `renv.lock` located in the root of the current project will be used. #' #' @param library The \R library to be used. When `NULL`, the active project #' library will be used instead. #' #' @param prompt Boolean; prompt the user before taking any action? For backwards #' compatibility, `confirm` is accepted as an alias for `prompt`. #' #' @param ... Unused arguments, reserved for future expansion. If any arguments #' are matched to `...`, renv will signal an error. #' #' @param clean Boolean; remove packages not recorded in the lockfile from #' the target library? Use `clean = TRUE` if you'd like the library state #' to exactly reflect the lockfile contents after `restore()`. #' #' @param rebuild Force packages to be rebuilt, thereby bypassing any installed #' versions of the package available in the cache? This can either be a #' boolean (indicating that all installed packages should be rebuilt), or a #' vector of package names indicating which packages should be rebuilt. #' #' @param repos The repositories to use when restoring packages installed #' from CRAN or a CRAN-like repository. By default, the repositories recorded #' in the lockfile will be used, ensuring that (e.g.) CRAN packages are #' re-installed from the same CRAN mirror. #' #' Use `repos = getOption("repos")` to override with the repositories set #' in the current session, or see the `repos.override` option in [config] for #' an alternate way override. #' #' @param profile The profile to be activated. See #' `vignette("profiles", package = "renv")` for more information. #' When `NULL` (the default), the profile is not changed. Use #' `profile = "default"` to revert to the default `renv` profile. #' #' @param dependencies A vector of DESCRIPTION field names that should be used #' for package dependency resolution. When `NULL` (the default), the value #' of `renv::settings$package.dependency.fields` is used. The aliases #' "strong", "most", and "all" are also supported. #' See [tools::package_dependencies()] for more details. #' #' @param packages Either `NULL` (the default) to install all packages required #' by the project, or a character vector of packages to install. renv #' supports a subset of the remotes syntax used for package installation, #' e.g: #' #' * `pkg`: install latest version of `pkg` from CRAN. #' * `pkg@version`: install specified version of `pkg` from CRAN. #' * `username/repo`: install package from GitHub #' * `bioc::pkg`: install `pkg` from Bioconductor. #' #' See and the examples #' below for more details. #' #' renv deviates from the remotes spec in one important way: subdirectories #' are separated from the main repository specification with a `:`, not `/`. #' So to install from the `subdir` subdirectory of GitHub package #' `username/repo` you'd use `"username/repo:subdir`. #' #' @return The project directory, invisibly. Note that this function is normally #' called for its side effects. #' #' @name renv-params NULL renv_roxygen_config_section <- function() { # read config config <- yaml::read_yaml("inst/config.yml") # generate items items <- map_chr(config, function(entry) { # extract fields name <- entry$name type <- entry$type default <- entry$default description <- entry$description # deparse default value default <- case( identical(default, list()) ~ "NULL", TRUE ~ deparse(default) ) # generate table row fmt <- "\\subsection{renv.config.%s}{%s Defaults to \\code{%s}.}" sprintf(fmt, name, description, default) }) c( "@section Configuration:", "", "The following renv configuration options are available:", "", items, "" ) } renv/R/run.R0000644000176200001440000000674414731330073012370 0ustar liggesusers #' Run a script #' #' Run an \R script, in the context of a project using renv. The script will #' be run within an \R sub-process. #' #' @inherit renv-params #' #' @param script The path to an \R script. #' #' @param job Run the requested script as an RStudio job? Requires a recent #' version of both RStudio and the rstudioapi packages. When `NULL`, the #' script will be run as a job if possible, and as a regular \R process #' launched by [system2()] if not. #' #' @param name The name to associate with the job, for scripts run as a job. #' #' @param args description A character vector of command line arguments to be #' passed to the launched job. These parameters can be accessed via #' `commandArgs(trailingOnly = FALSE)`. #' #' @param project The path to the renv project. This project will be loaded #' before the requested script is executed. When `NULL` (the default), renv #' will automatically determine the project root for the associated script #' if possible. #' #' @export run <- function(script, ..., job = NULL, name = NULL, args = NULL, project = NULL) { renv_scope_error_handler() renv_dots_check(...) script <- renv_path_normalize(script, mustWork = TRUE) # find the project directory project <- project %||% renv_file_find(script, function(path) { paths <- file.path(path, c("renv", "renv.lock")) if (any(file.exists(paths))) return(path) }) if (is.null(project)) { fmt <- "could not determine project root for script '%s'" stopf(fmt, renv_path_aliased(script)) } # ensure that it has an activate script activate <- renv_paths_activate(project = project) if (!file.exists(activate)) { fmt <- "project '%s' does not have an renv activate script" stopf(fmt, renv_path_aliased(project)) } # run as a job when possible in RStudio jobbable <- !identical(job, FALSE) && renv_rstudio_available() && renv_package_installed("rstudioapi") && renv_package_version("rstudioapi") >= "0.10" && rstudioapi::verifyAvailable("1.2.1335") if (identical(job, TRUE) && identical(jobbable, FALSE)) stopf("cannot run script as job: required versions of RStudio + rstudioapi not available") if (jobbable) renv_run_job(script = script, name = name, args = args, project = project) else renv_run_impl(script = script, name = name, args = args, project = project) } renv_run_job <- function(script, name, args, project) { activate <- renv_paths_activate(project = project) exprs <- expr({ # insert a shim for commandArg local({ # unlock binding temporarily base <- .BaseNamespaceEnv base$unlockBinding("commandArgs", base) on.exit(base$lockBinding("commandArgs", base), add = TRUE) # insert our shim cargs <- commandArgs(trailingOnly = FALSE) base$commandArgs <- function(trailingOnly = FALSE) { result <- !!args if (trailingOnly) result else union(cargs, result) } }) # run the script source(!!activate) source(!!script) }) code <- deparse(exprs) jobscript <- tempfile("renv-job-", fileext = ".R") writeLines(code, con = jobscript) rstudioapi::jobRunScript( path = jobscript, workingDir = project, name = name ) } renv_run_impl <- function(script, name, args, project) { renv_scope_wd(project) system2(R(), c( "-s", "-f", renv_shell_path(script), if (length(args)) c("--args", args) ), wait = FALSE) } renv/R/options.R0000644000176200001440000000145014731330073013244 0ustar liggesusers renv_options_set <- function(key, value) { data <- list(value) names(data) <- key do.call(base::options, data) } renv_options_resolve <- function(value, arguments) { if (is.function(value)) return(do.call(value, arguments)) value } renv_options_override <- function(scope, key, default = NULL, extra = NULL) { # first, check for scoped option value <- getOption(paste(scope, key, sep = ".")) if (!is.null(value)) return(renv_options_resolve(value, list(extra))) # next, check for unscoped option value <- getOption(scope) if (key %in% names(value)) return(renv_options_resolve(value[[key]], list(extra))) # check for functional value if (is.function(value)) return(renv_options_resolve(value, list(key, extra))) # nothing found; use default default } renv/R/scaffold.R0000644000176200001440000000425514731330073013340 0ustar liggesusers#' Generate project infrastructure #' #' @description #' Create the renv project infrastructure. This will: #' #' - Create a project library, `renv/library`. #' #' - Install renv into the project library. #' #' - Update the project `.Rprofile` to call `source("renv/activate.R")` so #' that renv is automatically loaded for new \R sessions launched in #' this project. #' #' - Create `renv/.gitignore`, which tells git to ignore the project library. #' #' - Create `.Rbuildignore`, if the project is also a package. This tells #' `R CMD build` to ignore the renv infrastructure, #' #' - Write a (bare) [lockfile], `renv.lock`. #' #' @inheritParams renv-params #' #' @param version The version of renv to associate with this project. By #' default, the version of renv currently installed is used. #' #' @param repos The \R repositories to associate with this project. #' #' @param settings A list of renv settings, to be applied to the project #' after creation. These should map setting names to the desired values. #' See [settings] for more details. #' #' @examples #' #' \dontrun{ #' # create scaffolding with 'devtools' ignored #' renv::scaffold(settings = list(ignored.packages = "devtools")) #' } #' #' @export scaffold <- function(project = NULL, version = NULL, repos = getOption("repos"), settings = NULL) { renv_scope_error_handler() renv_scope_options(repos = repos) project <- renv_project_resolve(project) renv_project_lock(project = project) # install renv into project library renv_imbue_impl(project, version) # write out project infrastructure renv_infrastructure_write(project, version) # update project settings if (is.list(settings)) renv_settings_persist(project, settings) # generate a lockfile lockfile <- renv_lockfile_create( project = project, libpaths = renv_paths_library(project = project), type = "implicit" ) renv_lockfile_write(lockfile, file = renv_lockfile_path(project)) # notify user fmt <- "- renv infrastructure has been generated for project %s." writef(fmt, renv_path_pretty(project)) # return project invisibly invisible(project) } renv/R/release.R0000644000176200001440000000063414731330073013174 0ustar liggesusers renv_release_preflight <- function() { ok <- all( renv_release_preflight_urlcheck() ) if (!ok) stop("one or more pre-flight release checks failed") ok } renv_release_preflight_urlcheck <- function() { # check for bad URLs urlchecker <- renv_namespace_load("urlchecker") result <- urlchecker$url_check() # report to user print(result) # return success nrow(result) == 0L } renv/R/abort.R0000644000176200001440000000250414731330072012660 0ustar liggesusers abort <- function(message, ..., body = NULL, class = NULL) { # create condition object cnd <- if (is.character(message)) { structure(class = c(class, "error", "condition"), list( message = paste(c(message, body), collapse = "\n"), meta = list(message = message, body = body), ... )) } else if (inherits(message, "condition")) { message } else { stop("internal error: abort called with unexpected message") } # if we were called with a custom condition object not having our meta, # just throw it as-is if (is.null(cnd$meta)) stop(cnd) # signal the condition, giving calling handlers a chance to run first signalCondition(cnd) # if we got here, then there wasn't any tryCatch() handler on the stack. # handle printing of the error ourselves, and then stop with fallback. all <- c( cnd$meta$body, if (length(cnd$meta$body)) "", paste("Error:", paste(cnd$meta$message, collapse = "\n")) ) # write error message to stderr, as errors might normally do writeLines(all, con = stderr()) # create the fallback, but 'dodge' the existing error handlers fallback <- cnd fallback$message <- "" class(fallback) <- "condition" # disable error printing for the empty error renv_scope_options(show.error.messages = FALSE) # now throw the error stop(fallback) } renv/R/utils-connections.R0000644000176200001440000000015614731330073015233 0ustar liggesusers textfile <- function(description, open = "wt") { file(description, open = open, encoding = "native.enc") } renv/R/ansify.R0000644000176200001440000000245514750474604013062 0ustar liggesusers ansify <- function(text) { if (renv_ansify_enabled()) renv_ansify_enhanced(text) else renv_ansify_default(text) } renv_ansify_enabled <- function() { override <- Sys.getenv("RENV_ANSIFY_ENABLED", unset = NA) if (!is.na(override)) return(as.logical(override)) pane <- Sys.getenv("RSTUDIO_CHILD_PROCESS_PANE", unset = NA) if (identical(pane, "build")) return(FALSE) testthat <- Sys.getenv("TESTTHAT", unset = "false") if (tolower(testthat) %in% "true") return(FALSE) iderun <- Sys.getenv("R_CLI_HAS_HYPERLINK_IDE_RUN", unset = "false") if (tolower(iderun) %in% "false") return(FALSE) TRUE } renv_ansify_default <- function(text) { text } renv_ansify_enhanced <- function(text) { # R help links pattern <- "`\\?(renv::(?:[^`])+)`" replacement <- "`\033]8;;x-r-help:\\1\a?\\1\033]8;;\a`" text <- gsub(pattern, replacement, text, perl = TRUE) # runnable code pattern <- "`(renv::(?:[^`])+)`" replacement <- "`\033]8;;x-r-run:\\1\a\\1\033]8;;\a`" text <- gsub(pattern, replacement, text, perl = TRUE) # return ansified text text } renv_ansify_init <- function() { envir <- renv_envir_self() if (renv_ansify_enabled()) assign("ansify", renv_ansify_enhanced, envir = envir) else assign("ansify", renv_ansify_default, envir = envir) } renv/R/path.R0000644000176200001440000001133114731330073012504 0ustar liggesusers the$alpha <- c(letters, LETTERS) renv_path_absolute <- function(path) { substr(path, 1L, 1L) %in% c("~", "/", "\\") || ( substr(path, 1L, 1L) %in% the$alpha && substr(path, 2L, 3L) %in% c(":/", ":\\") ) } renv_path_aliased <- function(path) { home <- Sys.getenv("HOME", unset = Sys.getenv("R_USER")) if (!nzchar(home)) return(path) home <- gsub("\\", "/", home, fixed = TRUE) path <- gsub("\\", "/", path, fixed = TRUE) match <- regexpr(home, path, fixed = TRUE, useBytes = TRUE) path[match == 1L] <- file.path("~", substring(path[match == 1L], nchar(home) + 2L)) path } renv_path_within <- function(path, parent) { path <- renv_path_canonicalize(path) prefix <- paste(renv_path_canonicalize(parent), "/", sep = "") path == parent | substring(path, 1L, nchar(prefix)) == prefix } renv_path_normalize <- function(path, winslash = "/", mustWork = FALSE) { if (renv_platform_unix()) renv_path_normalize_unix(path, winslash, mustWork) else renv_path_normalize_win32(path, winslash, mustWork) } renv_path_normalize_unix <- function(path, winslash = "/", mustWork = FALSE) { # force paths to be absolute -- this ensures that path prefixes # are prepended even if the path does not exist bad <- !map_lgl(path, renv_path_absolute) if (any(bad)) { prefix <- normalizePath(".", winslash = winslash) path[bad] <- paste(prefix, path[bad], sep = winslash) } # normalize the expanded paths normalizePath(path, winslash, mustWork) } # NOTE: in versions of R < 4.0.0, normalizePath() does not normalize path # casing; e.g. normalizePath("~/MyPaTh") will not normalize to "~/MyPath" # (assuming that is the "true" underlying casing on the filesystem) # # we work around this by round-tripping between the short name and # the long name, as Windows then has no choice but to figure out # the correct casing for us # # this isn't 100% reliable (not all paths have a short-path equivalent) # but seems to be good enough in practice ... # # except that, if the path contains characters that cannot be represented in the # current encoding, then attempting to normalize the short version of that path # will fail -- so if the path is already UTF-8, then we need to avoid # round-tripping through the short path. # # furthermore, it appears that shortPathName() can mis-encode its result for # strings marked with latin1 encoding? # # https://github.com/rstudio/renv/issues/629 renv_path_normalize_win32 <- function(path, winslash = "/", mustWork = FALSE) { # see the NOTE above, this workaround is only necessary for R < 4.0.0, # and it complicates things unnecessarily if (getRversion() >= "4.0.0") return(renv_path_normalize_unix(path, winslash, mustWork)) # get encoding for this set of paths enc <- Encoding(path) # perform separate operations for each utf8 <- enc == "UTF-8" latin1 <- enc == "latin1" unknown <- enc == "unknown" # normalize based on their encoding path[utf8] <- normalizePath(path[utf8], winslash, mustWork) path[latin1] <- normalizePath(path[latin1], winslash, mustWork) path[unknown] <- renv_path_normalize_win32_impl(path[unknown], winslash, mustWork) # return resulting path path } renv_path_normalize_win32_impl <- function(path, winslash = "/", mustWork = FALSE) { # get short path expanded <- path.expand(path) short <- utils::shortPathName(expanded) # if a UTF-8 string is passed to utils::shortPathName(), it seems that # the string might be latin1-encoded, even though it's marked as UTF-8? if (!identical(R.version$crt, "ucrt")) { utf8 <- Encoding(short) == "UTF-8" Encoding(short[utf8]) <- "latin1" } # normalize normalizePath(short, winslash, mustWork) } # TODO: this is a lie; for existing paths symlinks will be resolved. # don't use this for paths that need to be uniquely resolved! renv_path_canonicalize <- function(path) { parent <- dirname(path) root <- renv_path_normalize(parent) trimmed <- sub("/+$", "", root) file.path(trimmed, basename(path)) } renv_path_same <- function(lhs, rhs) { renv_path_canonicalize(lhs) == renv_path_canonicalize(rhs) } # get the nth path component from the end of the path renv_path_component <- function(path, index = 1) { splat <- strsplit(path, "[/\\]+") map_chr(splat, function(parts) parts[length(parts) - index + 1]) } renv_path_pretty <- function(path) { renv_json_quote(renv_path_aliased(path)) } renv_path_relative <- function(path, root) { within <- startsWith(path, root) path[within] <- substring(path[within], nchar(root) + 2L) path } renv/R/libpaths.R0000644000176200001440000001151014731330073013355 0ustar liggesusers the$default_libpaths <- new.env(parent = emptyenv()) # NOTE: if sandboxing is used then these symbols will be clobbered; # save them so we can properly restore them later if so required renv_libpaths_init <- function() { assign(".libPaths()", .libPaths(), envir = the$default_libpaths) assign(".Library", .Library, envir = the$default_libpaths) assign(".Library.site", .Library.site, envir = the$default_libpaths) } renv_libpaths_active <- function() { .libPaths()[[1L]] } renv_libpaths_all <- function() { .libPaths() } renv_libpaths_system <- function() { get(".Library", envir = the$default_libpaths) } renv_libpaths_site <- function() { get(".Library.site", envir = the$default_libpaths) } renv_libpaths_external <- function(project) { projlib <- settings$external.libraries(project = project) conflib <- config$external.libraries(project) .expand_R_libs_env_var(c(projlib, conflib)) } # on Windows, attempting to use a library path containing # characters considered special by cmd.exe will fail. # to guard against this, we try to create a junction point # from the temporary directory to the target library path # # https://github.com/rstudio/renv/issues/334 renv_libpaths_safe <- function(libpaths) { if (renv_libpaths_safe_check(libpaths)) return(libpaths) map_chr(libpaths, renv_libpaths_safe_impl) } renv_libpaths_safe_check <- function(libpaths) { # if any of the paths have single quotes, # then we need to use a safe path # https://bugs.r-project.org/bugzilla/show_bug.cgi?id=17973 if (any(grepl("'", libpaths, fixed = TRUE))) return(FALSE) # on Windows, we need to use safe library paths for R < 4.0.0 # https://bugs.r-project.org/bugzilla/show_bug.cgi?id=17709 if (renv_platform_windows() && getRversion() < "4.0.0") return(FALSE) # otherwise, we're okay return(TRUE) } renv_libpaths_safe_impl <- function(libpath) { # check for an unsafe library path unsafe <- Encoding(libpath) == "UTF-8" || grepl("[&<>^|'\"]", libpath) # if the path appears safe, use it as-is if (!unsafe) return(libpath) # try to form a safe library path methods <- c( renv_libpaths_safe_tempdir, renv_libpaths_safe_userlib ) for (method in methods) { safelib <- catchall(method(libpath)) if (is.character(safelib)) return(safelib) } # could not form a safe library path; # just use the existing library path as-is libpath } renv_libpaths_safe_tempdir <- function(libpath) { safelib <- tempfile("renv-safelib-") if (renv_platform_windows()) renv_file_junction(libpath, safelib) else file.symlink(libpath, safelib) safelib } renv_libpaths_safe_userlib <- function(libpath) { # form path into user library userlib <- renv_libpaths_user()[[1]] base <- file.path(userlib, ".renv-links") ensure_directory(base) # create name for actual junction point name <- renv_hash_text(libpath) safelib <- file.path(base, name) # if the junction already exists, use it if (renv_file_same(libpath, safelib)) return(safelib) # otherwise, try to create it. note that junction # points can be removed with a non-recursive unlink unlink(safelib) if (renv_platform_windows()) renv_file_junction(libpath, safelib) else file.symlink(libpath, safelib) safelib } renv_libpaths_set <- function(libpaths) { oldlibpaths <- .libPaths() safepaths <- renv_libpaths_safe(libpaths) .libPaths(safepaths) oldlibpaths } renv_libpaths_default <- function() { the$default_libpaths$`.libPaths()` } # NOTE: may return more than one library path! renv_libpaths_user <- function() { # if renv is active, the user library will be saved envvars <- c("RENV_DEFAULT_R_LIBS_USER", "R_LIBS_USER") for (envvar in envvars) { value <- Sys.getenv(envvar, unset = NA) if (is.na(value) || value %in% c("", "", "NULL")) next parts <- strsplit(value, .Platform$path.sep, fixed = TRUE)[[1L]] return(parts) } # otherwise, default to active library # (shouldn't happen but best be safe) renv_libpaths_active() } renv_init_libpaths <- function(project) { projlib <- renv_paths_library(project = project) extlib <- renv_libpaths_external(project = project) userlib <- if (config$user.library()) renv_libpaths_user() libpaths <- c(projlib, extlib, userlib) lapply(libpaths, ensure_directory) libpaths } renv_libpaths_restore <- function() { libpaths <- get(".libPaths()", envir = the$default_libpaths) renv_libpaths_set(libpaths) } # We need to ensure the system library is included, for cases where users have # provided an explicit 'library' argument in calls to functions like # 'renv::restore(library = <...>)') # # https://github.com/rstudio/renv/issues/1544 renv_libpaths_resolve <- function(library = NULL) { if (is.null(library)) return(renv_libpaths_all()) unique(c(library, .Library)) } renv/R/sdkroot.R0000644000176200001440000000132114731330073013233 0ustar liggesusers renv_sdkroot_init <- function() { if (!renv_platform_macos()) return() enabled <- Sys.getenv("RENV_SDKROOT_ENABLED", unset = "TRUE") if (!truthy(enabled, default = TRUE)) return() sdkroot <- Sys.getenv("SDKROOT", unset = NA) if (!is.na(sdkroot)) return() sdk <- "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk" if (!file.exists(sdk)) return() makeconf <- file.path(R.home("etc"), "Makeconf") if (!file.exists(makeconf)) return() contents <- readLines(makeconf) cxx <- grep("^CXX\\s*=", contents, value = TRUE, perl = TRUE) if (length(cxx) == 0L) return() if (!grepl("(?:/usr/local|/opt/homebrew)/opt/llvm", cxx)) return() Sys.setenv(SDKROOT = sdk) } renv/R/call.R0000644000176200001440000000335314731330072012467 0ustar liggesusers # given a call of the form e.g. 'pkg::foo()' or 'foo()', # check that method 'foo()' is truly being called and # strip off the 'pkg::' part for easier parsing. # # this gets called very often when parsing dependencies, # so optimizations are welcome here renv_call_expect <- function(node, package, methods) { result <- NULL # check for call of the form 'pkg::foo(a, b, c)' if (is.call(call <- node[[1L]])) if (is.symbol(symbol <- call[[1L]])) if (symbol == "::" || symbol == ":::") if (call[[2L]] == package) node[[1L]] <- call[[3L]] # check for any method match if (is.symbol(symbol <- node[[1L]])) if (any(symbol == methods)) result <- node result } renv_call_normalize <- function(node) { # check for magrittr pipe -- if this part of the expression is # being piped into, then we need to munge the call ispipe <- renv_call_matches(node, names = c("%>%", "%T>%", "%<>%")) if (!ispipe) return(node) # get lhs and rhs of piped expression lhs <- node[[2L]] rhs <- node[[3L]] # handle rhs symbols if (is.symbol(rhs)) rhs <- call(as.character(rhs)) # check for usage of '.' # if it exists, replace each with lhs hasdot <- FALSE dot <- as.symbol(".") for (i in seq_along(rhs)) { if (identical(dot, rhs[[i]])) { hasdot <- TRUE rhs[[i]] <- lhs } } if (hasdot) return(rhs) # otherwise, mutate rhs call with lhs passed as first argument args <- as.list(rhs) as.call(c(args[[1L]], lhs, args[-1L])) } renv_call_matches <- function(call, names, nargs = NULL) { ok <- FALSE if (is.call(call)) if (is.symbol(sym <- call[[1L]])) if (any(names == sym)) ok <- is.null(nargs) || length(call) == nargs + 1L ok } renv/R/patch.R0000644000176200001440000001245314731330073012655 0ustar liggesusers renv_patch_init <- function() { renv_patch_rprofile() renv_patch_tar() renv_patch_repos() renv_patch_golem() renv_patch_methods_table() } renv_patch_rprofile <- function() { # resolve path to user profile path <- Sys.getenv("R_PROFILE_USER", unset = "~/.Rprofile") info <- renv_file_info(path) if (!identical(info$isdir, FALSE)) return(FALSE) # if the .Rprofile is empty, do nothing if (info$size == 0) return(TRUE) # check for trailing newline data <- readBin(path, raw(), n = info$size) if (empty(data)) return(TRUE) last <- data[length(data)] endings <- as.raw(c(0x0a, 0x0d)) if (last %in% endings) return(TRUE) # if it's missing, inform the user warningf("%s is missing a trailing newline", renv_path_pretty(path)) FALSE } renv_patch_tar <- function() { # read value of TAR tar <- Sys.getenv("TAR", unset = "") # on Windows, if TAR is unset, then force the usage # of R's internal tar implementation. this is done to # avoid issues where e.g. versions of tar which do not # understand Windows paths are on the PATH # # https://github.com/rstudio/renv/issues/521 if (renv_platform_windows() && !nzchar(tar)) { Sys.setenv(TAR = "internal") return(TRUE) } # otherwise, allow empty / internal tars if (tar %in% c("", "internal")) return(TRUE) # the user (or R itself) has set the TAR environment variable # validate that it exists (resolve from PATH) # # note that the user can set TAR to be a full command; e.g. # # TAR = /path/to/tar --force-local # # so we need to handle that case appropriately whitespace <- gregexpr("(?:\\s+|$)", tar, perl = TRUE)[[1L]] for (index in whitespace) { candidate <- substring(tar, 1L, index - 1L) resolved <- Sys.which(candidate) if (nzchar(resolved)) return(TRUE) } # TAR appears to be set but invalid; override it # and warn the user newtar <- Sys.which("tar") if (!nzchar(newtar)) newtar <- "internal" Sys.setenv(TAR = newtar) # report to the user fmt <- "requested TAR '%s' does not exist; using '%s' instead" warningf(fmt, tar, newtar) } renv_patch_golem <- function() { renv_package_hook("golem", renv_patch_golem_impl) } renv_patch_golem_impl <- function(...) { if (packageVersion("golem") != "0.2.1") return() golem <- getNamespace("golem") replacement <- function(file, pattern, replace) { # skip .rds files if (grepl("[.]rds$", file)) return() # skip files containing nul bytes info <- renv_file_info(file) bytes <- readBin(file, "raw", info$size) if (any(bytes == 0L)) return() # otherwise, attempt replacement old <- readLines(file) new <- gsub(pattern, replace, old) writeLines(new, con = file) } environment(replacement) <- golem if ("compiler" %in% loadedNamespaces()) replacement <- compiler::cmpfun(replacement) renv_binding_replace(golem, "replace_word", replacement) } renv_patch_methods_table <- function() { catchall(renv_patch_methods_table_impl()) } renv_patch_methods_table_impl <- function() { # ensure promises in S3 methods table are forced # https://bugs.r-project.org/bugzilla/show_bug.cgi?id=16644 for (envir in list(.BaseNamespaceEnv, renv_namespace_load("utils"))) { # unlock binding if it's locked binding <- ".__S3MethodsTable__." base <- baseenv() if (base$bindingIsLocked(binding, env = envir)) { base$unlockBinding(binding, env = envir) defer(base$lockBinding(binding, envir)) } # force everything defined in the environment table <- envir[[binding]] for (key in ls(envir = table, all.names = TRUE)) table[[key]] <- force(table[[key]]) } } # puts the current version of renv into an on-disk package repository, # so that packages using renv can find this version of renv in tests # this helps renv survive CRAN revdep checks (e.g. jetpack) renv_patch_repos <- function() { # nothing to do in embedded mode if (renv_metadata_embedded()) return() # nothing to do if we're not running tests checking <- checking() if (!checking) return() # nothing to do if we're running our own tests name <- Sys.getenv("_R_CHECK_PACKAGE_NAME_", unset = NA) if (identical(name, "renv")) return() # presumably this will never happen when the dev version of renv is # installed, so we skip to avoid parsing a sha as version sha <- attr(the$metadata$version, "sha") if (!is.null(sha)) return() # nothing to do if this version of 'renv' is already available version <- renv_metadata_version() entry <- catch(renv_available_packages_entry("renv", filter = version, quiet = TRUE)) if (!inherits(entry, "error")) return() # check if we've already set repos if ("RENV" %in% names(getOption("repos"))) return() # use package-local repository path repopath <- system.file("repos", package = "renv", mustWork = FALSE) if (!file.exists(repopath)) return() # update our repos option fmt <- if (renv_platform_windows()) "file:///%s" else "file://%s" repourl <- sprintf(fmt, repopath) # renv needs to be first so the right version is found? repos <- c(RENV = repourl, getOption("repos")) names(repos) <- make.names(names(repos)) options(repos = repos) # make sure these repositories are used in restore too options(renv.config.repos.override = repos) } renv/R/record.R0000644000176200001440000000660214740260564013042 0ustar liggesusers #' Update package records in a lockfile #' #' Use `record()` to record a new entry within an existing renv lockfile. #' #' This function can be useful when you need to change one or more of the #' package records within an renv lockfile -- for example, because a recorded #' package cannot be restored in a particular environment, and you know of a #' suitable alternative. #' #' # Records #' #' Records can be provided either using the **remotes** short-hand syntax, #' or by using an \R list of entries to record within the lockfile. See #' `?lockfiles` for more information on the structure of a package record. #' #' @inheritParams renv-params #' #' @param records A list of named records, mapping package names to a definition #' of their source. See **Records** for more details. #' #' @example examples/examples-record.R #' @export record <- function(records, lockfile = NULL, project = NULL) { renv_scope_error_handler() project <- renv_project_resolve(project) renv_project_lock(project = project) lockfile <- lockfile %||% renv_lockfile_path(project) records <- case( is.character(records) ~ lapply(records, renv_remotes_resolve, latest = TRUE), is.list(records) ~ renv_records_resolve(records, latest = TRUE), ~ stopf("unexpected records format '%s'", typeof(records)) ) names(records) <- enum_chr(records, function(package, record) { if (is.null(package) || is.na(package) || !nzchar(package)) record[["Package"]] else package }) if (is.list(lockfile)) return(renv_lockfile_modify(lockfile, records)) if (!file.exists(lockfile)) { fmt <- "no lockfile exists at path %s" stopf(fmt, renv_path_pretty(lockfile)) } old <- renv_lockfile_read(lockfile) new <- renv_lockfile_modify(old, records) local({ renv_scope_options(renv.verbose = FALSE) renv_lockfile_write(new, lockfile) }) n <- length(records) fmt <- "- Updated %s in %s." writef(fmt, nplural("record", n), renv_path_pretty(lockfile)) renv <- records[["renv"]] if (!is.null(renv) && !is.null(renv[["Version"]])) { renv_infrastructure_write_activate( project = project, version = renv[["Version"]] ) } invisible(lockfile) } renv_record_normalize <- function(record) { # normalize source source <- record$Source %||% "unknown" if (source %in% c("CRAN", "P3M", "PPM", "RSPM")) record$Source <- "Repository" # drop remotes from records with a repository source if (renv_record_cranlike(record)) record <- record[grep("^Remote", names(record), invert = TRUE)] # keep only specific records for comparison remotes <- grep("^Remote", names(record), value = TRUE) keep <- c("Package", "Version", "Source", remotes) record <- record[intersect(names(record), keep)] # return normalized record record } renv_record_tag <- function(record, type, url, name) { attr(record, "url") <- url attr(record, "type") <- type attr(record, "name") <- name record } renv_record_tagged <- function(record) { attrs <- attributes(record) all(c("url", "type") %in% names(attrs)) } # abstracted out in case we want to use a different sigil in the future, # like `_`, ``, or something else renv_record_placeholder <- function() { "*" } renv_record_cranlike <- function(record) { type <- record[["RemoteType"]] is.null(type) || tolower(type) %in% c("cran", "repository", "standard") } renv/R/update.R0000644000176200001440000003107414761163114013043 0ustar liggesusers the$update_errors <- new.env(parent = emptyenv()) renv_update_find_repos <- function(records) { results <- lapply(records, function(record) { catch(renv_update_find_repos_impl(record)) }) failed <- map_lgl(results, inherits, "error") if (any(failed)) renv_update_errors_set("repos", results[failed]) results[!failed] } renv_update_find_repos_impl <- function(record) { # retrieve latest-available package package <- record$Package latest <- catch(renv_available_packages_latest(package)) if (inherits(latest, "error")) return(NULL) # validate our versions if (empty(latest$Version) || empty(record$Version)) return(NULL) # compare the versions; return NULL if the 'latest' version # is older compare <- renv_version_compare(latest$Version, record$Version) if (compare != 1L) return(NULL) latest } renv_update_find_git <- function(records) { renv_parallel_exec(records, renv_update_find_git_impl) } renv_update_find_git_impl <- function(record) { sha <- renv_remotes_resolve_git_sha_ref(record) # if sha is empty: # `git remote-ls origin ref` expects ref to be a reference, not a sha # it is empty if ref isn't a reference on the repo # this may be due to record$RemoteRef actually being a sha # or it may be because record$RemoteRef is not a real ref # but we can't check, so we will try to fetch the ref & see what we get oldsha <- record$RemoteSha %||% "" if (nzchar(oldsha) && identical(sha, oldsha)) return(NULL) current <- record current$RemoteSha <- sha desc <- renv_remotes_resolve_git_description(current) current$Version <- desc$Version current$Package <- desc$Package updated <- renv_version_ge(current$Version, record$Version) if (updated) return(current) } renv_update_find_github <- function(records) { names(records) <- map_chr(records, `[[`, "Package") results <- renv_parallel_exec(records, function(record) { catch(renv_update_find_github_impl(record)) }) failed <- map_lgl(results, inherits, "error") if (any(failed)) renv_update_errors_set("github", results[failed]) results[!failed] } renv_update_find_github_impl <- function(record) { # construct and parse record entry host <- record$RemoteHost %||% config$github.host() user <- record$RemoteUsername repo <- record$RemoteRepo subdir <- record$RemoteSubdir ref <- record$RemoteRef # check for changed sha sha <- renv_remotes_resolve_github_sha_ref(host, user, repo, ref) if (sha == record$RemoteSha) return(NULL) url <- record$RemoteUrl %||% { origin <- fsub("api.github.com", "github.com", renv_retrieve_origin(host)) parts <- c(origin, user, repo) paste(parts, collapse = "/") } # get updated record desc <- renv_remotes_resolve_github_description(url, host, user, repo, subdir, sha) current <- list( Package = desc$Package, Version = desc$Version, Source = "GitHub", RemoteUsername = user, RemoteRepo = repo, RemoteSubdir = subdir, RemoteRef = ref, RemoteSha = sha, RemoteHost = host ) # check that the version has actually updated updated <- current$RemoteSha != record$RemoteSha && numeric_version(current$Version) >= numeric_version(record$Version) if (updated) return(current) } renv_update_find_remote <- function(records, type) { update <- switch(type, "gitlab" = renv_remotes_resolve_gitlab, "bitbucket" = renv_remotes_resolve_bitbucket, stopf("Unsupported type %s", type) ) names(records) <- map_chr(records, `[[`, "Package") results <- renv_parallel_exec(records, function(record) { catch(renv_update_find_remote_impl(record, update)) }) failed <- map_lgl(results, inherits, "error") if (any(failed)) renv_update_errors_set(type, results[failed]) results[!failed] } renv_update_find_remote_impl <- function(record, update) { remote <- list( host = record$RemoteHost, user = record$RemoteUsername, repo = record$RemoteRepo, ref = record$RemoteRef ) current <- update(remote) # check that the version has actually updated updated <- current$RemoteSha != record$RemoteSha && numeric_version(current$Version) >= numeric_version(record$Version) if (updated) return(current) } renv_update_find <- function(records) { sources <- extract_chr(records, "Source") grouped <- split(records, sources) # retrieve updates results <- enumerate(grouped, function(source, records) { case( source == "Bioconductor" ~ renv_update_find_repos(records), source == "Repository" ~ renv_update_find_repos(records), source == "GitHub" ~ renv_update_find_github(records), source == "Git" ~ renv_update_find_git(records), source == "GitLab" ~ renv_update_find_remote(records, "gitlab"), source == "Bitbucket" ~ renv_update_find_remote(records, "bitbucket") ) }) # remove groupings ungrouped <- unlist(results, recursive = FALSE, use.names = FALSE) if (empty(ungrouped)) return(list()) # keep non-null results updates <- Filter(Negate(is.null), ungrouped) if (empty(updates)) return(list()) names(updates) <- extract_chr(updates, "Package") renv_records_sort(updates) } #' Update packages #' #' @description #' Update packages which are currently out-of-date. Currently supports CRAN, #' Bioconductor, other CRAN-like repositories, GitHub, GitLab, Git, and #' BitBucket. #' #' Updates will only be checked from the same source -- for example, #' if a package was installed from GitHub, but a newer version is #' available on CRAN, that updated version will not be seen. #' #' @inherit renv-params #' #' @param packages A character vector of \R packages to update. When `NULL` #' (the default), all packages (apart from any listed in the `ignored.packages` #' project setting) will be updated. #' #' @param check Boolean; check for package updates without actually #' installing available updates? This is useful when you'd like to determine #' what updates are available, without actually installing those updates. #' #' @param exclude A set of packages to explicitly exclude from updating. #' Use `renv::update(exclude = <...>)` to update all packages except for #' a specific set of excluded packages. #' #' @param lock Boolean; update the `renv.lock` lockfile after the successful #' installation of the requested packages? #' #' @return A named list of package records which were installed by renv. #' #' @export #' #' @examples #' \dontrun{ #' #' # update the 'dplyr' package #' renv::update("dplyr") #' #' } update <- function(packages = NULL, ..., exclude = NULL, library = NULL, type = NULL, rebuild = FALSE, check = FALSE, prompt = interactive(), lock = FALSE, project = NULL) { renv_consent_check() renv_scope_error_handler() renv_dots_check(...) project <- renv_project_resolve(project) renv_project_lock(project = project) renv_scope_verbose_if(prompt) # resolve library path libpaths <- renv_libpaths_resolve(library) library <- nth(libpaths, 1L) renv_scope_libpaths(libpaths) # check for explicitly-provided type -- we handle this specially for PPM if (!is.null(type)) { renv_scope_binding(the, "install_pkg_type", type) renv_scope_options(pkgType = type) } # resolve exclusions exclude <- c(exclude, settings$ignored.packages(project = project)) # if users have requested the use of pak, delegate there if (config$pak.enabled() && !recursing()) { packages <- setdiff(packages, exclude) renv_pak_init() return( renv_pak_install( packages = packages, library = libpaths, rebuild = rebuild, type = type, prompt = prompt, project = project ) ) } # get package records renv_scope_binding(the, "snapshot_hash", FALSE) records <- renv_snapshot_libpaths(libpaths = libpaths, project = project) packages <- packages %||% names(records) # apply exclusions packages <- setdiff(packages, exclude) # check if the user has requested update for packages not installed missing <- renv_vector_diff(packages, names(records)) if (!empty(missing)) { if (prompt || renv_verbose()) { bulletin( "The following package(s) are not currently installed:", missing, "The latest available versions of these packages will be installed instead." ) } cancel_if(prompt && !proceed()) } # select records selected <- c( records[renv_vector_intersect(packages, names(records))], named(lapply(missing, renv_available_packages_latest), missing) ) # check for usage of cran, bioc repo <- FALSE bioc <- FALSE for (record in selected) { source <- renv_record_source(record, normalize = TRUE) if (source %in% c("repository")) { repo <- TRUE next } if (source %in% c("bioconductor")) { repo <- bioc <- TRUE next } } # activate bioc repositories if needed if (bioc) renv_scope_bioconductor(project = project) # ensure database of available packages is current if (repo) { for (type in renv_package_pkgtypes()) { available_packages(type = type) } } printf("- Checking for updated packages ... ") # remove records that appear to be from an R package repository, # but are not actually available in the current repositories selected <- filter(selected, function(record) { source <- renv_record_source(record, normalize = TRUE) if (!source %in% c("bioconductor", "cran", "repository")) return(TRUE) # check for available package package <- record$Package entry <- catch(renv_available_packages_latest(package)) !inherits(entry, "error") }) updates <- renv_update_find(selected) writef("Done!") renv_update_errors_emit() if (empty(updates)) { writef("- All packages appear to be up-to-date.") return(invisible(TRUE)) } # perform a diff (for reporting to user) old <- selected[names(updates)] new <- updates diff <- renv_lockfile_diff_packages(old, new) # if we're only checking for updates, just report and exit if (check) { fmt <- case( length(diff) == 1 ~ "- %i package has updates available.", length(diff) != 1 ~ "- %i packages have updates available." ) preamble <- sprintf(fmt, length(diff)) renv_updates_report(preamble, diff, old, new) return(invisible(renv_updates_create(diff, old, new))) } if (prompt || renv_verbose()) { renv_restore_report_actions(diff, old, new) cancel_if(prompt && !proceed()) } # perform the install install( packages = updates, library = libpaths, rebuild = rebuild, prompt = prompt, lock = lock, project = project ) } renv_update_errors_set <- function(key, errors) { assign(key, errors, envir = the$update_errors) } renv_update_errors_clear <- function() { rm( list = ls(envir = the$update_errors, all.names = TRUE), envir = the$update_errors ) } renv_update_errors_emit <- function() { # clear errors when we're done defer(renv_update_errors_clear()) # if we have any errors, start by emitting a single newline all <- ls(envir = the$update_errors, all.names = TRUE) if (!empty(all)) writef() # then emit errors for each class renv_update_errors_emit_repos() renv_update_errors_emit_remote("github", "GitHub") renv_update_errors_emit_remote("gitlab", "GitLab") renv_update_errors_emit_remote("bitbucket", "BitBucket") } renv_update_errors_emit_impl <- function(key, preamble, postamble) { errors <- the$update_errors[[key]] if (empty(errors)) return() messages <- enumerate(errors, function(package, error) { errmsg <- paste(conditionMessage(error), collapse = "; ") sprintf("%s: %s", format(package), errmsg) }) bulletin( preamble = preamble, values = messages, postamble = postamble ) } renv_update_errors_emit_repos <- function() { renv_update_errors_emit_impl( key = "repos", preamble = "One or more errors occurred while finding updates for the following packages:", postamble = "Ensure that these packages are available from your active package repositories." ) } renv_update_errors_emit_remote <- function(key, label) { renv_update_errors_emit_impl( key = key, preamble = sprintf("One or more errors occurred while finding updates for the following %s packages:", label), postamble = sprintf("Ensure that these packages were installed from an accessible %s remote.", label) ) } renv/R/history.R0000644000176200001440000000447114731330073013260 0ustar liggesusers #' View and revert to a historical lockfile #' #' @description #' `history()` uses your version control system to show prior versions of the #' lockfile and `revert()` allows you to restore one of them. #' #' These functions are currently only implemented for projects that use git. #' #' @inherit renv-params #' #' @export #' #' @return `history()` returns a `data.frame` summarizing the commits in which #' `renv.lock` has been changed. `revert()` is usually called for its #' side-effect but also invisibly returns the `commit` used. #' #' @examples #' \dontrun{ #' #' # get history of previous versions of renv.lock in VCS #' db <- renv::history() #' #' # choose an older commit #' commit <- db$commit[5] #' #' # revert to that version of the lockfile #' renv::revert(commit = commit) #' #' } history <- function(project = NULL) { renv_scope_error_handler() project <- renv_project_resolve(project) renv_project_lock(project = project) lockpath <- renv_lockfile_path(project) if (!file.exists(lockpath)) return(data_frame()) renv_git_preflight() renv_scope_wd(project) args <- c("log", "--pretty=format:%H\031%at\031%ct\031%s", renv_shell_path(lockpath)) data <- renv_system_exec("git", args, action = "retrieving git log") parts <- strsplit(data, "\031", fixed = TRUE) tbl <- bind(parts, names = c("commit", "author_date", "committer_date", "subject")) tbl$author_date <- as.POSIXct(as.numeric(tbl$author_date), origin = "1970-01-01") tbl$committer_date <- as.POSIXct(as.numeric(tbl$committer_date), origin = "1970-01-01") tbl } #' @param commit The commit associated with a prior version of the lockfile. #' @param ... Optional arguments; currently unused. #' @export #' @rdname history revert <- function(commit = "HEAD", ..., project = NULL) { renv_scope_error_handler() renv_dots_check(...) project <- renv_project_resolve(project) renv_project_lock(project = project) renv_git_preflight() renv_scope_wd(project) lockpath <- renv_lockfile_path(project = project) system2("git", c("checkout", commit, "--", renv_shell_path(lockpath))) system2("git", c("reset", "HEAD", renv_shell_path(lockpath)), stdout = FALSE, stderr = FALSE) system2("git", c("diff", "--", renv_shell_path(lockpath))) writef("- renv.lock from commit %s has been checked out.", commit) invisible(commit) } renv/R/tempdir.R0000644000176200001440000000162714731330073013223 0ustar liggesusers renv_tempdir_init <- function() { # only check on linux if (!renv_platform_linux()) return() # allow disable via envvar if needed check <- Sys.getenv("RENV_TEMPDIR_NOEXEC_CHECK", unset = "TRUE") if (not(check)) return() # check that scripts within the R temporary directory can be executed script <- tempfile("renv-script-", fileext = ".sh") writeLines("#!/usr/bin/env sh", con = script) Sys.chmod(script, mode = "0755") on.exit(unlink(script), add = TRUE) status <- system(script, ignore.stdout = TRUE, ignore.stderr = TRUE) if (identical(status, 0L)) return() fmt <- heredoc(" The R temporary directory appears to be within a folder mounted as 'noexec'. Installation of R packages from sources may fail. See the section **Note** within `?INSTALL` for more details. tempdir(): %s ") caution(fmt, tempdir(), con = stderr()) } renv/R/base64.R0000644000176200001440000000744514731330072012646 0ustar liggesusers the$base64_table <- as.integer(charToRaw("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=")) renv_base64_encode_main <- function(input) { ni <- as.integer(length(input)) if (ni < 3L) return(integer()) no <- ni %/% 3L * 4L output <- integer(no) i0 <- seq.int(1L, ni - 2L, by = 3L) i1 <- seq.int(2L, ni - 1L, by = 3L) i2 <- seq.int(3L, ni - 0L, by = 3L) o0 <- seq.int(1L, no - 3L, by = 4L) o1 <- seq.int(2L, no - 2L, by = 4L) o2 <- seq.int(3L, no - 1L, by = 4L) o3 <- seq.int(4L, no - 0L, by = 4L) output[o0] <- the$base64_table[1L + bitwShiftR(input[i0], 2L)] output[o1] <- the$base64_table[1L + bitwOr( bitwShiftL(bitwAnd(input[i0], 0x03L), 4L), bitwShiftR(bitwAnd(input[i1], 0xF0L), 4L) )] output[o2] <- the$base64_table[1L + bitwOr( bitwShiftL(bitwAnd(input[i1], 0x0FL), 2L), bitwShiftR(bitwAnd(input[i2], 0xC0L), 6L) )] output[o3] <- the$base64_table[1L + bitwAnd(input[i2], 0x3FL)] output } renv_base64_encode_rest <- function(input) { ni <- as.integer(length(input)) remaining <- ni %% 3L if (remaining == 0L) return(integer()) output <- rep.int(61L, 4L) i <- ni - remaining + 1 output[1L] <- the$base64_table[1L + bitwShiftR(input[i + 0L], 2L)] if (remaining == 1L) { output[2L] <- the$base64_table[1L + bitwShiftL(bitwAnd(input[i + 0L], 0x03L), 4L)] } else if (remaining == 2L) { output[2L] <- the$base64_table[1L + bitwOr( bitwShiftL(bitwAnd(input[i + 0L], 0x03L), 4L), bitwShiftR(bitwAnd(input[i + 1L], 0xF0L), 4L) )] output[3L] <- the$base64_table[1L + bitwShiftL(bitwAnd(input[i + 1L], 0x0FL), 2L)] } output } renv_base64_encode <- function(text) { # convert to raw vector input <- case( is.character(text) ~ as.integer(charToRaw(text)), is.raw(text) ~ as.integer(text), ~ stopf("unexpected input type '%s'", typeof(text)) ) encoded <- c( renv_base64_encode_main(input), renv_base64_encode_rest(input) ) rawToChar(as.raw(encoded)) } the$base64_decode_table <- NULL renv_base64_decode_table <- function() { the$base64_decode_table <- the$base64_decode_table %||% { table <- integer(255) text <- "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=" table[utf8ToInt(text)] <- seq_len(nchar(text)) - 1L table } } renv_base64_decode_main <- function(input) { ni <- length(input) no <- (ni * 3L) %/% 4L output <- integer(no) i0 <- seq(from = 1L, to = ni - 3L, by = 4L) i1 <- seq(from = 2L, to = ni - 2L, by = 4L) i2 <- seq(from = 3L, to = ni - 1L, by = 4L) i3 <- seq(from = 4L, to = ni - 0L, by = 4L) o0 <- seq.int(1L, no - 2L, by = 3L) o1 <- seq.int(2L, no - 1L, by = 3L) o2 <- seq.int(3L, no - 0L, by = 3L) t <- renv_base64_decode_table() output[o0] <- bitwOr( bitwAnd(bitwShiftL(t[input[i0]], 2L), 255L), bitwAnd(bitwShiftR(t[input[i1]], 4L), 255L) ) output[o1] <- bitwOr( bitwAnd(bitwShiftL(t[input[i1]], 4L), 255L), bitwAnd(bitwShiftR(t[input[i2]], 2L), 255L) ) output[o2] <- bitwOr( bitwAnd(bitwShiftL(t[input[i2]], 6L), 255L), bitwAnd(bitwShiftR(t[input[i3]], 0L), 255L) ) output } renv_base64_decode <- function(encoded) { # remove newlines if (c(regexpr("\n", encoded, fixed = TRUE)) != -1L) encoded <- gsub("\n", "", encoded, fixed = TRUE) # convert to raw vector input <- case( is.character(encoded) ~ as.integer(charToRaw(encoded)), is.raw(encoded) ~ as.integer(encoded), ~ stopf("unexpected input type '%s'", typeof(encoded)) ) # decode vector output <- renv_base64_decode_main(input) # trim off padded bits n <- length(input) if (input[n - 1L] == 61L) output <- head(output, n = -2L) else if (input[n] == 61L) output <- head(output, n = -1L) # convert back to string rawToChar(as.raw(output)) } renv/R/purge.R0000644000176200001440000000644614761163114012710 0ustar liggesusers #' Purge packages from the cache #' #' Purge packages from the cache. This can be useful if a package which had #' previously been installed in the cache has become corrupted or unusable, #' and needs to be reinstalled. #' #' `purge()` is an inherently destructive option. It removes packages from the #' cache, and so any project which had symlinked that package into its own #' project library would find that package now unavailable. These projects would #' hence need to reinstall any purged packages. Take heed of this in case you're #' looking to purge the cache of a package which is difficult to install, or #' if the original sources for that package are no longer available! #' #' @inherit renv-params #' #' @param package A single package to be removed from the cache. #' @param version The package version to be removed. When `NULL`, all versions #' of the requested package will be removed. #' @param hash The specific hashes to be removed. When `NULL`, all hashes #' associated with a particular package's version will be removed. #' #' @return The set of packages removed from the renv global cache, #' as a character vector of file paths. #' #' @export #' #' @examples #' \dontrun{ #' #' # remove all versions of 'digest' from the cache #' renv::purge("digest") #' #' # remove only a particular version of 'digest' from the cache #' renv::purge("digest", version = "0.6.19") #' #' } purge <- function(package, ..., version = NULL, hash = NULL, prompt = interactive()) { renv_scope_error_handler() renv_dots_check(...) renv_scope_verbose_if(prompt) invisible(renv_purge_impl(package, version, hash, prompt)) } renv_purge_impl <- function(package, version = NULL, hash = NULL, prompt = interactive()) { if (length(package) != 1) stop("argument 'package' is not of length one", call. = FALSE) bail <- function() { writef("- The requested package is not installed in the cache -- nothing to do.") character() } # get root cache path entry for package paths <- renv_paths_cache(package) if (!any(file.exists(paths))) return(bail()) # construct versioned path paths <- if (is.null(version)) list.files(paths, full.names = TRUE) else file.path(paths, version) if (!any(file.exists(paths))) return(bail()) # construct hashed path paths <- if (is.null(hash)) list.files(paths, full.names = TRUE) else file.path(paths, hash) if (all(!file.exists(paths))) return(bail()) # now add package name paths <- file.path(paths, renv_path_component(paths, 3)) # check that these entries exist missing <- !file.exists(paths) if (any(missing)) { bulletin( "The following entries were not found in the cache:", paths[missing], "They will be ignored." ) paths <- paths[!missing] } # nocov start if (prompt || renv_verbose()) { bulletin( "The following packages will be purged from the cache:", renv_cache_format_path(paths) ) cancel_if(prompt && !proceed()) } # nocov end unlink(paths, recursive = TRUE) renv_cache_clean_empty() n <- length(paths) writef("- Removed %s from the cache.", nplural("package", n)) invisible(paths) } renv/R/vendor.R0000644000176200001440000001221214731330073013044 0ustar liggesusers #' Vendor renv in an R package #' #' @description #' Calling `renv:::vendor()` will: #' #' - Compile a vendored copy of renv to `inst/vendor/renv.R`, #' - Generate an renv auto-loader at `R/renv.R`. #' #' Using this, projects can take a dependency on renv, and use renv #' internals, in a CRAN-compliant way. After vendoring renv, you can #' use renv APIs in your package via the embedded renv environment; #' for example, you could call the [renv::dependencies()] function with: #' #' ``` #' renv$dependencies() #' ``` #' #' Be aware that renv internals might change in future releases, so if you #' need to rely on renv internal functions, we strongly recommend testing #' your usages of these functions to avoid potential breakage. #' #' @param version The version of renv to vendor. `renv` sources will be pulled #' from GitHub, and so `version` should refer to either a commit hash or a #' branch name. #' #' @param project The project in which renv should be vendored. #' #' @keywords internal #' vendor <- function(version = "main", project = getwd()) { renv_scope_error_handler() # validate project is a package descpath <- file.path(project, "DESCRIPTION") if (!file.exists(descpath)) { fmt <- "%s does not contain a DESCRIPTION file; cannot proceed" stopf(fmt, renv_path_pretty(project)) } # retrieve package sources sources <- renv_vendor_sources(version) # compute package remote spec <- sprintf("rstudio/renv@%s", version) remote <- renv_remotes_resolve(spec) # build script header header <- renv_vendor_header(remote) # create the renv script itself embed <- renv_vendor_create( project = project, sources = sources, header = header ) # create the loader loader <- renv_vendor_loader(project, remote, header) # let the user know what just happened template <- heredoc(" # # A vendored copy of renv was created at: %s # The renv auto-loader was generated at: %s # # Please add `renv$initialize(libname, pkgname)` to your package's # `.onLoad()` to ensure that renv is initialized on package load. # ") writef(template, renv_path_pretty(embed), renv_path_pretty(loader)) invisible(TRUE) } renv_vendor_create <- function(project, sources, header) { # find all the renv R source scripts scripts <- list.files(file.path(sources, "R"), full.names = TRUE) # read into a single file contents <- map_chr(scripts, function(script) { header <- header(basename(script), n = 78L) contents <- readLines(script) parts <- c(header, "", contents, "", "") paste(parts, collapse = "\n") }) # paste into single script bundle <- paste(contents, collapse = "\n") all <- c(header, "", bundle) # write to file target <- file.path(project, "inst/vendor/renv.R") ensure_parent_directory(target) writeLines(all, con = target) # return generated bundle invisible(target) } renv_vendor_loader <- function(project, remote, header) { source <- system.file("resources/vendor/renv.R", package = "renv") template <- readLines(source, warn = FALSE) # replace '..imports..' with the imports we use imports <- renv_vendor_imports() # create metadata for the embedded version version <- renv_metadata_version_create(remote) metadata <- renv_metadata_create(embedded = TRUE, version = version) # format metadata for template insertion lines <- enum_chr(metadata, function(key, value) { sprintf(" %s = %s", key, deparse(value)) }) inner <- paste(lines, collapse = ",\n") replacements <- list( imports = imports, metadata = paste(c("list(", inner, " )"), collapse = "\n") ) contents <- renv_template_replace(template, replacements, format = "..%s..") all <- c("", header, "", contents) target <- file.path(project, "R/renv.R") ensure_parent_directory(target) writeLines(all, con = target) invisible(target) } renv_vendor_imports <- function() { imports <- getNamespaceImports("renv") # collect into sane format packages <- setdiff(unique(names(imports)), c("base", "")) names(packages) <- packages table <- map(packages, function(package) { unlist(imports[names(imports) == package], use.names = FALSE) }) # format nicely entries <- enum_chr(table, function(package, functions) { lines <- sprintf(" \"%s\"", functions) body <- paste(lines, collapse = ",\n") parts <- c(sprintf(" %s = c(", package), body, " )") paste(parts, collapse = "\n") }) paste(c("list(", paste(entries, collapse = ",\n"), " )"), collapse = "\n") } renv_vendor_sources <- function(version) { # retrieve renv tarball <- renv_bootstrap_download_github(version = version) # extract downloaded sources untarred <- tempfile("renv-vendor-") untar(tarball, exdir = untarred) # the package itself will exist as a folder within 'exdir' list.files(untarred, full.names = TRUE)[[1L]] } renv_vendor_header <- function(remote) { template <- heredoc(" # # renv %s [rstudio/renv#%s]: A dependency management toolkit for R. # Generated using `renv:::vendor()` at %s. # ") version <- remote$Version hash <- substring(remote$RemoteSha, 1L, 7L) sprintf(template, version, hash, Sys.time()) } renv/R/graph.R0000644000176200001440000002340714731330073012660 0ustar liggesusers #' Generate a Package Dependency Graph #' #' Generate a package dependency graph. #' #' @inheritParams renv-params #' #' @param root The top-most package dependencies of interest in the dependency graph. #' #' @param leaf The bottom-most package dependencies of interest in the dependency graph. #' #' @param suggests Should suggested packages be included within #' the dependency graph? #' #' @param enhances Should enhanced packages be included within #' the dependency graph? #' #' @param resolver An \R function accepting a package name, and returning the #' contents of its `DESCRIPTION` file (as an \R `data.frame` or `list`). #' When `NULL` (the default), an internal resolver is used. #' #' @param renderer Which package should be used to render the resulting graph? #' #' @param attributes An \R list of graphViz attributes, mapping node names to #' attribute key-value pairs. For example, to ask graphViz to prefer orienting #' the graph from left to right, you can use `list(graph = c(rankdir = "LR"))`. #' #' @examples #' #' \dontrun{ #' # graph the relationship between devtools and rlang #' graph(root = "devtools", leaf = "rlang") #' #' # figure out why a project depends on 'askpass' #' graph(leaf = "askpass") #' } #' #' @keywords internal graph <- function(root = NULL, leaf = NULL, ..., suggests = FALSE, enhances = FALSE, resolver = NULL, renderer = c("DiagrammeR", "visNetwork"), attributes = list(), project = NULL) { renv_scope_error_handler() project <- renv_project_resolve(project) # figure out packages to try and read root <- root %||% renv_graph_roots(project) # resolve fields fields <- c( "Depends", "Imports", "LinkingTo", if (suggests) "Suggests", if (enhances) "Enhances" ) # resolve renderer renderer <- renv_graph_renderer(renderer) # find dependencies envir <- new.env(parent = emptyenv()) revdeps <- new.env(parent = emptyenv()) for (package in root) renv_graph_build(package, fields, resolver, envir, revdeps) # prune the tree tree <- renv_graph_prune(root, leaf, envir, revdeps) # compute the graph graph <- enumerate(tree, function(package, dependencies) { enumerate(dependencies, function(field, packages) { attrs <- renv_graphviz_attrs(field, renderer) renv_graphviz_edge(package, packages, attrs) }) }) # figure out which packages remain part of the graph after pruning ok <- map_lgl(graph, function(items) { any(map_int(items, length) > 0) }) remaining <- intersect(root, names(graph)[ok]) if (empty(remaining)) { fmt <- "- Could not find any relationship between the requested packages." writef(fmt) return(invisible(NULL)) } defaults <- renv_graphviz_defaults(renderer) attributes <- overlay(defaults, attributes) # render attributes attrtext <- renv_graphviz_render(attributes, TRUE) # fill package names which are top-level dependencies topattrs <- renv_graphviz_render( map(named(remaining), function(name) { list( style = "filled", fillcolor = "#b3cde3" ) }), asis = FALSE ) botattrs <- renv_graphviz_render( map(named(leaf), function(name) { list( style = "filled", fillcolor = "#ccebc5" ) }), asis = FALSE ) # collapse into text parts <- c( 'digraph {', '', attrtext, '', topattrs, '', botattrs, '', unlist(graph), '', '}' ) diagram <- paste(parts, collapse = "\n") renderer <- case( identical(renderer, "DiagrammeR") ~ function(dot) { DiagrammeR <- renv_namespace_load("DiagrammeR") DiagrammeR$grViz(diagram = dot) }, identical(renderer, "visNetwork") ~ function(dot) { visNetwork <- renv_namespace_load("visNetwork") graph <- visNetwork$visNetwork(dot = dot) graph$x$options$edges$font$background <- "white" # TODO: allow hierarchical layout via option? # graph$x$options$layout = list( # hierarchical = list( # blockShifting = TRUE, # levelSeparation = 50, # nodeSpacing = 1, # shakeTowards = "roots", # sortMethod = "directed" # ) # ) graph }, is.function(renderer) ~ renderer, ~ stop("unrecognized renderer") ) renderer(diagram) } renv_graph_build <- function(package, fields, resolver, envir, revdeps) { # check if we've already scanned this package if (exists(package, envir = envir)) return() # read package dependencies deps <- renv_graph_dependencies(package, fields, resolver) # add dependencies to graph assign(package, deps, envir = envir) # recurse children <- sort(unique(unlist(deps))) for (child in children) { assign(child, c(package, revdeps[[child]]), envir = revdeps) renv_graph_build(child, fields, resolver, envir, revdeps) } } renv_graph_revdeps <- function(packages, revdeps) { envir <- new.env(parent = emptyenv()) for (package in packages) renv_graph_revdeps_impl(package, envir, revdeps) ls(envir = envir) } renv_graph_revdeps_impl <- function(package, envir, revdeps) { if (visited(package, envir)) return() for (child in revdeps[[package]]) renv_graph_revdeps_impl(child, envir, revdeps) } renv_graph_roots <- function(project) { deps <- renv_dependencies_impl(project, errors = "ignored") sort(unique(deps$Package)) } renv_graph_dependencies <- function(package, fields, resolver) { base <- installed_packages(priority = "base") desc <- local({ # try using the resolver if supplied if (!is.null(resolver)) { desc <- catch(resolver(package)) if (inherits(desc, "error")) warning(desc) else if (!is.null(desc)) return(desc) } # check for (and prefer) a locally-installed package path <- renv_package_find(package) if (nzchar(path)) return(renv_description_read(path)) # otherwise, try and see if this is a known CRAN package as.list(renv_available_packages_entry(package)) }) # parse the fields values <- map(fields, function(field) { item <- desc[[field]] if (is.null(item)) return(NULL) parsed <- renv_description_parse_field(item) packages <- parsed$Package setdiff(packages, c("R", base$Package)) }) names(values) <- fields values } renv_graph_prune <- function(root, leaf, envir, revdeps) { # grab all computed dependencies all <- as.list(envir) # if we don't have any leaves, then just return everything if (empty(leaf)) return(all) # otherwise, find recursive dependencies of the requested packages rrd <- renv_graph_revdeps(leaf, revdeps) map(all, function(children) { map(children, intersect, rrd) }) } renv_graphviz_node <- function(nodes, asis, attrs) { keys <- names(attrs) vals <- renv_json_quote(attrs) attrtext <- paste(keys, vals, sep = "=", collapse = ", ") fmt <- if (asis) '%s [%s]' else '"%s" [%s]' sprintf(fmt, nodes, attrtext) } renv_graphviz_edge <- function(lhs, rhs, attrs) { if (empty(lhs) || empty(rhs)) return(character()) keys <- names(attrs) vals <- renv_json_quote(attrs) attrtext <- paste(keys, vals, sep = "=", collapse = ", ") fmt <- '"%s" -> "%s" [%s]' sprintf(fmt, lhs, rhs, attrtext) } renv_graphviz_attrs <- function(field, renderer) { dil <- "#c0c0c0" defaults <- list( Depends = list( color = dil, style = "solid" ), Imports = list( color = dil, style = "solid" ), LinkingTo = list( color = dil, style = "dashed" ), Suggests = list( color = "darkgreen", style = "dashed" ), Enhances = list( color = "darkblue", style = "dashed" ) ) attrs <- defaults[[field]] if (identical(renderer, "visNetwork")) { extra <- c( font.align = "middle" ) attrs <- c(attrs, extra) } attrs } renv_graphviz_defaults <- function(renderer) { case( identical(renderer, "visNetwork") ~ renv_graphviz_defaults_visnetwork(), identical(renderer, "DiagrammeR") ~ renv_graphviz_defaults_diagrammer(), ) } renv_graphviz_defaults_visnetwork <- function() { list( node = list( style = "filled", shape = "ellipse", color = "black", fillcolor = "#e5d8bd", fontname = "Helvetica" ) ) } renv_graphviz_defaults_diagrammer <- function() { list( graph = list( nodesep = 0.10 ), node = list( style = "filled", shape = "ellipse", fillcolor = "#e5d8bd", fontname = "Helvetica" ) ) } renv_graphviz_render <- function(attributes, asis) { rendered <- enumerate(attributes, function(key, value) { if (is.null(names(value))) { lhs <- if (asis) key else renv_json_quote(key) rhs <- renv_graphviz_render_value(value) if (length(lhs) && length(rhs)) paste(lhs, rhs, sep = " = ") } else { keys <- names(value) vals <- renv_graphviz_render_value(value) fmt <- if (asis) '%s [%s]' else '"%s" [%s]' sprintf(fmt, key, paste(keys, vals, sep = "=", collapse = ", ")) } }) unlist(rendered, recursive = TRUE, use.names = FALSE) } renv_graphviz_render_value <- function(value) { if (is.numeric(value)) format(value) else if (is.logical(value)) tolower(as.character(value)) else renv_json_quote(value) } renv_graph_renderer <- function(renderer) { # allow functions as-is if (is.function(renderer)) return(renderer) # otherwise, match renderer <- match.arg(renderer, choices = c("DiagrammeR", "visNetwork")) if (!renv_package_installed(renderer)) { fmt <- "package '%s' is required to render graphs but is not installed" stopf(fmt, renderer) } renderer } renv/R/log.R0000644000176200001440000000422314742242046012337 0ustar liggesusers # the log level, indicating what severity of messages will be logged the$log_level <- 4L # the file to which log messages will be written the$log_file <- NULL # the scopes for which filtering will be enabled the$log_scopes <- NULL elog <- function(scope, fmt, ...) { renv_log_impl(4L, scope, fmt, ...) } wlog <- function(scope, fmt, ...) { renv_log_impl(3L, scope, fmt, ...) } ilog <- function(scope, fmt, ...) { renv_log_impl(2L, scope, fmt, ...) } dlog <- function(scope, fmt, ...) { renv_log_impl(1L, scope, fmt, ...) } renv_log_impl <- function(level, scope, fmt, ...) { # check log level if (level < the$log_level) return() # only include scopes matching the scopes scopes <- the$log_scopes if (is.character(scopes) && !scope %in% scopes) return() # build message message <- sprintf(fmt, ...) # annotate with prefix from scope, timestamp fmt <- "%sZ [renv-%i] %s: %s" now <- format(Sys.time(), format = "%Y-%m-%d %H:%M:%OS6", tz = "UTC") all <- sprintf(fmt, now, Sys.getpid(), scope, message) # write it out cat(all, file = the$log_file, sep = "\n", append = TRUE) } renv_log_init <- function(level = NULL, file = NULL, scopes = NULL) { the$log_level <- renv_log_level(level) the$log_file <- renv_log_file(file) the$log_scopes <- renv_log_scopes(scopes) } renv_log_level <- function(level = NULL) { level <- level %||% Sys.getenv("RENV_LOG_LEVEL", unset = NA) if (is.na(level)) return(4L) case( level %in% c("4", "error", "ERROR") ~ 4L, level %in% c("3", "warning", "WARNING") ~ 3L, level %in% c("2", "info", "INFO") ~ 2L, level %in% c("1", "debug", "DEBUG") ~ 1L, ~ { warningf("ignoring invalid RENV_LOG_LEVEL '%s'", level) 4L } ) } renv_log_file <- function(file = NULL) { # check for log file file <- file %||% Sys.getenv("RENV_LOG_FILE", unset = NA) if (!is.na(file)) return(file) # default to stderr, since it's unbuffered stderr() } renv_log_scopes <- function(scopes = NULL) { scopes <- scopes %||% Sys.getenv("RENV_LOG_SCOPES", unset = NA) if (is.na(scopes)) return(NULL) strsplit(scopes, ",", fixed = TRUE)[[1L]] } renv/R/process.R0000644000176200001440000000050514731330073013227 0ustar liggesusers # NOTE: We use 'psnice()' here as R also supports using that # for process detection on Windows; on all platforms R returns # NA if you request information about a non-existent process renv_process_exists <- function(pid) { !is.na(psnice(pid)) } renv_process_kill <- function(pid, signal = 15L) { pskill(pid, signal) } renv/R/zzz-libs.R0000644000176200001440000000054214731330073013336 0ustar liggesusers renv_zzz_libs <- function() { status <- tryCatch( renv_zzz_libs_impl(), error = identity ) } renv_zzz_libs_impl <- function() { if (!installing() || !renv_ext_enabled()) return(FALSE) message("** libs") package <- Sys.getenv("R_PACKAGE_DIR", unset = getwd()) renv_ext_compile(package) TRUE } renv_zzz_libs() renv/R/dots.R0000644000176200001440000000205714731330072012525 0ustar liggesusers renv_dots_check <- function(...) { dots <- list(...) parent <- parent.frame() # accept 'bioc' as an alias for 'bioconductor' bioc <- dots[["bioc"]] if (!is.null(bioc) && exists("bioconductor", envir = parent)) { if (is.null(parent$bioconductor)) { assign("bioconductor", bioc, envir = parent) dots[["bioc"]] <- NULL } } # allow 'confirm' as an alias for 'prompt' confirm <- dots[["confirm"]] if (!is.null(confirm) && exists("prompt", envir = parent)) { assign("prompt", confirm, envir = parent) dots[["confirm"]] <- NULL } # check for empty dots if (length(dots) == 0) return(TRUE) call <- sys.call(sys.parent()) func <- sys.function(sys.parent()) matched <- match.call(func, call, expand.dots = FALSE) dotcall <- format(matched["..."]) start <- regexpr("(", dotcall, fixed = TRUE) end <- nchar(dotcall) - 2L args <- substring(dotcall, start, end) n <- length(matched[["..."]]) message <- paste("unused", plural("argument", n), args) stop(simpleError(message = message, call = call)) } renv/R/remove.R0000644000176200001440000000415214731330073013050 0ustar liggesusers #' Remove packages #' #' Remove (uninstall) \R packages. #' #' @inherit renv-params #' #' @param packages A character vector of \R packages to remove. #' @param library The library from which packages should be removed. When #' `NULL`, the active library (that is, the first entry reported in #' `.libPaths()`) is used instead. #' #' @return A vector of package records, describing the packages (if any) which #' were successfully removed. #' #' @export #' #' @example examples/examples-init.R remove <- function(packages, ..., library = NULL, project = NULL) { renv_scope_error_handler() renv_dots_check(...) project <- renv_project_resolve(project) renv_project_lock(project = project) library <- renv_path_normalize(library %||% renv_libpaths_active()) # NOTE: users might request that we remove packages which aren't currently # installed, so we need to catch errors when trying to snapshot those packages descpaths <- file.path(library, packages, "DESCRIPTION") records <- lapply(descpaths, compose(catch, renv_snapshot_description)) names(records) <- packages records <- Filter(function(record) !inherits(record, "error"), records) if (library == renv_paths_library(project = project)) { writef("- Removing package(s) from project library ...") } else { fmt <- "- Removing package(s) from library '%s' ..." writef(fmt, renv_path_aliased(library)) } if (length(packages) == 1) { renv_remove_impl(packages, library) return(invisible(records)) } count <- 0 for (package in packages) { if (renv_remove_impl(package, library)) count <- count + 1 } writef("- Done! Removed %s.", nplural("package", count)) invisible(records) } renv_remove_impl <- function(package, library) { path <- file.path(library, package) if (!renv_file_exists(path)) { writef("- Package '%s' is not installed -- nothing to do.", package) return(FALSE) } recursive <- renv_file_type(path) == "directory" printf("Removing package '%s' ... ", package) unlink(path, recursive = recursive) writef("Done!") TRUE } renv/R/restore.R0000644000176200001440000003303714761163114013245 0ustar liggesusers the$restore_running <- FALSE the$restore_state <- NULL #' Restore project library from a lockfile #' #' Restore a project's dependencies from a lockfile, as previously generated by #' [snapshot()]. #' #' `renv::restore()` compares packages recorded in the lockfile to #' the packages installed in the project library. Where there are differences #' it resolves them by installing the lockfile-recorded package into the #' project library. If `clean = TRUE`, `restore()` will additionally delete any #' packages in the project library that don't appear in the lockfile. #' #' @section Transactional Restore: #' #' By default, `renv::restore()` will perform a 'transactional' restore, wherein the #' project library is mutated only if all packages within the lockfile are successfully #' restored. The intention here is to prevent the private library from entering #' an inconsistent state, if some subset of packages were to install successfully #' but some other subset of packages did not. `renv::restore(transactional = FALSE)` #' can be useful if you're attempting to restore packages from a lockfile, but would #' like to update or change certain packages piece-meal if they fail to install. #' #' The term 'transactional' here borrows from the parlance of a 'database transaction', #' where the failure of any intermediate step implies that the whole transaction #' will be rolled back, so that the state of the database before the transaction #' was initiated can be preserved. See #' for more details. #' #' @inherit renv-params #' #' @param library The library paths to be used during restore. #' #' @param packages A subset of packages recorded in the lockfile to restore. #' When `NULL` (the default), all packages available in the lockfile will be #' restored. Any required recursive dependencies of the requested packages #' will be restored as well. #' #' @param transactional Whether or not to use a 'transactional' restore. #' See **Transactional Restore** for more details. When `NULL` (the default), #' the value of the `install.transactional` [`config`] option will be used. #' #' @param exclude A subset of packages to be excluded during restore. This can #' be useful for when you'd like to restore all but a subset of packages from #' a lockfile. Note that if you attempt to exclude a package which is required #' as the recursive dependency of another package, your request will be #' ignored. #' #' @return A named list of package records which were installed by renv. #' #' @family reproducibility #' #' @export #' #' @example examples/examples-init.R restore <- function(project = NULL, ..., library = NULL, lockfile = NULL, packages = NULL, exclude = NULL, rebuild = FALSE, repos = NULL, clean = FALSE, transactional = NULL, prompt = interactive()) { renv_consent_check() renv_scope_error_handler() renv_dots_check(...) renv_scope_binding(the, "restore_running", TRUE) project <- renv_project_resolve(project) renv_project_lock(project = project) renv_scope_verbose_if(prompt) # resolve library, lockfile arguments libpaths <- renv_libpaths_resolve(library) lockfile <- lockfile %||% renv_lockfile_load(project = project, strict = TRUE) # set up .renvignore defensively renv_load_cache_renvignore(project = project) # set up transactional param transactional <- transactional %||% config$install.transactional() renv_scope_options(renv.config.install.transactional = transactional) # check and ask user if they need to activate first renv_activate_prompt("restore", library, prompt, project) # activate the requested library (place at front of library paths) library <- nth(libpaths, 1L) ensure_directory(library) renv_scope_libpaths(libpaths) # resolve the lockfile if (is.character(lockfile)) lockfile <- renv_lockfile_read(lockfile) # insert overrides (if any) lockfile <- renv_lockfile_override(lockfile) # repair potential issues in the lockfile lockfile <- renv_lockfile_repair(lockfile) # check for system requirements from these packages if (config$sysreqs.check(default = renv_platform_linux())) { records <- renv_lockfile_records(lockfile) sysreqs <- map(records, `[[`, "SystemRequirements") renv_sysreqs_check(sysreqs, prompt = prompt) } # override repositories if requested repos <- repos %||% config$repos.override() %||% lockfile$R$Repositories # transform PPM repositories if appropriate if (renv_ppm_enabled()) repos <- renv_ppm_transform(repos) if (length(repos)) renv_scope_options(repos = convert(repos, "character")) # if users have requested the use of pak, delegate there if (config$pak.enabled() && !recursing()) { renv_pak_init() records <- renv_pak_restore( lockfile = lockfile, packages = packages, exclude = exclude, prompt = prompt, project = project ) return(renv_restore_successful(records, prompt, project)) } # set up Bioconductor version + repositories biocversion <- lockfile$Bioconductor$Version if (!is.null(biocversion)) { renv_bioconductor_init(library = library) biocversion <- package_version(biocversion) renv_scope_options(renv.bioconductor.version = biocversion) } # get records for R packages currently installed current <- snapshot(project = project, library = libpaths, lockfile = NULL, type = "all") # compare lockfile vs. currently-installed packages diff <- renv_lockfile_diff_packages(current, lockfile) # don't remove packages unless 'clean = TRUE' diff <- renv_vector_diff(diff, if (!clean) "remove") # only remove packages from the project library is_package <- map_lgl(names(diff), function(package) { path <- find.package(package, lib.loc = libpaths, quiet = TRUE) identical(dirname(path), library) }) diff <- diff[!(diff == "remove" & !is_package)] # don't take any actions with ignored packages ignored <- renv_project_ignored_packages(project = project) diff <- diff[renv_vector_diff(names(diff), ignored)] # only take action with requested packages packages <- setdiff(packages %||% names(diff), exclude) diff <- diff[intersect(names(diff), packages)] if (!length(diff)) { name <- if (!missing(library)) "library" else "project" writef("- The %s is already synchronized with the lockfile.", name) return(renv_restore_successful(diff, prompt, project)) } # TODO: should we avoid double-prompting here? # we prompt once here for the preflight check, and then again below based # on the actions we'll perform. if (!renv_restore_preflight(project, libpaths, diff, current, lockfile)) cancel_if(prompt && !proceed()) if (prompt || renv_verbose()) { renv_restore_report_actions(diff, current, lockfile) cancel_if(prompt && !proceed()) } # perform the restore records <- renv_restore_run_actions(project, diff, current, lockfile, rebuild) renv_restore_successful(records, prompt, project) } renv_restore_run_actions <- function(project, actions, current, lockfile, rebuild) { packages <- names(actions) renv_scope_restore( project = project, library = renv_libpaths_active(), records = renv_lockfile_records(lockfile), packages = packages, rebuild = rebuild ) # first, handle package removals removes <- actions[actions == "remove"] enumerate(removes, function(package, action) { renv_restore_remove(project, package, current) }) # next, handle installs installs <- actions[actions != "remove"] packages <- names(installs) # perform the install records <- renv_retrieve_impl(packages) renv_install_impl(records) # detect dependency tree repair diff <- renv_lockfile_diff_packages(renv_lockfile_records(lockfile), records) diff <- diff[diff != "remove"] if (!empty(diff)) { renv_pretty_print_records( "The dependency tree was repaired during package installation:", records[names(diff)], "Call `renv::snapshot()` to capture these dependencies in the lockfile." ) } # check installed packages and prompt for reload if needed renv_install_postamble(names(records)) # return status invisible(records) } renv_restore_state <- function(key = NULL) { state <- the$restore_state if (is.null(key)) state else state[[key]] } renv_restore_begin <- function(project = NULL, library = NULL, records = NULL, packages = NULL, handler = NULL, rebuild = NULL, recursive = TRUE) { # resolve rebuild request rebuild <- case( identical(rebuild, TRUE) ~ packages, identical(rebuild, FALSE) ~ character(), identical(rebuild, "*") ~ NA_character_, as.character(rebuild) ) # get previous restore state (so we can restore it after if needed) oldstate <- the$restore_state # set new restore state the$restore_state <- env( # the active project (if any) used for restore project = project, # the library path into which packages will be installed. # this is set because some behaviors depend on whether the target # library is the project library, but during staged installs the # library paths might be mutated during restore library = library, # the package records used for restore, providing information # on the packages to be installed (their version, source, etc) records = records, # the set of packages to be installed in this restore session; # as explicitly requested by the user / front-end API call. # packages in this list should be re-installed even if a compatible # version appears to be already installed packages = packages, # an optional handler, to be used during retrieve / restore # TODO: should we split this into separate handlers? handler = handler %||% function(package, action) action, # packages which should be rebuilt (skipping the cache) rebuild = rebuild, # should package dependencies be crawled recursively? this is useful if # the records list is incomplete and needs to be built as packages are # downloaded recursive = recursive, # packages which we have attempted to retrieve retrieved = new.env(parent = emptyenv()), # packages which need to be installed install = mapping(), # a collection of the requirements imposed on dependent packages # as they are discovered requirements = new.env(parent = emptyenv()), # the number of packages that were downloaded downloaded = 0L ) # return prior state oldstate } renv_restore_end <- function(state) { the$restore_state <- state } # nocov start renv_restore_report_actions <- function(actions, current, lockfile) { if (!renv_verbose() || empty(actions)) return(invisible(NULL)) lhs <- renv_lockfile_records(current) rhs <- renv_lockfile_records(lockfile) renv_pretty_print_records_pair( "The following package(s) will be updated:", lhs[names(lhs) %in% names(actions)], rhs[names(rhs) %in% names(actions)] ) } # nocov end renv_restore_remove <- function(project, package, lockfile) { records <- renv_lockfile_records(lockfile) record <- records[[package]] printf("- Removing %s [%s] ... ", package, record$Version) paths <- renv_paths_library(project = project, package) recursive <- renv_file_type(paths) == "directory" unlink(paths, recursive = recursive) writef("OK [removed from library]") TRUE } renv_restore_preflight <- function(project, libpaths, actions, current, lockfile) { records <- renv_lockfile_records(lockfile) matching <- keep(records, names(actions)) renv_install_preflight(project, libpaths, matching) } renv_restore_find <- function(package, record) { # skip packages whose installation was explicitly requested state <- renv_restore_state() record <- renv_record_validate(package, record) if (package %in% state$packages) return("") # check the active library paths to see if this package is already installed for (library in renv_libpaths_all()) { path <- renv_restore_find_impl(package, record, library) if (nzchar(path)) return(path) } "" } renv_restore_find_impl <- function(package, record, library) { path <- file.path(library, package) if (!file.exists(path)) return("") # attempt to read DESCRIPTION current <- catch(as.list(renv_description_read(path))) if (inherits(current, "error")) return("") # check for an up-to-date version from R package repository if (renv_record_source(record) %in% c("cran", "repository")) { fields <- c("Package", "Version") if (identical(record[fields], current[fields])) return(path) } # otherwise, match on remote fields fields <- renv_record_names(record, c("Package", "Version")) if (identical(record[fields], current[fields])) return(path) # failed to match; return empty path "" } renv_restore_rebuild_required <- function(record) { state <- renv_restore_state() any(c(NA_character_, record$Package) %in% state$rebuild) } renv_restore_successful <- function(records, prompt, project) { # ensure the activate script is up-to-date renv_infrastructure_write_activate(project, create = FALSE) # perform python-related restore steps renv_python_restore(project, prompt) # return restored records invisible(records) } renv/R/errors.R0000644000176200001440000000645414731330072013075 0ustar liggesusers renv_error_format_srcref <- function(call, srcref) { srcfile <- attr(srcref, "srcfile", exact = TRUE) if (inherits(srcfile, c("srcfilecopy", "srcfilealias"))) { start <- srcref[7L] end <- srcref[8L] } else { start <- srcref[1L] end <- srcref[3L] } srclines <- getSrcLines(srcfile, start, end) index <- regexpr("[^[:space:]]", srclines) indent <- min(index) code <- substring(srclines, indent) if (length(code) >= 8L) { simplified <- renv_error_simplify(call) if (!identical(simplified, call)) code <- format(simplified) } n <- length(code) postfix <- sprintf("at %s#%i", basename(srcfile$filename), srcref[1L]) code[n] <- paste(code[n], postfix) code } renv_error_simplify <- function(object) { case( is.function(object) ~ renv_error_simplify_function(object), is.recursive(object) ~ renv_error_simplify_recursive(object), TRUE ~ object ) } renv_error_simplify_function <- function(object) { f <- function() {} formals(f) <- formals(object) body(f) <- quote({ ... }) f } renv_error_simplify_recursive <- function(object) { longcall <- renv_call_matches(object, "{") && length(object) >= 8 if (longcall) return(quote(...)) for (i in seq_along(object)) if (!is.null(object[[i]])) object[[i]] <- renv_error_simplify(object[[i]]) object } renv_error_format <- function(calls, frames) { # first, format calls formatted <- lapply(calls, function(call) { srcref <- attr(call, "srcref", exact = TRUE) if (!is.null(srcref)) { formatted <- catch(renv_error_format_srcref(call, srcref)) if (!inherits(formatted, "error")) return(formatted) } if (is.function(call[[1]])) return("(...)") format(renv_error_simplify(call)) }) # compute prefixes numbers <- format(seq_along(formatted)) prefixes <- sprintf("%s: ", rev(numbers)) # generate indent indent <- paste(rep.int(" ", min(nchar(prefixes))), collapse = "") # attach prefixes + indent annotated <- uapply(seq_along(formatted), function(i) { code <- formatted[[i]] prefix <- c(prefixes[[i]], rep.int(indent, length(code) - 1L)) paste(prefix, code, sep = "") }) header <- "Traceback (most recent calls last):" c(header, annotated) } renv_error_find <- function(calls, frames) { for (i in rev(seq_along(frames))) { fn <- sys.function(which = i) if (!identical(fn, stop)) next frame <- frames[[i]] args <- frame[["args"]] if (is.null(args) || empty(args)) next first <- args[[1L]] if (!inherits(first, "condition")) next return(first) } } renv_error_handler <- function(...) { calls <- head(sys.calls(), n = -1L) frames <- head(sys.frames(), n = -1L) error <- renv_error_find(calls, frames) if (identical(error$traceback, FALSE)) return(character()) formatted <- renv_error_format(calls, frames) caution(formatted) formatted } the$traceback <- NULL renv_error_capture <- function(e) { calls <- head(sys.calls(), n = -2L) frames <- head(sys.frames(), n = -2L) traceback <- renv_error_format(calls, frames) the$traceback <- traceback } renv_error_tag <- function(e) { e$traceback <- the$traceback e } renv_error_handler_call <- function() { as.call(list(renv_error_handler)) } renv/R/retrieve.R0000644000176200001440000011716214761163114013411 0ustar liggesusers #' Retrieve packages #' #' Retrieve (download) one or more packages from external sources. #' Using `renv::retrieve()` can be useful in CI / CD workflows, where #' you might want to download all packages listed in a lockfile #' before later invoking [renv::restore()]. Packages will be downloaded #' to an internal path within `renv`'s local state directories -- see #' [paths] for more details. #' #' If `destdir` is `NULL` and the requested package is already available #' within the `renv` cache, `renv` will return the path to that package #' directory in the cache. #' #' @inheritParams renv-params #' #' @param lockfile The path to an `renv` lockfile. When set, `renv` #' will retrieve the packages as defined within that lockfile. #' If `packages` is also non-`NULL`, then only those packages will #' be retrieved. #' #' @param destdir The directory where packages should be downloaded. #' When `NULL` (the default), the default internal storage locations #' (normally used by e.g. [renv::install()] or [renv::restore()]) will #' be used. #' #' @returns A named vector, mapping package names to the paths where #' those packages were downloaded. #' #' @export #' #' @examples #' \dontrun{ #' #' # retrieve package + versions as defined in the lockfile #' # normally used as a pre-flight step to renv::restore() #' renv::retrieve() #' #' # download one or more packages locally #' renv::retrieve("rlang", destdir = ".") #' #' } retrieve <- function(packages = NULL, ..., lockfile = NULL, destdir = NULL, project = NULL) { renv_consent_check() renv_scope_error_handler() renv_dots_check(...) project <- renv_project_resolve(project) renv_project_lock(project = project) # set destdir if available if (!is.null(destdir)) { renv_scope_options(renv.config.cache.enabled = FALSE) renv_scope_binding(the, "destdir", destdir) } # figure out which records we want to retrieve if (is.null(packages) && is.null(lockfile)) { lockfile <- renv_lockfile_load(project = project) records <- renv_lockfile_records(lockfile) packages <- names(records) } else if (is.null(lockfile)) { records <- map(packages, renv_remotes_resolve, latest = TRUE) packages <- map_chr(records, `[[`, "Package") names(records) <- packages } else if (is.character(lockfile)) { lockfile <- renv_lockfile_read(lockfile) records <- renv_lockfile_records(lockfile) packages <- packages %||% names(records) } # overlay project remotes records <- overlay(renv_project_remotes(project), records) # perform the retrieval renv_scope_restore( project = project, library = library, packages = packages, records = records ) result <- renv_retrieve_impl(packages) map_chr(result, `[[`, "Path") } renv_retrieve_impl <- function(packages) { # confirm that we have restore state set up state <- renv_restore_state() if (is.null(state)) stopf("renv_restore_begin() must be called first") # normalize repositories (ensure @CRAN@ is resolved) options(repos = renv_repos_normalize()) # transform repository URLs for PPM if (renv_ppm_enabled()) { repos <- getOption("repos") renv_scope_options(repos = renv_ppm_transform(repos)) } # ensure HTTPUserAgent is set (required for PPM binaries) agent <- renv_http_useragent() if (!grepl("renv", agent)) { renv <- sprintf("renv (%s)", renv_metadata_version()) agent <- paste(renv, agent, sep = "; ") } renv_scope_options(HTTPUserAgent = agent) before <- Sys.time() handler <- state$handler for (package in packages) handler(package, renv_retrieve_impl_one(package)) after <- Sys.time() state <- renv_restore_state() count <- state$downloaded if (count) { elapsed <- difftime(after, before, units = "secs") writef("Successfully downloaded %s in %s.", nplural("package", count), renv_difftime_format(elapsed)) writef("") } state$install$data() } renv_retrieve_impl_one <- function(package) { # skip packages with 'base' priority if (package %in% renv_packages_base()) return() # if we've already attempted retrieval of this package, skip state <- renv_restore_state() if (!is.null(state$retrieved[[package]])) return() # insert a dummy value just to avoid infinite recursions # (this will get updated on a successful installation later) state$retrieved[[package]] <- NA # extract record for package records <- state$records record <- records[[package]] %||% renv_retrieve_resolve(package) # resolve lazy records if (is.function(record)) { state$records[[package]] <- resolve(record) record <- state$records[[package]] } # normalize the record source source <- renv_record_source(record, normalize = TRUE) # don't install packages from incompatible OS ostype <- tolower(record[["OS_type"]] %||% "") skip <- renv_platform_unix() && identical(ostype, "windows") || renv_platform_windows() && identical(ostype, "unix") if (skip) return() # if this is a package from Bioconductor, activate those repositories now if (source %in% c("bioconductor")) { project <- renv_restore_state(key = "project") renv_scope_bioconductor(project = project) } # if this is a package from R-Forge, activate its repository if (source %in% c("repository")) { repository <- record$Repository %||% "" if (tolower(repository) %in% c("rforge", "r-forge")) { repos <- getOption("repos") if (!"R-Forge" %in% names(repos)) { repos[["R-Forge"]] <- "https://R-Forge.R-project.org" renv_scope_options(repos = repos) } } } # if the record doesn't declare the package version, # treat it as a request for the latest version on CRAN # TODO: should make this behavior configurable uselatest <- source %in% c("repository", "bioconductor") && is.null(record$Version) if (uselatest) { record <- withCallingHandlers( renv_available_packages_latest(package), error = function(err) stopf("package '%s' is not available", package) ) } # if the requested record is incompatible with the set # of requested package versions thus far, request the # latest version on the R package repositories # # TODO: handle more explicit dependency requirements # TODO: report to the user if they have explicitly requested # installation of this package version despite it being incompatible compat <- renv_retrieve_incompatible(package, record) if (NROW(compat)) { # get the latest available package version replacement <- renv_available_packages_latest(package) if (is.null(replacement)) stopf("package '%s' is not available", package) # if it's not compatible, then we might need to try again with # a source version (assuming type = "both") pkgtype <- getOption("pkgType") if (identical(pkgtype, "both")) { iscompat <- renv_retrieve_incompatible(package, replacement) if (NROW(iscompat)) { replacement <- renv_available_packages_latest(package, type = "source") } } # report if we couldn't find a compatible package renv_retrieve_incompatible_report(package, record, replacement, compat) record <- replacement } rebuild <- renv_restore_rebuild_required(record) if (!rebuild) { # if we have an installed package matching the requested record, finish early path <- renv_restore_find(package, record) if (file.exists(path)) { install <- !dirname(path) %in% renv_libpaths_all() return(renv_retrieve_successful(record, path, install = install)) } # if the requested record already exists in the cache, # we'll use that package for install cacheable <- renv_cache_config_enabled(project = state$project) && renv_record_cacheable(record) if (cacheable) { # try to find the record in the cache path <- renv_cache_find(record) if (nzchar(path) && renv_cache_package_validate(path)) return(renv_retrieve_successful(record, path)) } } # if this is a URL source, then it should already have a local path # check for the Path and Source fields and see if they resolve fields <- c("Path", "Source") for (field in fields) { # check for a valid field path <- record[[field]] if (is.null(path)) next # check whether it looks like an explicit source isurl <- is.character(path) && nzchar(path) && grepl("[/\\]|[.](?:zip|tgz|gz)$", path) if (!isurl) next # error if the field is declared but doesn't exist if (!file.exists(path)) { fmt <- "record for package '%s' declares local source '%s', but that file does not exist" stopf(fmt, record$Package, path) } # otherwise, success path <- renv_path_normalize(path, mustWork = TRUE) return(renv_retrieve_successful(record, path)) } # try some early shortcut methods shortcuts <- if (rebuild) c( renv_retrieve_cellar ) else c( renv_retrieve_explicit, renv_retrieve_cellar, if (!renv_tests_running() && config$install.shortcuts()) renv_retrieve_libpaths ) for (shortcut in shortcuts) { retrieved <- catch(shortcut(record)) if (identical(retrieved, TRUE)) return(TRUE) } state$downloaded <- state$downloaded + 1L if (state$downloaded == 1L) writef(header("Downloading packages")) # time to retrieve -- delegate based on previously-determined source switch(source, bioconductor = renv_retrieve_bioconductor(record), bitbucket = renv_retrieve_bitbucket(record), git = renv_retrieve_git(record), github = renv_retrieve_github(record), gitlab = renv_retrieve_gitlab(record), repository = renv_retrieve_repos(record), url = renv_retrieve_url(record), renv_retrieve_unknown_source(record) ) } renv_retrieve_name <- function(record, type = "source", ext = NULL) { package <- record$Package version <- record$RemoteSha %||% record$Version ext <- ext %||% renv_package_ext(type) sprintf("%s_%s%s", package, version, ext) } renv_retrieve_path <- function(record, type = "source", ext = NULL) { # extract relevant record information package <- record$Package name <- renv_retrieve_name(record, type, ext) # if we have a destdir override, use this if (!is.null(the$destdir)) return(file.path(the$destdir, name)) # check for packages from an PPM binary URL, and # update the package type if known source <- renv_record_source(record) if (renv_ppm_enabled()) { url <- attr(record, "url") if (is.character(url) && grepl("/__[^_]+__/", url)) type <- "binary" } # form path for package to be downloaded if (type == "source") renv_paths_source(source, package, name) else if (type == "binary") renv_paths_binary(source, package, name) else stopf("unrecognized type '%s'", type) } renv_retrieve_bioconductor <- function(record) { # try to read the bioconductor version from the record version <- renv_retrieve_bioconductor_version(record) # activate Bioconductor repositories in this context project <- renv_restore_state(key = "project") renv_scope_bioconductor(project = project, version = version) # retrieve record using updated repositories renv_retrieve_repos(record) } renv_retrieve_bioconductor_version <- function(record) { # read git branch branch <- record[["git_branch"]] if (is.null(branch)) return(NULL) # try and parse version parts <- strsplit(branch, "_", fixed = TRUE)[[1L]] ok <- length(parts) == 3L && tolower(parts[[1L]]) == "release" if (!ok) return(NULL) # we have a version; use it paste(tail(parts, n = -1L), collapse = ".") } renv_retrieve_bitbucket <- function(record) { # query repositories endpoint to find download URL host <- record$RemoteHost %||% config$bitbucket.host() origin <- renv_retrieve_origin(host) username <- record$RemoteUsername repo <- record$RemoteRepo # scope authentication renv_scope_auth(repo) fmt <- "%s/repositories/%s/%s" url <- sprintf(fmt, origin, username, repo) destfile <- renv_scope_tempfile("renv-bitbucket-") download(url, destfile = destfile, quiet = TRUE) json <- renv_json_read(destfile) # now build URL to tarball base <- json$links$html$href ref <- record$RemoteSha %||% record$RemoteRef fmt <- "%s/get/%s.tar.gz" url <- sprintf(fmt, base, ref) path <- renv_retrieve_path(record) renv_retrieve_package(record, url, path) } renv_retrieve_github <- function(record) { host <- record$RemoteHost %||% config$github.host() origin <- renv_retrieve_origin(host) username <- record$RemoteUsername repo <- record$RemoteRepo ref <- record$RemoteSha %||% record$RemoteRef if (is.null(ref)) { fmt <- "GitHub record for package '%s' has no recorded 'RemoteSha' / 'RemoteRef'" stopf(fmt, record$Package) } fmt <- "%s/repos/%s/%s/tarball/%s" url <- with(record, sprintf(fmt, origin, username, repo, ref)) path <- renv_retrieve_path(record) renv_retrieve_package(record, url, path) } renv_retrieve_gitlab <- function(record) { host <- record$RemoteHost %||% config$gitlab.host() origin <- renv_retrieve_origin(host) user <- record$RemoteUsername repo <- record$RemoteRepo id <- URLencode(paste(user, repo, sep = "/"), reserved = TRUE) fmt <- "%s/api/v4/projects/%s/repository/archive.tar.gz" url <- sprintf(fmt, origin, id) path <- renv_retrieve_path(record) sha <- record$RemoteSha %||% record$RemoteRef if (!is.null(sha)) url <- paste(url, paste("sha", sha, sep = "="), sep = "?") renv_retrieve_package(record, url, path) } renv_retrieve_git <- function(record) { # NOTE: This path will later be used during the install step, so we don't # want to clean it up afterwards path <- tempfile("renv-git-") ensure_directory(path) renv_retrieve_git_impl(record, path) renv_retrieve_successful(record, path) } renv_retrieve_git_impl <- function(record, path) { renv_git_preflight() package <- record$Package url <- record$RemoteUrl ref <- record$RemoteRef sha <- record$RemoteSha # figure out the default ref gitref <- case( nzchar(sha %||% "") ~ sha, nzchar(ref %||% "") ~ ref, "HEAD" ) # be quiet if requested quiet <- getOption("renv.git.quiet", default = TRUE) quiet <- if (quiet) "--quiet" else "" template <- heredoc(' git init ${QUIET} git remote add origin "${ORIGIN}" git fetch ${QUIET} --depth=1 origin "${REF}" git reset ${QUIET} --hard FETCH_HEAD ') data <- list( ORIGIN = url, REF = gitref, QUIET = quiet ) commands <- renv_template_replace(template, data) command <- gsub("\n", " && ", commands, fixed = TRUE) if (renv_platform_windows()) command <- paste(comspec(), "/C", command) printf("- Cloning '%s' ... ", url) before <- Sys.time() status <- local({ ensure_directory(path) renv_scope_wd(path) renv_scope_auth(record) renv_scope_git_auth() system(command) }) after <- Sys.time() if (status != 0L) { fmt <- "error cloning '%s' from '%s' [status code %i]" stopf(fmt, package, url, status) } fmt <- "OK [cloned repository in %s]" elapsed <- difftime(after, before, units = "auto") writef(fmt, renv_difftime_format(elapsed)) TRUE } renv_retrieve_cellar_find <- function(record, project = NULL) { project <- renv_project_resolve(project) # packages installed with 'remotes::install_local()' will # have a RemoteUrl entry that we can use url <- record$RemoteUrl %||% "" if (file.exists(url)) { path <- renv_path_normalize(url, mustWork = TRUE) type <- if (fileext(path) %in% c(".tgz", ".zip")) "binary" else "source" return(named(path, type)) } # otherwise, look in the cellar roots <- renv_cellar_roots(project) for (type in c("binary", "source")) { name <- renv_retrieve_name(record, type = type) for (root in roots) { package <- record$Package paths <- c( file.path(root, package, name), file.path(root, name) ) for (path in paths) if (file.exists(path)) return(named(path, type)) } } fmt <- "%s [%s] is not available locally" stopf(fmt, record$Package, record$Version) } renv_retrieve_cellar_report <- function(record) { source <- renv_record_source(record) if (source == "cellar") return(record) fmt <- "- Package %s [%s] will be installed from the cellar." with(record, writef(fmt, Package, Version)) record } renv_retrieve_cellar <- function(record) { source <- renv_retrieve_cellar_find(record) record <- renv_retrieve_cellar_report(record) renv_retrieve_successful(record, source) } renv_retrieve_libpaths <- function(record) { libpaths <- c(renv_libpaths_user(), renv_libpaths_site()) for (libpath in libpaths) if (renv_retrieve_libpaths_impl(record, libpath)) return(TRUE) } renv_retrieve_libpaths_impl <- function(record, libpath) { # form path to installed package's DESCRIPTION path <- file.path(libpath, record$Package) if (!file.exists(path)) return(FALSE) # read DESCRIPTION desc <- renv_description_read(path = path) # check if it's compatible with the requested record fields <- c("Package", "Version", grep("^Remote", names(record), value = TRUE)) compatible <- identical(record[fields], desc[fields]) if (!compatible) return(FALSE) # check that it was built for a compatible version of R built <- desc[["Built"]] if (is.null(built)) return(FALSE) ok <- catch(renv_description_built_version(desc)) if (!identical(ok, TRUE)) return(FALSE) # check that this package has a known source source <- renv_snapshot_description_source(desc) if (identical(source$Source, "unknown")) return(FALSE) # OK: copy this package as-is renv_retrieve_successful(record, path) } renv_retrieve_explicit <- function(record) { # try parsing as a local remote source <- record$Path %||% record$RemoteUrl %||% "" if (nzchar(source)) { resolved <- catch(renv_remotes_resolve_path(source)) if (inherits(resolved, "error")) return(FALSE) } # treat as 'local' source but extract path normalized <- renv_path_normalize(source, mustWork = TRUE) resolved$Source <- "Local" renv_retrieve_successful(resolved, normalized) } renv_retrieve_repos <- function(record) { # if this record is tagged with a type + url, we can # use that directly for retrieval if (renv_record_tagged(record)) return(renv_retrieve_repos_impl(record)) # figure out what package sources are okay to use here pkgtype <- getOption("pkgType", default = "source") srcok <- pkgtype %in% c("both", "source") || getOption("install.packages.check.source", default = "yes") %in% "yes" binok <- pkgtype %in% c("both", "binary") || grepl("binary", pkgtype, fixed = TRUE) # collect list of 'methods' for retrieval methods <- stack(mode = "list") # add binary package methods if (binok) { # prefer repository binaries if available methods$push(renv_retrieve_repos_binary) # also try fallback binary locations (for Nexus) methods$push(renv_retrieve_repos_binary_fallback) # if p3m is enabled, check those binaries as well if (renv_p3m_enabled()) methods$push(renv_retrieve_repos_p3m) } # next, try to retrieve from sources if (srcok) { # retrieve from source repositories methods$push(renv_retrieve_repos_source) # also try fallback source locations (for Nexus) methods$push(renv_retrieve_repos_source_fallback) # if this is a package from r-universe, try restoring from github # (currently inferred from presence for RemoteUrl field) unifields <- c("RemoteUrl", "RemoteSha") if (all(unifields %in% names(record))) methods$push(renv_retrieve_git) else methods$push(renv_retrieve_repos_archive) } # capture errors for reporting errors <- stack() for (method in methods$data()) { status <- catch( withCallingHandlers( method(record), renv.retrieve.error = function(error) { errors$push(error$data) } ) ) if (inherits(status, "error")) { errors$push(status) next } if (identical(status, TRUE)) return(TRUE) if (!is.logical(status)) { fmt <- "internal error: unexpected status code '%s'" warningf(fmt, stringify(status)) } } # if we couldn't download the package, report the errors we saw local({ renv_scope_options(warn = 1L) for (error in errors$data()) warning(error) }) remote <- renv_record_format_remote(record, compact = TRUE) stopf("failed to retrieve package '%s'", remote) } renv_retrieve_repos_error_report <- function(record, errors) { if (empty(errors)) return() messages <- extract(errors, "message") if (empty(messages)) return() messages <- unlist(messages, recursive = TRUE, use.names = FALSE) if (empty(messages)) return() fmt <- "The following error(s) occurred while retrieving '%s':" preamble <- sprintf(fmt, record$Package) bulletin( preamble = preamble, values = paste("-", messages) ) if (renv_verbose()) str(errors) } renv_retrieve_url_resolve <- function(record) { # https://github.com/rstudio/renv/issues/2060 pkgref <- record$RemotePkgRef if (!is.null(pkgref)) { remote <- renv_remotes_parse(pkgref) if (identical(remote$type, "url")) return(remote$url) } record$RemoteUrl } renv_retrieve_url <- function(record) { url <- renv_retrieve_url_resolve(record) resolved <- renv_remotes_resolve_url(url, quiet = FALSE) renv_retrieve_successful(record, resolved$Path) } renv_retrieve_repos_archive_name <- function(record, type = "source") { file <- record$File if (length(file) && !is.na(file)) return(file) ext <- renv_package_ext(type) paste0(record$Package, "_", record$Version, ext) } renv_retrieve_repos_p3m <- function(record) { # TODO: support Linux if (renv_platform_linux()) return(FALSE) # ensure local database is up-to-date renv_p3m_database_refresh(explicit = FALSE) # check that we have an available database path <- renv_p3m_database_path() if (!file.exists(path)) return(FALSE) # attempt to read it database <- catch(renv_p3m_database_load()) if (inherits(database, "error")) { warning(database) return(FALSE) } # get entry for this version of R + platform suffix <- contrib.url("", type = "binary") entry <- database[[suffix]] if (is.null(entry)) return(FALSE) # check for known entry for this package + version key <- paste(record$Package, record$Version) idate <- entry[[key]] if (is.null(idate)) return(FALSE) # convert from integer to date date <- as.Date(idate, origin = "1970-01-01") # form url to binary package base <- renv_p3m_url(date, suffix) name <- renv_retrieve_name(record, type = "binary") url <- file.path(base, name) # form path to saved file path <- renv_retrieve_path(record, "binary") # tag record with repository name record <- overlay(record, list( Source = "Repository", Repository = "P3M" )) # attempt to retrieve renv_retrieve_package(record, url, path) } renv_retrieve_repos_binary <- function(record) { renv_retrieve_repos_impl(record, "binary") } renv_retrieve_repos_binary_fallback <- function(record) { for (repo in getOption("repos")) { if (renv_nexus_enabled(repo)) { repourl <- contrib.url(repo, type = "binary") status <- catch(renv_retrieve_repos_impl(record, "binary", repo = repourl)) if (!inherits(status, "error")) return(status) } } FALSE } renv_retrieve_repos_source <- function(record) { renv_retrieve_repos_impl(record, "source") } renv_retrieve_repos_source_fallback <- function(record, repo) { for (repo in getOption("repos")) { if (renv_nexus_enabled(repo)) { repourl <- contrib.url(repo, type = "source") status <- catch(renv_retrieve_repos_impl(record, "source", repo = repourl)) if (!inherits(status, "error")) return(status) } } FALSE } renv_retrieve_repos_archive <- function(record) { # get the current repositories repos <- getOption("repos") # if this record has a repository recorded, use or prefer it repository <- record[["Repository"]] if (is.character(repository)) { names(repository) <- names(repository) %||% repository if (grepl("://", repository, fixed = TRUE)) { repos <- c(repository, repos) } else if (repository %in% names(repos)) { matches <- names(repos) == repository repos <- c(repos[matches], repos[!matches]) } } for (repo in repos) { # try to determine path to package in archive root <- renv_retrieve_repos_archive_root(repo, record) if (is.null(root)) next # attempt download; report errors via condition handler name <- renv_retrieve_repos_archive_name(record, type = "source") status <- catch(renv_retrieve_repos_impl(record, "source", name, root)) if (inherits(status, "error")) { attr(status, "record") <- record renv_condition_signal("renv.retrieve.error", status) } # exit now if we had success if (identical(status, TRUE)) return(TRUE) } return(FALSE) } renv_retrieve_repos_archive_root <- function(url, record) { # allow users to provide a custom archive path for a record, # in case they're using a repository that happens to archive # packages with a different format than regular CRAN network # https://github.com/rstudio/renv/issues/602 override <- getOption("renv.retrieve.repos.archive.path") if (is.function(override)) { result <- override(url, record) if (!is.null(result)) return(result) } # retrieve the appropriate formatter for this repository url formatter <- memoize( key = url, value = renv_retrieve_repos_archive_formatter(url) ) # use it formatter(url, record) } renv_retrieve_repos_archive_formatter <- function(url) { # list of known formatters formatters <- list( # default CRAN format cran = function(repo, record) { with(record, file.path(repo, "src/contrib/Archive", Package)) }, # format used by older releases of Artifactory # https://github.com/rstudio/renv/issues/602 # https://github.com/rstudio/renv/issues/1996 artifactory = function(repo, record) { with(record, file.path(repo, "src/contrib/Archive", Package, Version)) }, # format used by Nexus # https://github.com/rstudio/renv/issues/595 nexus = function(repo, record) { with(record, file.path(repo, "src/contrib")) } ) # check for an override override <- getOption("renv.repos.formatters") if (!is.null(override)) { formatter <- formatters[[override[[url]] %||% ""]] if (!is.null(formatter)) return(formatter) } # build URL to PACKAGES file in src/contrib pkgurl <- file.path(url, "src/contrib/PACKAGES") headers <- renv_download_headers(pkgurl) # use the headers to infer the repository type if ("x-artifactory-id" %in% names(headers)) { formatters[["cran"]] } else if (grepl("Nexus", headers[["server"]] %||% "")) { formatters[["nexus"]] } else { formatters[["cran"]] } } # NOTE: If 'repo' is provided, it should be the path to the appropriate 'arm' # of a repository, which is normally generated from the repository URL via # 'contrib.url()'. renv_retrieve_repos_impl <- function(record, type = NULL, name = NULL, repo = NULL) { package <- record$Package version <- record$Version type <- type %||% attr(record, "type", exact = TRUE) name <- name %||% renv_retrieve_repos_archive_name(record, type) repo <- repo %||% attr(record, "url", exact = TRUE) # if we weren't provided a repository for this package, try to find it if (is.null(repo)) { entry <- catch( renv_available_packages_entry( package = package, type = type, filter = version, prefer = record[["Repository"]] ) ) if (inherits(entry, "error")) { attr(entry, "record") <- record renv_condition_signal("renv.retrieve.error", entry) return(FALSE) } # get repository path repo <- entry$Repository # add in the path if available path <- entry$Path if (length(path) && !is.na(path)) repo <- file.path(repo, path) # update the tarball name if it was declared file <- entry$File if (length(file) && !is.na(file)) name <- file } url <- file.path(repo, name) path <- renv_retrieve_path(record, type) renv_retrieve_package(record, url, path) } renv_retrieve_package <- function(record, url, path) { ensure_parent_directory(path) type <- renv_record_source(record) status <- local({ renv_scope_auth(record) preamble <- renv_retrieve_package_preamble(record, url) catch(download(url, preamble = preamble, destfile = path, type = type)) }) # report error for logging upstream if (inherits(status, "error")) { attr(status, "record") <- record renv_condition_signal("renv.retrieve.error", status) } # handle FALSE returns (shouldn't normally happen?) if (identical(status, FALSE)) { fmt <- "an unknown error occurred installing '%s' (%s)" msg <- sprintf(fmt, record$Package, renv_record_format_remote(record)) status <- simpleError(msg) } # handle errors if (inherits(status, "error")) stop(status) # handle success renv_retrieve_successful(record, path) } renv_retrieve_package_preamble <- function(record, url) { message <- sprintf( "- Downloading %s from %s ... ", record$Package, record$Repository %||% record$Source ) format(message, width = the$install_step_width) } renv_retrieve_successful_subdir <- function(record, path) { # if it's a file, assume RemoteSubdir needs to be honored info <- file.info(path, extra_cols = FALSE) if (identical(info$isdir, FALSE)) return(record$RemoteSubdir) # otherwise, respect RemoteSubdir only if it seems to # point at a valid DESCRPITION file if (!is.null(record$RemoteSubdir)) { parts <- c(path, record$RemoteSubdir, "DESCRIPTION") descpath <- paste(parts, collapse = "/") if (file.exists(descpath)) return(record$RemoteSubdir) } } renv_retrieve_successful <- function(record, path, install = TRUE) { # if we downloaded an archive, adjust its permissions here mode <- Sys.getenv("RENV_CACHE_MODE", unset = NA) if (!is.na(mode)) { info <- file.info(path, extra_cols = FALSE) if (identical(info$isdir, FALSE)) { parent <- dirname(path) renv_system_exec( command = "chmod", args = c("-Rf", renv_shell_quote(mode), renv_shell_path(parent)), action = "chmoding cached package", quiet = TRUE, success = NULL ) } } # the handling of 'subdir' here is a little awkward, as this function # can receive: # # - archives, whose package might live within a sub-directory; # - folders, whose package might live within a sub-directory; # - cache paths, for which the subdir is no longer relevant # # this warrants a proper cleanup, but for now we we use a hack subdir <- renv_retrieve_successful_subdir(record, path) # augment record with information from DESCRIPTION file desc <- renv_description_read(path, subdir = subdir) # update the record's package name, version # TODO: should we warn if they didn't match for some reason? package <- record$Package <- desc$Package record$Version <- desc$Version # add in path information to record (used later during install) record$Path <- path # add information on the retrieved record state <- renv_restore_state() state$retrieved[[package]] <- record # record this package's requirements requirements <- state$requirements # figure out the dependency fields to use -- if the user explicitly requested # this package be installed, but also provided a 'dependencies' argument in # the call to 'install()', then we want to use those fields <- if (package %in% state$packages) the$install_dependency_fields else "strong" deps <- renv_dependencies_discover_description(path, subdir = subdir, fields = fields) if (length(deps$Source)) deps$Source <- record$Package rowapply(deps, function(dep) { package <- dep[["Package"]] requirements[[package]] <- requirements[[package]] %||% stack() requirements[[package]]$push(dep) }) # read and handle remotes declared by this package remotes <- desc$Remotes if (length(remotes) && config$install.remotes()) renv_retrieve_remotes(remotes) # ensure its dependencies are retrieved as well if (state$recursive) local({ repos <- if (is.null(desc$biocViews)) getOption("repos") else renv_bioconductor_repos() renv_scope_options(repos = repos) renv_retrieve_successful_recurse(deps) }) # mark package as requiring install if needed if (install && !state$install$contains(package)) state$install$insert(package, record) TRUE } renv_retrieve_successful_recurse <- function(deps) { remotes <- setdiff(unique(deps$Package), renv_packages_base()) for (remote in remotes) renv_retrieve_successful_recurse_impl(remote) } renv_retrieve_successful_recurse_impl_check <- function(remote) { # only done for package names if (!grepl(renv_regexps_package_name(), remote)) return(FALSE) # check whether this package has been retrieved yet state <- renv_restore_state() record <- state$retrieved[[remote]] if (is.null(record) || identical(record, NA)) return(FALSE) # check the current requirements for this package incompat <- renv_retrieve_incompatible(remote, record) if (NROW(incompat) == 0L) return(FALSE) # we have an incompatible record; ensure it gets retrieved state$retrieved[[remote]] <- NULL TRUE } renv_retrieve_successful_recurse_impl <- function(remote) { # if remote is a plain package name that we've already retrieved, # we may need to retrieve it again if the version of that package # required is greater than the previously-obtained version # # TODO: implement a proper solver so we can stop doing these hacks... # if this is a 'plain' package remote, retrieve it force <- renv_retrieve_successful_recurse_impl_check(remote) dynamic( key = list(remote = remote), value = renv_retrieve_successful_recurse_impl_one(remote), force = force ) } renv_retrieve_successful_recurse_impl_one <- function(remote) { # ignore base packages base <- renv_packages_base() if (remote %in% base) return(list()) # if this is a 'plain' package remote, retrieve it if (grepl(renv_regexps_package_name(), remote)) { renv_retrieve_impl_one(remote) return(list()) } # otherwise, handle custom remotes record <- renv_retrieve_remotes_impl(remote) if (length(record)) { renv_retrieve_impl_one(record$Package) return(list()) } list() } renv_retrieve_unknown_source <- function(record) { # try to find a matching local package status <- catch(renv_retrieve_cellar(record)) if (!inherits(status, "error")) return(status) # failed; parse as though from R package repository record$Source <- "Repository" renv_retrieve_repos(record) } # TODO: what should we do if we detect incompatible remotes? # e.g. if pkg A requests 'r-lib/rlang@0.3' but pkg B requests # 'r-lib/rlang@0.2'. renv_retrieve_remotes <- function(remotes) { remotes <- strsplit(remotes, "\\s*,\\s*")[[1L]] for (remote in remotes) renv_retrieve_remotes_impl(remote) } renv_retrieve_remotes_impl <- function(remote) { dynamic( key = list(remote = remote), value = renv_retrieve_remotes_impl_one(remote) ) } renv_retrieve_remotes_impl_one <- function(remote) { # TODO: allow customization of behavior when remote parsing fails? resolved <- catch(renv_remotes_resolve(remote)) if (inherits(resolved, "error")) { warningf("failed to resolve remote '%s'; skipping", remote) return(invisible(NULL)) } # get the current package record state <- renv_restore_state() package <- resolved$Package record <- state$records[[package]] # if we already have a package record, and it's not a 'plain' # repository record, skip skip <- !is.null(record) && !identical(record, list(Package = package, Source = "Repository")) if (skip) { dlog("retrieve", "skipping remote '%s'; it's already been declared", remote) dlog("retrieve", "using existing remote '%s'", stringify(record)) return(invisible(NULL)) } # update the requested record dlog("retrieve", "using remote '%s'", remote) state$records[[package]] <- resolved # mark the record as needing retrieval state$retrieved[[package]] <- NULL # return new record invisible(resolved) } renv_retrieve_resolve <- function(package) { tryCatch( renv_snapshot_description(package = package), error = function(e) { renv_retrieve_missing_record(package) } ) } renv_retrieve_missing_record <- function(package) { # TODO: allow users to configure the action to take here, e.g. # # 1. retrieve latest from R repositories (the default), # 2. request a package + version to be retrieved, # 3. hard error # record <- renv_available_packages_latest(package) if (!is.null(record)) return(record) fmt <- heredoc(" renv was unable to find a compatible version of package '%1$s'. The latest-available version %1$s is '%2$s', but that version does not appear to be compatible with this version of R. You may need to manually re-install a different version of '%1$s'. ") entry <- renv_available_packages_entry(package, type = "source") version <- entry$Version %||% "" writef(fmt, package, version) stopf("failed to find a compatible version of the '%s' package", package) } # check to see if this requested record is incompatible # with the set of required dependencies recorded thus far # during the package retrieval process renv_retrieve_incompatible <- function(package, record) { state <- renv_restore_state() record <- renv_record_validate(package, record) # check and see if the installed version satisfies all requirements requirements <- state$requirements[[package]] if (is.null(requirements)) return(NULL) data <- bind(requirements$data()) explicit <- data[nzchar(data$Require) & nzchar(data$Version), ] if (nrow(explicit) == 0) return(NULL) # drop 'Dev' column explicit$Dev <- NULL # retrieve record version version <- record$Version if (is.null(version)) return(NULL) # for each row, compute whether we're compatible rversion <- numeric_version(version) compatible <- map_lgl(seq_len(nrow(explicit)), function(i) { expr <- call(explicit$Require[[i]], rversion, explicit$Version[[i]]) eval(expr, envir = baseenv()) }) # keep whatever wasn't compatible explicit[!compatible, ] } renv_retrieve_incompatible_report <- function(package, record, replacement, compat) { # only report if the user explicitly requesting installation of a particular # version of a package, but that package isn't actually compatible state <- renv_restore_state() if (!package %in% state$packages) return() fmt <- "%s (requires %s %s %s)" values <- with(compat, sprintf(fmt, Source, Package, Require, Version)) fmt <- "Installation of '%s %s' was requested, but the following constraints are not met:" preamble <- with(record, sprintf(fmt, Package, Version)) fmt <- "renv will try to install '%s %s' instead." postamble <- with(replacement, sprintf(fmt, Package, Version)) if (!renv_tests_running()) { bulletin( preamble = preamble, values = values, postamble = postamble ) } } renv_retrieve_origin <- function(host) { # NOTE: some host URLs may come with a protocol already formed; # if we find a protocol, use it as-is if (grepl("://", host, fixed = TRUE)) return(host) # otherwise, prepend protocol (assume https) paste("https", host, sep = "://") } renv/R/bioconductor.R0000644000176200001440000001247614753435173014270 0ustar liggesusers renv_bioconductor_manager <- function() { if (getRversion() >= "3.5.0") "BiocManager" else "BiocInstaller" } renv_bioconductor_versions <- function() { # map versions of Bioconductor to the versions of R they can be used with list( "3.9" = "3.6", "3.10" = "3.6", "3.11" = "4.0", "3.12" = "4.0", "3.13" = "4.1", "3.14" = "4.1", "3.15" = "4.2", "3.16" = "4.2", "3.17" = "4.3", "3.18" = "4.3", "3.19" = "4.4", "3.20" = "4.4", "3.21" = "4.5", # speculative "3.22" = "4.5" # speculative ) } renv_bioconductor_validate <- function(version, prompt = interactive()) { # check for the requested Bioconductor version in our internal version map; # if it doesn't exist, then just assume compatibility # # we previously used BiocManager for this, but because it makes web requests, # this can be prohibitively slow for certain users # # https://github.com/rstudio/renv/issues/2091 biocversions <- renv_bioconductor_versions() rversion <- biocversions[[version]] if (is.null(rversion)) return(TRUE) # check that the version of R in use matches what Bioconductor requires ok <- renv_version_eq(rversion, getRversion(), n = 2L) if (ok) return(TRUE) fmt <- lines( "You are using Bioconductor %1$s, which is not compatible with R %2$s.", "Use 'renv::init(bioconductor = TRUE)' to re-initialize this project with the appropriate Bioconductor release.", if (renv_package_installed("BiocVersion")) "Please uninstall the 'BiocVersion' package first, with `remove.packages(\"BiocVersion\")`." ) caution(fmt, version, getRversion()) if (prompt) { writef("") response <- ask("Would you still like to use this version of Bioconductor?") cancel_if(!response) } TRUE } renv_bioconductor_init <- function(library = NULL) { renv_scope_options(renv.verbose = FALSE) if (identical(renv_bioconductor_manager(), "BiocManager")) renv_bioconductor_init_biocmanager(library) else renv_bioconductor_init_biocinstaller(library) } renv_bioconductor_init_biocmanager <- function(library = NULL) { library <- library %||% renv_libpaths_active() if (renv_package_installed("BiocManager", lib.loc = library)) return(TRUE) ensure_directory(library) install("BiocManager", library = library, prompt = FALSE) TRUE } renv_bioconductor_init_biocinstaller <- function(library = NULL) { library <- library %||% renv_libpaths_active() if (renv_package_installed("BiocInstaller", lib.loc = library)) return(TRUE) url <- "https://bioconductor.org/biocLite.R" destfile <- renv_scope_tempfile("renv-bioclite-", fileext = ".R") download(url, destfile = destfile, quiet = TRUE) ensure_directory(library) renv_scope_libpaths(library) source(destfile) TRUE } renv_bioconductor_version <- function(project, refresh = FALSE) { # check and see if we have an override via option version <- getOption("renv.bioconductor.version") if (!is.null(version)) return(version) # check and see if the project has been configured to use a specific # Bioconductor release if (!refresh) { version <- settings$bioconductor.version(project = project) if (length(version)) return(version) } # if BiocVersion is installed, use it if (renv_package_available("BiocVersion")) return(format(packageVersion("BiocVersion")[1, 1:2])) # make sure the required bioc package is available renv_bioconductor_init() # otherwise, infer the Bioconductor version from installed packages case( renv_package_available("BiocManager") ~ { BiocManager <- renv_scope_biocmanager() format(BiocManager$version()) }, renv_package_available("BiocVersion") ~ { BiocInstaller <- renv_namespace_load("BiocInstaller") format(BiocInstaller$biocVersion()) } ) } # Returns the union of the inferred Bioconductor repositories, together with the # current value of the 'repos' R option. The Bioconductor repositories are # placed first in the repository list. renv_bioconductor_repos <- function(project = NULL, version = NULL) { # allow bioconductor repos override repos <- getOption("renv.bioconductor.repos") if (!is.null(repos)) return(repos) # make sure the required bioc package is available renv_bioconductor_init() # read Bioconductor version (normally set during restore) version <- version %||% renv_bioconductor_version(project = project) # read Bioconductor repositories (prefer BiocInstaller for older R) if (identical(renv_bioconductor_manager(), "BiocManager")) renv_bioconductor_repos_biocmanager(version) else renv_bioconductor_repos_biocinstaller(version) } renv_bioconductor_repos_biocmanager <- function(version) { BiocManager <- renv_scope_biocmanager() version <- version %||% BiocManager$version() tryCatch( BiocManager$.repositories(site_repository = character(), version = version), error = function(e) { BiocManager$repositories(version = version) } ) } renv_bioconductor_repos_biocinstaller <- function(version) { BiocInstaller <- asNamespace("BiocInstaller") version <- version %||% BiocInstaller$biocVersion() BiocInstaller$biocinstallRepos(version = version) } renv_bioconductor_required <- function(records) { for (record in records) if (identical(record$Source, "Bioconductor")) return(TRUE) FALSE } renv/R/updates.R0000644000176200001440000000062614731330073013222 0ustar liggesusers renv_updates_create <- function(diff, old, new) { structure( list(diff = diff, old = old, new = new), class = "renv_updates" ) } renv_updates_report <- function(preamble, diff, old, new) { lhs <- renv_lockfile_records(old) rhs <- renv_lockfile_records(new) renv_pretty_print_records_pair( preamble, lhs[names(lhs) %in% names(diff)], rhs[names(rhs) %in% names(diff)] ) } renv/R/checkout.R0000644000176200001440000002133714731330072013363 0ustar liggesusers #' Checkout a repository #' #' `renv::checkout()` can be used to retrieve the latest-available packages from #' a (set of) package repositories. #' #' `renv::checkout()` is most useful with services like the Posit's #' [Package Manager](https://packagemanager.rstudio.com/), as it #' can be used to switch between different repository snapshots within an #' renv project. In this way, you can upgrade (or downgrade) all of the #' packages used in a particular renv project to the package versions #' provided by a particular snapshot. #' #' Note that calling `renv::checkout()` will also install the version of `renv` #' available as of the requested snapshot date, which might be older or lack #' features available in the currently-installed version of `renv`. In addition, #' the project's `renv/activate.R` script will be re-generated after checkout. #' If this is undesired, you can re-install a newer version of `renv` after #' checkout from your regular \R package repository. #' #' @section Caveats: #' #' If your library contains packages installed from other remote sources (e.g. #' GitHub), but a version of a package of the same name is provided by the #' repositories being checked out, then please be aware that the package will be #' replaced with the version provided by the requested repositories. This could #' be a concern if your project uses \R packages from GitHub whose name matches #' that of an existing CRAN package, but is otherwise unrelated to the package #' on CRAN. #' #' @inheritParams renv-params #' #' @param repos The \R package repositories to use. #' #' @param packages The packages to be installed. When `NULL` (the default), #' all packages currently used in the project will be installed, as #' determined by [renv::dependencies()]. The recursive dependencies of these #' packages will be included as well. #' #' @param date The snapshot date to use. When set, the associated snapshot as #' available from the Posit's public #' [Package Manager](https://packagemanager.rstudio.com/) instance will be #' used. Ignored if `repos` is non-`NULL`. #' #' @param restart Should the \R session be restarted after the new #' packages have been checked out? When `NULL` (the default), the #' session is restarted if the `"restore"` action was taken. #' #' @param actions The action(s) to perform with the requested repositories. #' This can either be `"snapshot"`, in which `renv` will generate a lockfile #' based on the latest versions of the packages available from `repos`, or #' `"restore"` if you'd like to install those packages. You can use #' `c("snapshot", "restore")` if you'd like to generate a lockfile and #' install those packages in a single call. #' #' @examples #' \dontrun{ #' #' # check out packages from PPM using the date '2023-01-02' #' renv::checkout(date = "2023-01-02") #' #' # alternatively, supply the full repository path #' renv::checkout(repos = c(PPM = "https://packagemanager.rstudio.com/cran/2023-01-02")) #' #' # only check out some subset of packages (and their recursive dependencies) #' renv::checkout(packages = "dplyr", date = "2023-01-02") #' #' # generate a lockfile based on a snapshot date #' renv::checkout(date = "2023-01-02", actions = "snapshot") #' #' } #' @export checkout <- function(repos = NULL, ..., packages = NULL, date = NULL, clean = FALSE, actions = "restore", restart = NULL, project = NULL) { renv_consent_check() renv_scope_error_handler() renv_dots_check(...) project <- renv_project_resolve(project) renv_project_lock(project = project) # set new repositories repos <- repos %||% renv_checkout_repos(date) options(repos = repos) # TODO: Activate Bioconductor if it appears to be used by this project # select packages to install packages <- packages %||% renv_checkout_packages(project = project) # get the associated remotes for these packages remotes <- renv_checkout_remotes(packages, project) # parse these into package records records <- map(remotes, renv_remotes_resolve, latest = TRUE) # create a lockfile matching this request lockfile <- renv_lockfile_init(project) lockfile$Packages <- records if ("restore" %in% actions) local({ # install the requested packages restore(lockfile = lockfile, clean = clean) # make sure we can find 'renv' on the library paths path <- renv_namespace_path("renv") renv_scope_libpaths(c(dirname(path), renv_libpaths_all())) # invoke activate args <- c("--vanilla", "-s", "-e", shQuote("renv::activate()")) r(args) # update the renv lockfile record # (note: it might not be available when running tests) renv <- renv_lockfile_records(lockfile)[["renv"]] if (!is.null(renv)) { renv_scope_options(renv.verbose = FALSE) record(records = list(renv = renv), project = project) } }) # re-generate the lockfile if requested if ("snapshot" %in% actions) { snapshot(project) } # try to restart the session if we installed some packages restart <- restart %||% "restore" %in% actions if (restart) renv_restart_request(project = project, reason = "renv has been updated") invisible(lockfile) } renv_checkout_packages <- function(project) { renv_dependencies_impl( project, field = "Package", dev = TRUE ) } renv_checkout_remotes <- function(packages, project) { # get available packages dbs <- available_packages(type = "source") if (is.null(dbs)) stop("no package repositories are available") # flatten so we only see the latest version of a package db <- renv_available_packages_flatten(dbs) # keep only packages which appear to be available in the repositories packages <- intersect(packages, db$Package) # remove ignored packages -- note we intentionally do this before # computing recursive dependencies as we don't want to allow users # to ignore a recursive dependency of a required package ignored <- renv_project_ignored_packages(project) packages <- setdiff(packages, ignored) # compute recursive dependencies for these packages renv_checkout_recdeps(packages, db) } renv_checkout_recdeps <- function(packages, db) { # initialize environment (will map package names to discovered remotes) envir <- new.env(parent = emptyenv()) # set R to NA since it's a common non-package 'dependency' for packages envir$R <- NA # iterate through dependencies for (package in packages) renv_checkout_recdeps_impl(package, db, envir) # get list of discovered dependencies recdeps <- as.list.environment(envir, all.names = TRUE) # drop any NA values recdeps <- filter(recdeps, Negate(is.na)) # return sorted vector recdeps[csort(names(recdeps))] } renv_checkout_recdeps_impl <- function(package, db, envir) { # check if we've already visited this package if (!is.null(envir[[package]])) return() # get entry from database entry <- rows(db, db$Package == package) if (nrow(entry) == 0L) { envir[[package]] <- NA_character_ return() } # set discovered remote envir[[package]] <- with(entry, paste(Package, Version, sep = "@")) # iterate through hard dependencies fields <- c("Depends", "Imports", "LinkingTo") for (field in fields) { value <- entry[[field]] if (!is.null(value) && !is.na(value)) { value <- renv_description_parse_field(entry[[field]]) for (package in value$Package) if (is.null(envir[[package]])) renv_checkout_recdeps_impl(package, db, envir) } } # for soft dependencies, only include those if they're currently installed # TODO: or check if it's in the lockfile? value <- entry[["Suggests"]] if (!is.null(value) && !is.na(value)) { value <- renv_description_parse_field(value) for (package in value$Package) if (is.null(envir[[package]])) if (renv_package_installed(package)) renv_checkout_recdeps_impl(package, db, envir) } } renv_checkout_repos <- function(date) { # if no date was provided, just use default repositories if (is.null(date)) return(getOption("repos")) # build path to repository snapshot location root <- dirname(config$ppm.url()) url <- file.path(root, date) if (renv_download_available(file.path(url, "src/contrib/PACKAGES"))) return(c(PPM = url)) # requested date not available; try to search a bit candidate <- date for (i in 1:7) { candidate <- format(as.Date(candidate) - 1L) url <- file.path(root, candidate) if (renv_download_available(file.path(url, "src/contrib/PACKAGES"))) { fmt <- "- Snapshot date '%s' not available; using '%s' instead" printf(fmt, date, candidate) return(c(PPM = url)) } } stopf("repository snapshot '%s' not available", date) } renv/R/actions.R0000644000176200001440000000644614731330072013222 0ustar liggesusers actions <- function(action = c("snapshot", "restore"), ..., project = NULL, library = NULL, lockfile = NULL, type = settings$snapshot.type(project = project), clean = FALSE) { action <- match.arg(action) project <- renv_project_resolve(project) lockfile <- lockfile %||% renv_lockfile_path(project = project) renv_project_lock(project = project) switch( action, snapshot = renv_actions_snapshot(project, library, lockfile, type), restore = renv_actions_restore(project, library, lockfile, clean) ) } renv_actions_merge <- function(snap, lock, diff) { fields <- c("Package", "Version", "Source") defaults <- data.frame( "Package" = character(), "Library Version" = character(), "Library Source" = character(), "Lockfile Version" = character(), "Lockfile Source" = character(), check.names = FALSE, stringsAsFactors = FALSE ) lhs <- bapply(unname(renv_lockfile_records(snap)), `[`, fields) if (length(lhs)) names(lhs) <- c("Package", paste("Library", names(lhs)[-1L])) rhs <- bapply(unname(renv_lockfile_records(lock)), `[`, fields) if (length(rhs)) names(rhs) <- c("Package", paste("Lockfile", names(rhs)[-1L])) merged <- if (length(lhs) && length(rhs)) merge(lhs, rhs, by = "Package", all = TRUE) else if (length(lhs)) lhs else if (length(rhs)) rhs else defaults actions <- data.frame(Package = names(diff), Action = as.character(diff), check.names = FALSE, stringsAsFactors = FALSE) all <- merge(merged, actions, by = "Package") missing <- setdiff(names(defaults), names(all)) all[missing] <- NA_character_ all } renv_actions_snapshot <- function(project, library, lockfile, type) { lock <- renv_lockfile_load(project = project) snap <- snapshot(project = project, library = library, lockfile = NULL, type = type) diff <- renv_lockfile_diff_packages(lock, snap) renv_actions_merge(snap, lock, diff) } renv_actions_restore <- function(project, library, lockfile, clean) { # NOTE: we use a simple snapshot here as we just want to know the # difference in library state before and after applying the lockfile; # that is, we want to know what the library looks like without any # filtering of what records would be reported from the library lock <- renv_lockfile_load(project = project) snap <- snapshot(project = project, library = library, lockfile = NULL, type = "all") diff <- renv_lockfile_diff_packages(snap, lock) actions <- renv_actions_merge(snap, lock, diff) renv_actions_restore_clean(actions, clean, project) } renv_actions_restore_clean <- function(actions, clean, project) { # if not cleaning, then we don't do any removals if (!clean) { filtered <- actions[actions$Action != "remove", ] return(filtered) } # otherwise, only process removals in the project library projlib <- renv_paths_library(project = project) locations <- renv_package_find(actions$Package) keep <- actions$Action != "remove" | dirname(locations) == projlib actions[keep, ] } renv/R/unload.R0000644000176200001440000000174014731330073013035 0ustar liggesusers unload <- function(project = NULL, quiet = FALSE) { project <- renv_project_resolve(project) renv_scope_error_handler() if (renv_tests_running()) return() if (quiet) renv_scope_options(renv.verbose = FALSE) renv_envvars_restore() renv_unload_shims(project) renv_unload_project(project) renv_unload_profile(project) renv_unload_envvars(project) renv_unload_sandbox(project) renv_unload_libpaths(project) } renv_unload_shims <- function(project) { renv_shims_deactivate() } renv_unload_project <- function(project) { renv_project_clear() } renv_unload_profile <- function(project) { Sys.unsetenv("RENV_PROFILE") } renv_unload_envvars <- function(project) { renv_envvars_restore() } renv_unload_sandbox <- function(project) { renv_sandbox_deactivate() } renv_unload_libpaths <- function(project) { renv_libpaths_restore() } renv_unload_finalizer <- function(libpath) { libpath <- renv_namespace_path(.packageName) .onUnload(libpath) } renv/R/mapping.R0000644000176200001440000000052714731330073013210 0ustar liggesusers # basically just a mutable R list mapping <- function() { .data <- vector("list", 0L) list( get = function(key) { .data[[key]] }, contains = function(key) { key %in% names(.data) }, insert = function(key, value) { .data[[key]] <<- value }, data = function() { .data } ) } renv/R/pip.R0000644000176200001440000000405414731330073012344 0ustar liggesusers pip_freeze <- function(..., python = NULL) { python <- python %||% renv_python_active() hook <- getOption("renv.hooks.pip_freeze") if (is.function(hook)) return(hook(python = python)) renv_scope_envvars(PIP_DISABLE_PIP_VERSION_CHECK = "1") python <- renv_path_canonicalize(python) args <- c("-m", "pip", "freeze") action <- "invoking pip freeze" renv_system_exec(python, args, action, ...) } pip_install <- function(modules, ..., python = NULL) { python <- python %||% renv_python_active() hook <- getOption("renv.hooks.pip_install") if (is.function(hook)) return(hook(modules = modules, python = python)) renv_scope_envvars(PIP_DISABLE_PIP_VERSION_CHECK = "1") python <- renv_path_canonicalize(python) args <- c("-m", "pip", "install", "--upgrade", modules) action <- paste("installing", paste(shQuote(modules), collapse = ", ")) renv_system_exec(python, args, action, ...) } pip_install_requirements <- function(requirements, ..., python = NULL) { python <- python %||% renv_python_active() hook <- getOption("renv.hooks.pip_install_requirements") if (is.function(hook)) return(hook(requirements = requirements, python = python)) file <- renv_scope_tempfile("renv-requirements-", fileext = ".txt") writeLines(requirements, con = file) renv_scope_envvars(PIP_DISABLE_PIP_VERSION_CHECK = "1") python <- renv_path_canonicalize(python) args <- c("-m", "pip", "install", "--upgrade", "-r", renv_shell_path(file)) action <- "restoring Python packages" renv_system_exec(python, args, action, ...) } pip_uninstall <- function(modules, ..., python = NULL) { python <- python %||% renv_python_active() hook <- getOption("renv.hooks.pip_uninstall") if (is.function(hook)) return(hook(modules = modules, python = python)) renv_scope_envvars(PIP_DISABLE_PIP_VERSION_CHECK = "1") python <- renv_path_canonicalize(python) args <- c("-m", "pip", "uninstall", "--yes", modules) action <- paste("uninstalling", paste(shQuote(modules), collapse = ", ")) renv_system_exec(python, args, action, ...) TRUE } renv/R/lockfile-read.R0000644000176200001440000000515214761163114014260 0ustar liggesusers renv_lockfile_read_finish_impl <- function(key, val) { # convert repository records to named vectors # (be careful to handle NAs, NULLs) if (identical(key, "Repositories") && is.null(names(val))) { getter <- function(name) function(record) record[[name]] %||% "" %NA% "" keys <- map_chr(val, getter("Name")) vals <- map_chr(val, getter("URL")) result <- case( empty(keys) ~ list(), any(nzchar(keys)) ~ named(vals, keys), TRUE ~ vals ) return(as.list(result)) } # convert the "Requirements" field to a character vector if (identical(key, "Requirements")) return(unlist(val)) # recurse for lists if (is.list(val)) return(enumerate(val, renv_lockfile_read_finish_impl)) # return other values as-is val } renv_lockfile_read_finish <- function(data) { # create lockfile lockfile <- enumerate(data, renv_lockfile_read_finish_impl) class(lockfile) <- "renv_lockfile" # compute hashes for records if possible renv_lockfile_records(lockfile) <- renv_lockfile_records(lockfile) %>% map(function(record) { record$Hash <- record$Hash %||% { fields <- renv_hash_fields_remotes(record) if (all(names(record) %in% fields)) renv_hash_record(record) } record }) # return lockfile lockfile } renv_lockfile_read_preflight <- function(contents) { # check for merge conflict markers starts <- grep("^[<]+", contents) ends <- grep("^[>]+", contents) hasconflicts <- length(starts) && length(ends) && length(starts) == length(ends) if (hasconflicts) { parts <- .mapply(function(start, end) { c(contents[start:end], "") }, list(starts, ends), NULL) all <- unlist(parts, recursive = TRUE, use.names = FALSE) bulletin( "The lockfile contains one or more merge conflict markers:", head(all, n = -1L), "You will need to resolve these merge conflicts before the file can be read." ) stop("lockfile contains merge conflict markers; cannot proceed", call. = FALSE) } } renv_lockfile_read <- function(file = NULL, text = NULL) { # read the lockfile contents <- if (is.null(file)) unlist(strsplit(text, "\n", fixed = TRUE)) else readLines(file, warn = FALSE, encoding = "UTF-8") # check and report some potential errors (e.g. merge conflicts) renv_lockfile_read_preflight(contents) withCallingHandlers( json <- renv_json_read(text = contents), error = function(err) { stop("Failed to parse 'renv.lock':\n", conditionMessage(err)) } ) renv_lockfile_read_finish(json) } renv/R/zzz.R0000644000176200001440000001545414761163114012422 0ustar liggesusers .onLoad <- function(libname, pkgname) { # NOTE: needs to be visible to embedded instances of renv as well the$envir_self <<- renv_envir_self() # load extensions if available renv_ext_onload(libname, pkgname) # make sure renv (and packages using renv!!!) use tempdir for storage # when running tests, or R CMD check if (checking() || testing()) { # set root directory root <- Sys.getenv("RENV_PATHS_ROOT", unset = tempfile("renv-root-")) Sys.setenv(RENV_PATHS_ROOT = root) # unset on exit reg.finalizer(renv_envir_self(), function(envir) { if (identical(root, Sys.getenv("RENV_PATHS_ROOT", unset = NA))) Sys.unsetenv("RENV_PATHS_ROOT") }, onexit = TRUE) # set up sandbox -- only done on non-Windows due to strange intermittent # test failures that seemed to occur there? if (renv_platform_unix()) { sandbox <- Sys.getenv("RENV_PATHS_SANDBOX", unset = tempfile("renv-sandbox-")) Sys.setenv(RENV_PATHS_SANDBOX = sandbox) } } # don't lock sandbox while testing / checking if (testing() || checking() || devmode()) { options(renv.sandbox.locking_enabled = FALSE) Sys.setenv(RENV_SANDBOX_LOCKING_ENABLED = FALSE) } renv_defer_init() renv_metadata_init() renv_ext_init() renv_ansify_init() renv_platform_init() renv_virtualization_init() renv_envvars_init() renv_log_init() renv_methods_init() renv_libpaths_init() renv_patch_init() renv_sandbox_init() renv_sdkroot_init() renv_watchdog_init() renv_tempdir_init() if (!renv_metadata_embedded()) { # TODO: It's not clear if these callbacks are safe to use when renv is # embedded, but it's unlikely that clients would want them anyhow. renv_task_create(renv_sandbox_task) renv_task_create(renv_snapshot_task) } # if an renv project already appears to be loaded, then re-activate # the sandbox now -- this is primarily done to support suspend and # resume with RStudio where the user profile might not have been run, # but RStudio would have restored options from the prior session # # https://github.com/rstudio/renv/issues/2036 if (renv_rstudio_available()) { project <- getOption("renv.project.path") if (!is.null(project)) { renv_project_set(project) renv_sandbox_activate(project = project) } } # make sure renv is unloaded on exit, so locks etc. are released # we previously tried to orchestrate this via unloadNamespace(), # but this fails when a package importing renv is already loaded # https://github.com/rstudio/renv/issues/1621 reg.finalizer(renv_envir_self(), renv_unload_finalizer, onexit = TRUE) } .onAttach <- function(libname, pkgname) { renv_rstudio_fixup() } .onUnload <- function(libpath) { renv_lock_unload() renv_task_unload() renv_watchdog_unload() # do some extra cleanup when running R CMD check if (renv_platform_unix() && checking() && !ci()) cleanse() # flush the help db to avoid errors on reload # https://github.com/rstudio/renv/issues/1294 helpdb <- file.path(libpath, "help/renv.rdb") .Internal <- .Internal lazyLoadDBflush <- function(...) {} tryCatch( .Internal(lazyLoadDBflush(helpdb)), error = function(e) NULL ) } # NOTE: required for devtools::load_all() .onDetach <- function(libpath) { if (devmode()) .onUnload(libpath) } renv_zzz_run <- function() { # check if we're in pkgload::load_all() # if so, then create some files if (devmode()) { renv_zzz_bootstrap_activate() renv_zzz_bootstrap_config() } # check if we're running as part of R CMD build # if so, build our local repository with a copy of ourselves if (building()) renv_zzz_repos() } renv_zzz_bootstrap_activate <- function() { source <- "templates/template-activate.R" target <- "inst/resources/activate.R" scripts <- c("R/ansify.R", "R/bootstrap.R", "R/json-read.R") # Do we need an update source_mtime <- max(renv_file_info(c(source, scripts))$mtime) target_mtime <- renv_file_info(target)$mtime if (!is.na(target_mtime) && target_mtime > source_mtime) return() # read the necessary bootstrap scripts contents <- map(scripts, readLines) bootstrap <- unlist(contents) # format nicely for insertion bootstrap <- paste(" ", bootstrap) bootstrap <- paste(bootstrap, collapse = "\n") # replace template with bootstrap code template <- renv_file_read(source) replaced <- renv_template_replace(template, list(BOOTSTRAP = bootstrap)) # write to resources printf("- Generating 'inst/resources/activate.R' ... ") writeLines(replaced, con = target) writef("Done!") } renv_zzz_bootstrap_config <- function() { source <- "inst/config.yml" target <- "R/config-defaults.R" source_mtime <- renv_file_info(source)$mtime target_mtime <- renv_file_info(target)$mtime if (target_mtime > source_mtime) return() template <- renv_template_create(heredoc(leave = 2, ' ${NAME} = function(..., default = ${DEFAULT}) { renv_config_get( name = "${NAME}", type = "${TYPE}", default = default, args = list(...) ) } ')) template <- gsub("^\\n+|\\n+$", "", template) generate <- function(entry) { name <- entry$name type <- entry$type default <- entry$default code <- entry$code default <- if (length(code)) trimws(code) else deparse(default) replacements <- list( NAME = name, TYPE = type, DEFAULT = default ) renv_template_replace(template, replacements) } config <- yaml::read_yaml("inst/config.yml") code <- map_chr(config, generate) all <- c( "", "# Auto-generated by renv_zzz_bootstrap_config()", "", "#' @rdname config", "#' @export", "#' @format NULL", "config <- list(", "", paste(code, collapse = ",\n\n"), "", ")" ) printf("- Generating 'R/config-defaults.R' ... ") writeLines(all, con = target) writef("Done!") } renv_zzz_repos <- function() { # don't run if we're running tests if (checking()) return() # prevent recursion installing <- Sys.getenv("RENV_INSTALLING_REPOS", unset = NA) if (!is.na(installing)) return() renv_scope_envvars(RENV_INSTALLING_REPOS = "TRUE") writeLines("** installing renv to package-local repository") # get package directory pkgdir <- getwd() # move to build directory tdir <- tempfile("renv-build-") ensure_directory(tdir) renv_scope_wd(tdir) # build renv again r_cmd_build("renv", path = pkgdir, "--no-build-vignettes") # copy built tarball to inst folder src <- list.files(tdir, full.names = TRUE) tgt <- file.path(pkgdir, "inst/repos/src/contrib") ensure_directory(tgt) file.copy(src, tgt) # write PACKAGES renv_scope_envvars(R_DEFAULT_SERIALIZE_VERSION = "2") write_PACKAGES(tgt, type = "source") } if (identical(.packageName, "renv")) { renv_zzz_run() } renv/R/virtualization.R0000644000176200001440000000116214731330073014635 0ustar liggesusers the$virtualization_type <- NULL renv_virtualization_init <- function() { type <- tryCatch( renv_virtualization_type_impl(), error = function(e) "unknown" ) the$virtualization_type <- type } renv_virtualization_type <- function() { the$virtualization_type } renv_virtualization_type_impl <- function() { # only done on linux for now if (!renv_platform_linux()) return("native") # check for cgroup if (file.exists("/proc/1/cgroup")) { contents <- readLines("/proc/1/cgroup") if (any(grepl("/docker/", contents))) return("docker") } # assume native otherwise "native" } renv/R/vector.R0000644000176200001440000000040614731330073013053 0ustar liggesusers # these functions are like the base R equivalents, but preserve names renv_vector_diff <- function(x, y) { x[match(x, y, 0L) == 0L] } renv_vector_intersect <- function(x, y) { y[match(x, y, 0L)] } renv_vector_unique <- function(x) { x[!duplicated(x)] } renv/R/methods.R0000644000176200001440000000222514731330073013215 0ustar liggesusers renv_methods_map <- function() { list( renv_path_normalize = c( unix = "renv_path_normalize_unix", win32 = "renv_path_normalize_win32" ), renv_file_exists = c( unix = "renv_file_exists_unix", win32 = "renv_file_exists_win32" ), renv_file_list_impl = c( unix = "renv_file_list_impl_unix", win32 = "renv_file_list_impl_win32" ), renv_file_broken = c( unix = "renv_file_broken_unix", win32 = "renv_file_broken_win32" ) ) } renv_methods_init <- function() { # get list of method mappings methods <- renv_methods_map() # determine appropriate lookup key for finding alternative key <- if (renv_platform_windows()) "win32" else "unix" alts <- map(methods, `[[`, key) # update methods in namespace envir <- renv_envir_self() enumerate(alts, function(name, alt) { replacement <- eval(parse(text = alt), envir = envir) assign(name, replacement, envir = envir) }) } renv_methods_error <- function() { call <- sys.call(sys.parent()) fmt <- "internal error: '%s()' not initialized in .onLoad()" stopf(fmt, as.character(call[[1L]]), call. = FALSE) } renv/R/recurse.R0000644000176200001440000000025714731330073013225 0ustar liggesusers recurse <- function(object, callback, ...) { callback(object, ...) if (is.recursive(object)) for (i in seq_along(object)) recurse(object[[i]], callback, ...) } renv/R/http.R0000644000176200001440000000071014731330073012526 0ustar liggesusers renv_http_useragent <- function() { # https://github.com/rstudio/renv/issues/1787 agent <- getOption("renv.http.useragent", default = getOption("HTTPUserAgent")) if (is.character(agent) && length(agent) == 1L) return(agent) renv_http_useragent_default() } renv_http_useragent_default <- function() { version <- getRversion() platform <- with(R.version, paste(version, platform, arch, os)) sprintf("R/%s R (%s)", version, platform) } renv/R/renvignore.R0000644000176200001440000001520014731330073013725 0ustar liggesusers # given a path within a project, read all relevant ignore files # and generate a pattern that can be used to filter file results renv_renvignore_pattern <- function(path = getwd(), root = path) { if (is.null(root)) return(NULL) stopifnot( renv_path_absolute(path), renv_path_absolute(root) ) # prepare ignores ignores <- stack() # read ignore files parent <- path while (parent != dirname(parent)) { # attempt to read either .renvignore or .gitignore for (file in c(".renvignore", ".gitignore")) { candidate <- file.path(parent, file) if (file.exists(candidate)) { contents <- readLines(candidate, warn = FALSE) parsed <- renv_renvignore_parse(contents, parent) if (length(parsed)) ignores$push(parsed) break } } # stop once we've hit the project root if (parent == root) break parent <- dirname(parent) } # collect patterns read patterns <- ignores$data() # separate exclusions, exclusions include <- unlist(extract(patterns, "include")) exclude <- unlist(extract(patterns, "exclude")) # allow for inclusion / exclusion via option # (primarily intended for internal use with packrat) include <- c(include, renv_renvignore_pattern_extra("include", root)) exclude <- c(exclude, renv_renvignore_pattern_extra("exclude", root)) # ignore hidden directories by default exclude <- c("/[.][^/]*/$", exclude) list(include = include, exclude = exclude) } # reads a .gitignore / .renvignore file, and translates the associated # entries into PCREs which can be combined and used during directory traversal renv_renvignore_parse <- function(contents, prefix = "") { # read the ignore entries contents <- grep("^\\s*(?:#|$)", contents, value = TRUE, invert = TRUE) if (empty(contents)) return(list()) # split into inclusion, exclusion patterns negate <- substring(contents, 1L, 1L) == "!" exclude <- contents[!negate] include <- substring(contents[negate], 2L) # For include rules, if we're explicitly including a file within # a sub-directory, then we need to force all parent directories # to also be included. In other words, a rule like: # # !a/b/c # # needs to be implicitly treated like # # !/a # !/a/b # !/a/b/c # # so we perform that transformation here. # # Note that this isn't perfect; for example, with the .gitignore file # # dir # !dir/matched # # The exclusion of 'dir' will take precedence, and dir/matched won't # get a chance to apply. expanded <- map(include, function(rule) { # check for slashes; leave unslashed rules alone idx <- gregexpr("(?:/|$)", rule, perl = TRUE)[[1L]] if (length(idx) == 1L) return(rule) # otherwise, split into multiple rules for each sub-directory gsub("^/*", "/", substring(rule, 1L, idx)) }) # collapse back into a list include <- unique(unlist(expanded)) # parse patterns separately list( exclude = renv_renvignore_parse_impl(exclude, prefix), include = renv_renvignore_parse_impl(include, prefix) ) } renv_renvignore_parse_impl <- function(entries, prefix = "") { # check for empty entries list if (empty(entries)) return(character()) # remove trailing whitespace entries <- gsub("\\s+$", "", entries) # entries without a slash (other than a trailing one) should match in tree noslash <- grep("/", gsub("/*$", "", entries), fixed = TRUE, invert = TRUE) entries[noslash] <- paste("**", entries[noslash], sep = "/") # remove a leading slash (avoid double-slashing) entries <- gsub("^/+", "", entries) # save any '**' entries seen entries <- gsub("**/", "\001", entries, fixed = TRUE) entries <- gsub("/**", "\002", entries, fixed = TRUE) # transform '*' and '?' entries <- gsub("*", "\\E[^/]*\\Q", entries, fixed = TRUE) entries <- gsub("?", "\\E[^/]\\Q", entries, fixed = TRUE) # restore '**' entries entries <- gsub("\001", "\\E(?:.*/)?\\Q", entries, fixed = TRUE) entries <- gsub("\002", "/\\E.*\\Q", entries, fixed = TRUE) # if we don't have a trailing slash, then we can match both files and dirs noslash <- grep("/$", entries, invert = TRUE) entries[noslash] <- paste0(entries[noslash], "\\E(?:/)?\\Q") # enclose in \\Q \\E to ensure e.g. plain '.' are not treated # as regex characters entries <- sprintf("\\Q%s\\E$", entries) # prepend prefix entries <- sprintf("^\\Q%s/\\E%s", prefix, entries) # remove \\Q\\E, \\E\\Q entries <- gsub("\\Q\\E", "", entries, fixed = TRUE) entries <- gsub("\\E\\Q", "", entries, fixed = TRUE) # all done! entries } renv_renvignore_exec <- function(path, root, children) { # the root directory is always included if (identical(root, children)) return(FALSE) # compute exclusion patterns patterns <- renv_renvignore_pattern(path, root) # if we have no patterns, then we're not excluding anything if (empty(patterns) || empty(patterns$exclude)) return(logical(length(children))) # append slashes to files which are directories info <- renv_file_info(children) dirs <- info$isdir %in% TRUE children[dirs] <- paste0(children[dirs], "/") # get the entries that need to be excluded excludes <- logical(length = length(children)) for (pattern in patterns$exclude) if (nzchar(pattern)) excludes <- excludes | grepl(pattern, children, perl = TRUE) if (length(patterns$include)) { # check for entries that should be explicitly included # (note that these override any excludes) includes <- logical(length = length(children)) for (pattern in patterns$include) if (nzchar(pattern)) includes <- includes | grepl(pattern, children, perl = TRUE) # unset those excludes excludes[includes] <- FALSE } # return vector of excludes excludes } renv_renvignore_pattern_extra <- function(key, root) { # check for value from option optname <- paste("renv.renvignore", key, sep = ".") patterns <- getOption(optname) if (is.null(patterns)) return(NULL) # should we use the pattern as-is? asis <- attr(patterns, "asis", exact = TRUE) if (identical(asis, TRUE)) return(patterns) # otherwise, process it as an .renvignore-style ignore root <- attr(patterns, "root", exact = TRUE) %||% root patterns <- renv_renvignore_parse(patterns, root) patterns[[key]] } renv_renvignore_create <- function(paths, create = FALSE, contents = "*") { for (path in paths) { if (file.exists(path)) { ignorefile <- file.path(path, ".renvignore") if (!file.exists(ignorefile)) writeLines(contents, con = ignorefile) } } } renv/R/available-packages.R0000644000176200001440000004550214761163114015256 0ustar liggesusers # tools for querying information about packages available on CRAN. # note that this does _not_ merge package entries from multiple repositories; # rather, a list of databases is returned (one for each repository) available_packages <- function(type, repos = NULL, limit = NULL, quiet = FALSE, cellar = FALSE) { dynamic( key = list( type = type, repos = repos %||% getOption("repos"), cellar = cellar ), value = renv_available_packages_impl( type = type, repos = repos, limit = limit, quiet = quiet, cellar = cellar ) ) } renv_available_packages_impl <- function(type, repos = NULL, limit = NULL, quiet = FALSE, cellar = FALSE) { limit <- limit %||% Sys.getenv("R_AVAILABLE_PACKAGES_CACHE_CONTROL_MAX_AGE", "3600") repos <- renv_repos_normalize(repos %||% getOption("repos")) # invalidate cache if http_proxy or https_proxy environment variables change, # since those could effect (or even re-direct?) repository URLs envkeys <- c("http_proxy", "https_proxy", "HTTP_PROXY", "HTTPS_PROXY") envvals <- Sys.getenv(envkeys, unset = NA) # invalidate the cache if 'renv.download.headers' changes as well headers <- getOption("renv.download.headers") key <- list(repos = repos, type = type, headers = headers, envvals) # retrieve available packages dbs <- if (length(repos)) index( scope = "available-packages", key = key, value = renv_available_packages_query(type, repos, quiet), limit = as.integer(limit) ) # include cellar if requested dbs[["__renv_cellar__"]] <- if (cellar) renv_available_packages_cellar(type = type) dbs } renv_available_packages_query <- function(type, repos, quiet = FALSE) { if (quiet) renv_scope_options(renv.verbose = FALSE) fmt <- "- Querying repositories for available %s packages ... " printf(fmt, type) # exclude repositories which are known to not have packages available if (type == "binary") { ignored <- setdiff(grep("^BioC", names(repos), value = TRUE), "BioCsoft") repos <- repos[setdiff(names(repos), ignored)] } # request repositories urls <- contrib.url(repos, type) errors <- new.env(parent = emptyenv()) dbs <- map(urls, renv_available_packages_query_impl, type = type, errors = errors) names(dbs) <- names(repos) # notify finished writef("Done!") # propagate errors errors <- as.list(errors) if (empty(errors)) return(dbs) header <- "renv was unable to query available packages from the following repositories:" msgs <- enum_chr(errors, function(url, cnds) { msgs <- map_chr(cnds, conditionMessage) paste(c(header(url), msgs, ""), collapse = "\n") }) bulletin(header, msgs) filter(dbs, Negate(is.null)) } renv_available_packages_query_impl_packages_rds <- function(url) { path <- file.path(url, "PACKAGES.rds") destfile <- renv_scope_tempfile("renv-packages-", fileext = ".rds") download(url = path, destfile = destfile, quiet = TRUE) suppressWarnings(readRDS(destfile)) } renv_available_packages_query_impl_packages_gz <- function(url) { path <- file.path(url, "PACKAGES.gz") destfile <- renv_scope_tempfile("renv-packages-", fileext = ".gz") download(url = path, destfile = destfile, quiet = TRUE) suppressWarnings(read.dcf(destfile)) } renv_available_packages_query_impl_packages <- function(url) { path <- file.path(url, "PACKAGES") destfile <- renv_scope_tempfile("renv-packages-") download(url = path, destfile = destfile, quiet = TRUE) suppressWarnings(read.dcf(destfile)) } renv_available_packages_query_impl <- function(url, type, errors) { # define query_impl methods for the different PACKAGES methods <- list( renv_available_packages_query_impl_packages_rds, renv_available_packages_query_impl_packages_gz, renv_available_packages_query_impl_packages ) stack <- stack() seize <- function(restart) { function(condition) { stack$push(condition) invokeRestart(restart) } } for (method in methods) { db <- withCallingHandlers( catch(method(url)), warning = seize(restart = "muffleWarning"), message = seize(restart = "muffleMessage") ) if (inherits(db, "error")) { stack$push(db) next } return(renv_available_packages_success(db, url, type)) } assign(url, stack$data(), envir = errors) NULL } renv_available_packages_success <- function(db, url, type) { # https://github.com/rstudio/renv/issues/1706 if (empty(db)) return(data.frame()) # convert to data.frame db <- as_data_frame(db) if (nrow(db) == 0L) return(db) # build repository url repository <- rep.int(url, nrow(db)) # update with path path <- db$Path if (length(path)) { set <- !is.na(path) repository[set] <- paste(url, path[set], sep = "/") } # set it db$Repository <- repository # add in necessary missing columns required <- c( "Package", "Version", "Priority", "Depends", "Imports", "LinkingTo", "Suggests", "Enhances", "License", "License_is_FOSS", "License_restricts_use", "OS_type", "Archs", "MD5sum", if (type %in% "source") "NeedsCompilation", "File", "Repository" ) missing <- setdiff(required, names(db)) db[missing] <- NA_character_ db <- db[required] # filter as appropriate db <- renv_available_packages_filter(db) # remove row names row.names(db) <- NULL # ok db } renv_available_packages_entry <- function(package, type = "source", repos = NULL, filter = NULL, quiet = FALSE, prefer = NULL) { # if filter is a string, treat it as an explicit version requirement version <- NULL if (is.character(filter)) { version <- filter filter <- function(entries) { matches <- which(entries$Version == version) candidate <- head(matches, n = 1L) entries[candidate, ] } } # by default, provide a filter that selects the newest-available package filter <- filter %||% function(entries) { version <- numeric_version(entries$Version) ordered <- order(version, decreasing = TRUE) entries[ordered[[1]], ] } # if 'prefer' was supplied as a repository URL, include it repos <- repos %||% as.list(getOption("repos")) if (is.character(prefer)) { # ensure repositories are named nms <- names(prefer) %||% rep.int("", length(prefer)) nms[!nzchar(nms)] <- prefer[!nzchar(nms)] names(prefer) <- nms # include any 'prefer' repositories that were supplied as URLs isurl <- grep("^\\w+://", prefer) repos <- c(prefer[isurl], repos) } # read available packages dbs <- available_packages( type = type, repos = repos, quiet = quiet ) # if a preferred repository is marked and available, prefer using that if (is.character(prefer)) { idx <- omit_if(match(names(prefer), names(dbs)), is.na) ord <- c(idx, setdiff(seq_along(dbs), idx)) dbs <- dbs[ord] } # iterate through repositories, and find first matching for (i in seq_along(dbs)) { db <- dbs[[i]] matches <- which(db$Package == package) if (empty(matches)) next entries <- db[matches, ] entry <- filter(entries) if (nrow(entry) == 0) next entry[["Type"]] <- type entry[["Name"]] <- names(dbs)[[i]] %||% "" return(entry) } # report package + version if both available pkgver <- if (length(version)) paste(package, version) else package fmt <- "failed to find %s for '%s' in package repositories" stopf(fmt, type, pkgver) } renv_available_packages_record <- function(entry, type) { # check if this record was already tagged if (renv_record_tagged(entry)) return(entry) # otherwise, construct it record <- entry if (identical(record$Name, "__renv_cellar__")) { record$Source <- "Cellar" record$Repository <- NULL record$Name <- NULL } else { record$Source <- "Repository" record$Repository <- entry$Name record$Name <- NULL } # form url url <- entry$Repository path <- entry$Path name <- entry$Name # if path was supplied, it should be used relative # to the repository URL if (length(path) && !is.na(path)) url <- paste(url, path, sep = "/") renv_record_tag( record = record, type = type, url = url, name = name ) } renv_available_packages_latest_repos_impl <- function(package, type, repos) { # get available packages dbs <- available_packages( type = type, repos = repos, quiet = TRUE, cellar = TRUE ) fields <- c( "Package", "Version", "OS_type", "NeedsCompilation", "Repository", "Path", "File" ) entries <- bapply(dbs, function(db) { # extract entries for this package entries <- rows(db, db$Package == package) if (nrow(entries) == 0L) return(entries) # keep only compatible rows + the required fields cols(entries, intersect(fields, names(db))) }, index = "Name") if (is.null(entries)) return(NULL) # sort based on version version <- numeric_version(entries$Version) ordered <- order(version, decreasing = TRUE) # extract newest entry entry <- as.list(entries[ordered[[1L]], ]) # remove an NA file entry if necessary # https://github.com/rstudio/renv/issues/1045 if (length(entry$File) && is.na(entry$File)) entry$File <- NULL # return newest-available version renv_available_packages_record(entry, type) } renv_available_packages_latest <- function(package, type = NULL, repos = NULL) { methods <- list( renv_available_packages_latest_repos, if (renv_p3m_enabled()) renv_available_packages_latest_p3m ) errors <- stack() entries <- lapply(methods, function(method) { if (is.null(method)) return(NULL) entry <- catch(method(package, type, repos)) if (inherits(entry, "error")) { errors$push(entry) return(NULL) } entry }) # if both entries are null, error if (all(map_lgl(entries, is.null))) { map(errors$data(), warning) stopf("package '%s' is not available", package) } else if (is.null(entries[[2L]])) { return(entries[[1L]]) } else if (is.null(entries[[1L]])) { return(entries[[2L]]) } # extract both entries lhs <- entries[[1L]] rhs <- entries[[2L]] # extract versions lhsv <- package_version(lhs$Version %||% "0.0") rhsv <- package_version(rhs$Version %||% "0.0") # if the versions don't match, take the newest one if (lhsv > rhsv) return(lhs) else if (rhsv > lhsv) return(rhs) # otherwise, if we have a binary from the active package repositories, # use those; otherwise, use the mran binary if (identical(lhsv, rhsv)) { if (identical(attr(lhs, "type", exact = TRUE), "binary")) return(lhs) else return(rhs) } # otherwise, return the regular repository entry lhs } renv_available_packages_latest_p3m <- function(package, type = NULL, repos = NULL) { type <- type %||% getOption("pkgType") if (identical(type, "source")) stop("binary packages are not available") # ensure local p3m database is up-to-date renv_p3m_database_refresh(explicit = FALSE) # attempt to read it database <- catch(renv_p3m_database_load()) if (inherits(database, "error")) return(database) # get entry for this version of R + platform suffix <- contrib.url("", type = "binary") entry <- database[[suffix]] if (is.null(entry)) stopf("no records available from repository URL '%s'", suffix) # find all available packages keys <- attr(entry, "keys") pattern <- paste0("^", package, " ") matching <- grep(pattern, keys, perl = TRUE, value = TRUE) if (empty(matching)) stopf("package '%s' is not available", package) # take the latest-available package entries <- unlist(mget(matching, envir = entry)) sorted <- sort(entries, decreasing = TRUE) key <- names(sorted)[[1L]] idate <- sorted[[1L]] # split into package, version index <- regexpr(" ", key, fixed = TRUE) version <- substring(key, index + 1) # return an appropriate record record <- list( Package = package, Version = version, Source = "Repository", Repository = "P3M" ) # convert from integer to date date <- as.Date(idate, origin = "1970-01-01") # form url to binary package base <- renv_p3m_url(date, suffix) name <- renv_retrieve_name(record, type = "binary") url <- file.path(base, name) # tag record with url + type renv_record_tag( record = record, type = "binary", url = dirname(url), name = "P3M" ) } renv_available_packages_latest_repos <- function(package, type = NULL, repos = NULL) { type <- type %||% getOption("pkgType") repos <- repos %||% getOption("repos") # detect requests for only source packages if (identical(type, "source")) return(renv_available_packages_latest_repos_impl(package, "source", repos)) # detect requests for only binary packages if (grepl("\\bbinary\\b", type)) return(renv_available_packages_latest_repos_impl(package, "binary", repos)) # otherwise, check both source and binary repositories src <- renv_available_packages_latest_repos_impl(package, "source", repos) bin <- renv_available_packages_latest_repos_impl(package, "binary", repos) # choose an appropriate record if (is.null(src) && is.null(bin)) stopf("package '%s' is not available", package) else if (is.null(src)) renv_available_packages_record(bin, "binary") else if (is.null(bin)) renv_available_packages_record(src, "source") else renv_available_packages_latest_select(src, bin) } renv_available_packages_latest_select <- function(src, bin) { # if the binary is at least as old as the source version, # then use the binary version if (renv_version_compare(bin$Version, src$Version) >= 0) return(renv_available_packages_record(bin, "binary")) # if the user has requested we skip source repositories, # use the binary anyway ipcs <- getOption("install.packages.check.source", default = "yes") if (!identical(ipcs, "yes")) return(renv_available_packages_record(bin, "binary")) # if the package requires compilation, check to see whether # the user has opted in to compiling packages from source nc <- identical(src$NeedsCompilation, "yes") if (nc) { # check user preference re: compilation from source ipcfs <- getOption( "install.packages.compile.from.source", default = Sys.getenv("R_COMPILE_AND_INSTALL_PACKAGES") ) # if make is not available, then we can't build from source make <- Sys.getenv("MAKE", unset = "make") if (!nzchar(Sys.which(make))) ipcfs <- "never" # if we're on macOS and command line tools are not available, # then we can't build from sources if (renv_platform_macos() && !renv_xcode_available()) ipcfs <- "never" if (identical(ipcfs, "never")) return(renv_available_packages_record(bin, "binary")) } # take the source version renv_available_packages_record(src, "source") } renv_available_packages_cellar <- function(type, project = NULL) { # look in the cellar project <- renv_project_resolve(project) roots <- renv_cellar_roots(project = project) # look for packages all <- list.files( path = roots, all.files = TRUE, full.names = TRUE, recursive = TRUE, include.dirs = FALSE ) # keep only files with matching extensions ext <- renv_package_ext(type = type) keep <- all[fileext(all) %in% ext] # construct records for each cellar entry records <- lapply(keep, function(path) { # infer package name, version from tarball name base <- basename(keep) idx <- regexpr("_", base, fixed = TRUE) package <- substring(base, 1L, idx - 1L) version <- substring(base, idx + 1L, nchar(base) - nchar(ext)) # set the Repository field prefix <- if (renv_platform_windows()) "file:///" else "file://" repository <- paste0(prefix, dirname(path)) # build record list( Package = package, Version = version, Repository = repository ) }) bind(records) } renv_available_packages_filter <- function(db) { # sanity check if (is.null(db) || nrow(db) == 0L) return(db) # TODO: subarch? duplicates? # remove packages which won't work on this OS db <- renv_available_packages_filter_ostype(db) db <- renv_available_packages_filter_version(db) # return filtered database db } renv_available_packages_filter_ostype <- function(db) { ostype <- db$OS_type ok <- is.na(ostype) | ostype %in% .Platform$OS.type rows(db, ok) } renv_available_packages_filter_version <- function(db) { depends <- db$Depends # find the packages which express an R dependency splat <- strsplit(depends, "\\s*,\\s*", perl = TRUE) # remove the non-R dependencies table <- c("R ", "R\n", "R(") splat <- map(splat, function(requirements) { requirements[match(substr(requirements, 1L, 2L), table, 0L) != 0L] }) # collect the unique R dependencies dependencies <- unique(unlist(splat)) # convert this to a simpler form pattern <- "^R\\s*\\(([^\\d\\s+]+)\\s*([^\\)]+)\\)$" matches <- gsub(pattern, "\\1 \\2", dependencies, perl = TRUE) # split into operator and version idx <- regexpr(" ", matches, fixed = TRUE) ops <- substring(matches, 1L, idx - 1L) version <- numeric_version(substring(matches, idx + 1L)) # bundle the calls for efficiency ok <- rep.int(NA, length(ops)) names(ok) <- dependencies # iterate over the operations, and update our vector rversion <- getRversion() for (op in unique(ops)) { idx <- ops == op ok[idx] <- do.call(op, list(rversion, version[idx])) } # now, map the names back to their computed values, and check whether # all requirements were satisfied ok <- map_lgl(splat, function(requirements) { all(ok[requirements]) }) rows(db, ok) } # flattens available packages, keeping only the newest version renv_available_packages_flatten <- function(dbs) { # stack the databases together stacked <- bind(dbs) # order by package + version # TODO: 'order()' is kind of slow for numeric versions; can we do better? index <- with(stacked, order(Package, numeric_version(Version), decreasing = TRUE)) ordered <- rows(stacked, index) # remove duplicates dupes <- duplicated(ordered$Package) filtered <- rows(ordered, !dupes) # ready to return filtered } renv/R/ext.R0000644000176200001440000000374614731330072012362 0ustar liggesusers renv_ext_enabled <- function() { # disable on Windows; may be able to re-evaluate in future if (renv_platform_windows()) return(FALSE) # otherwise, check envvar truthy(Sys.getenv("RENV_EXT_ENABLED", unset = "TRUE")) } renv_ext_init <- function() { if (!renv_ext_enabled() || is.null(the$dll_info)) return() envir <- renv_envir_self() symbols <- ls(envir = envir, pattern = "^__ffi__") map(symbols, function(symbol) { renv_binding_replace( envir = envir, symbol = substring(symbol, 8L), replacement = envir[[symbol]] ) }) } renv_ext_onload <- function(libname, pkgname) { if (!renv_ext_enabled()) return() # if we're being invoked via devtools::load_all(), compile extensions package <- file.path(libname, pkgname) libsdir <- renv_package_libsdir(package) # use alternate library path for load_all + tests compile <- renv_envvar_exists("DEVTOOLS_LOAD") && !renv_envvar_exists("CALLR_IS_RUNNING") if (compile) { renv_ext_compile(package, libsdir) } # now try to load it soname <- paste0("renv", .Platform$dynlib.ext) sofile <- file.path(libsdir, soname) if (file.exists(sofile)) { info <- library.dynam("renv", pkgname, libname) the$dll_info <- info } } renv_ext_compile <- function(package, libsdir = renv_package_libsdir(package)) { if (!renv_ext_enabled()) return() soname <- if (renv_platform_windows()) "renv.dll" else "renv.so" unlink(file.path(libsdir, soname)) extdirs <- file.path(package, c("inst/ext", "ext")) extdir <- filter(extdirs, file.exists)[[1L]] srcfiles <- list.files(extdir, "\\.c$", full.names = TRUE) ensure_directory(libsdir) file.copy(srcfiles, libsdir) renv_scope_wd(libsdir) r <- file.path(R.home("bin"), if (.Platform$OS.type == "unix") "R" else "R.exe") system2(r, c("CMD", "SHLIB", shQuote(basename(srcfiles)))) oldfiles <- list.files(pattern = "\\.[co]$", full.names = TRUE) unlink(oldfiles) } renv/R/expr.R0000644000176200001440000000247114731330072012532 0ustar liggesusers expr <- function(expr, envir = parent.frame()) { renv_expr_impl(substitute(expr), envir) } renv_expr_impl <- function(expr, envir) { # repair parse trees expr <- renv_expr_repair(expr) # check for inject calls if (is.call(expr) && identical(expr[[1L]], as.symbol("!"))) { inner <- expr[[2L]] if (is.call(inner) && identical(inner[[1L]], as.symbol("!"))) { value <- eval(inner[[2L]], envir = envir) return(value) } } # recurse where possible if (is.recursive(expr)) { for (i in seq_along(expr)) { expr[[i]] <- renv_expr_impl(expr[[i]], envir) } } expr } renv_expr_extract <- function(expr) { if (is.call(expr) && identical(expr[[1L]], as.symbol("!"))) { inner <- expr[[2L]] if (is.call(inner) && identical(inner[[1L]], as.symbol("!"))) { return(inner[[2L]]) } } } # TODO: Doesn't properly handle precedence for multiple injections, # e.g. in '!!a + !!b + !!c'. renv_expr_repair <- function(expr) { lhs <- renv_expr_extract(expr) if (is.null(lhs)) return(expr) check <- is.call(lhs) && length(lhs) == 3L if (!check) return(expr) rhs <- renv_expr_extract(lhs[[3L]]) if (is.null(rhs)) return(expr) parts <- list( lhs[[1L]], call("!", call("!", lhs[[2L]])), call("!", call("!", rhs)) ) as.call(parts) } renv/R/url.R0000644000176200001440000000135714731330073012361 0ustar liggesusers renv_url_parse <- function(url) { pattern <- paste0( "^", "([^:]+://)?", # protocol "([^/?#]+)", # domain "(?:(/[^?#]*))?", # path "(?:[?]([^#]+))?", # parameters "(?:#(.*))?", # fragment "" ) matches <- regmatches(url, regexec(pattern, url, perl = TRUE))[[1L]] if (length(matches) != 6L) stopf("couldn't parse url '%s'", url) matches <- as.list(matches) names(matches) <- c("url", "protocol", "domain", "path", "parameters", "fragment") # parse parameters into named list matches$parameters <- renv_properties_read( text = chartr("&", "\n", matches$parameters), delimiter = "=", dequote = FALSE, trim = FALSE ) # return parsed URL matches } renv/R/autoload.R0000644000176200001440000000413614731330072013364 0ustar liggesusers #' Auto-load the active project #' #' Automatically load the renv project associated with a particular directory. #' renv will search parent directories for the renv project root; if found, #' that project will be loaded via [renv::load()]. #' #' To enable the renv auto-loader, you can place: #' #' ``` #' renv::autoload() #' ```` #' #' into your site-wide or user `.Rprofile` to ensure that renv projects are #' automatically loaded for any newly-launched \R sessions, even if those \R #' sessions are launched within the sub-directory of an renv project. #' #' If you'd like to launch \R within the sub-directory of an renv project #' without auto-loading renv, you can set the environment variable: #' #' ``` #' RENV_AUTOLOAD_ENABLED = FALSE #' ``` #' #' before starting \R. #' #' Note that `renv::autoload()` is only compatible with projects using #' `renv 0.15.3` or newer, as it relies on features within the `renv/activate.R` #' script that are only generated with newer versions of renv. #' #' @export autoload <- function() { invisible(renv_autoload_impl()) } renv_autoload_impl <- function() { # check if we're disabled enabled <- Sys.getenv("RENV_AUTOLOAD_ENABLED", unset = "TRUE") if (!truthy(enabled)) return(FALSE) # bail if load is already being called if (the$load_running) return(FALSE) # avoid recursion running <- getOption("renv.autoload.running") if (identical(running, TRUE)) return(FALSE) # set our flag renv_scope_options(renv.autoload.running = TRUE) # try to find a project project <- catch(renv_project_find()) if (inherits(project, "error")) return(FALSE) # move to project directory renv_scope_wd(project) # if we have a project profile, source it profile <- file.path(project, ".Rprofile") if (file.exists(profile)) { sys.source(profile, envir = globalenv()) return(TRUE) } # if we have an activate script, run it activate <- file.path(project, "renv/activate.R") if (file.exists(activate)) { sys.source(activate, envir = globalenv()) return(TRUE) } # otherwise, just try to load the project load(project) TRUE } renv/R/bootstrap.R0000644000176200001440000006326214740260564013606 0ustar liggesusers `%||%` <- function(x, y) { if (is.null(x)) y else x } catf <- function(fmt, ..., appendLF = TRUE) { quiet <- getOption("renv.bootstrap.quiet", default = FALSE) if (quiet) return(invisible()) msg <- sprintf(fmt, ...) cat(msg, file = stdout(), sep = if (appendLF) "\n" else "") invisible(msg) } header <- function(label, ..., prefix = "#", suffix = "-", n = min(getOption("width"), 78)) { label <- sprintf(label, ...) n <- max(n - nchar(label) - nchar(prefix) - 2L, 8L) if (n <= 0) return(paste(prefix, label)) tail <- paste(rep.int(suffix, n), collapse = "") paste0(prefix, " ", label, " ", tail) } heredoc <- function(text, leave = 0) { # remove leading, trailing whitespace trimmed <- gsub("^\\s*\\n|\\n\\s*$", "", text) # split into lines lines <- strsplit(trimmed, "\n", fixed = TRUE)[[1L]] # compute common indent indent <- regexpr("[^[:space:]]", lines) common <- min(setdiff(indent, -1L)) - leave text <- paste(substring(lines, common), collapse = "\n") # substitute in ANSI links for executable renv code ansify(text) } bootstrap <- function(version, library) { friendly <- renv_bootstrap_version_friendly(version) section <- header(sprintf("Bootstrapping renv %s", friendly)) catf(section) # attempt to download renv catf("- Downloading renv ... ", appendLF = FALSE) withCallingHandlers( tarball <- renv_bootstrap_download(version), error = function(err) { catf("FAILED") stop("failed to download:\n", conditionMessage(err)) } ) catf("OK") on.exit(unlink(tarball), add = TRUE) # now attempt to install catf("- Installing renv ... ", appendLF = FALSE) withCallingHandlers( status <- renv_bootstrap_install(version, tarball, library), error = function(err) { catf("FAILED") stop("failed to install:\n", conditionMessage(err)) } ) catf("OK") # add empty line to break up bootstrapping from normal output catf("") return(invisible()) } renv_bootstrap_tests_running <- function() { getOption("renv.tests.running", default = FALSE) } renv_bootstrap_repos <- function() { # get CRAN repository cran <- getOption("renv.repos.cran", "https://cloud.r-project.org") # check for repos override repos <- Sys.getenv("RENV_CONFIG_REPOS_OVERRIDE", unset = NA) if (!is.na(repos)) { # check for RSPM; if set, use a fallback repository for renv rspm <- Sys.getenv("RSPM", unset = NA) if (identical(rspm, repos)) repos <- c(RSPM = rspm, CRAN = cran) return(repos) } # check for lockfile repositories repos <- tryCatch(renv_bootstrap_repos_lockfile(), error = identity) if (!inherits(repos, "error") && length(repos)) return(repos) # retrieve current repos repos <- getOption("repos") # ensure @CRAN@ entries are resolved repos[repos == "@CRAN@"] <- cran # add in renv.bootstrap.repos if set default <- c(FALLBACK = "https://cloud.r-project.org") extra <- getOption("renv.bootstrap.repos", default = default) repos <- c(repos, extra) # remove duplicates that might've snuck in dupes <- duplicated(repos) | duplicated(names(repos)) repos[!dupes] } renv_bootstrap_repos_lockfile <- function() { lockpath <- Sys.getenv("RENV_PATHS_LOCKFILE", unset = "renv.lock") if (!file.exists(lockpath)) return(NULL) lockfile <- tryCatch(renv_json_read(lockpath), error = identity) if (inherits(lockfile, "error")) { warning(lockfile) return(NULL) } repos <- lockfile$R$Repositories if (length(repos) == 0) return(NULL) keys <- vapply(repos, `[[`, "Name", FUN.VALUE = character(1)) vals <- vapply(repos, `[[`, "URL", FUN.VALUE = character(1)) names(vals) <- keys return(vals) } renv_bootstrap_download <- function(version) { sha <- attr(version, "sha", exact = TRUE) methods <- if (!is.null(sha)) { # attempting to bootstrap a development version of renv c( function() renv_bootstrap_download_tarball(sha), function() renv_bootstrap_download_github(sha) ) } else { # attempting to bootstrap a release version of renv c( function() renv_bootstrap_download_tarball(version), function() renv_bootstrap_download_cran_latest(version), function() renv_bootstrap_download_cran_archive(version) ) } for (method in methods) { path <- tryCatch(method(), error = identity) if (is.character(path) && file.exists(path)) return(path) } stop("All download methods failed") } renv_bootstrap_download_impl <- function(url, destfile) { mode <- "wb" # https://bugs.r-project.org/bugzilla/show_bug.cgi?id=17715 fixup <- Sys.info()[["sysname"]] == "Windows" && substring(url, 1L, 5L) == "file:" if (fixup) mode <- "w+b" args <- list( url = url, destfile = destfile, mode = mode, quiet = TRUE ) if ("headers" %in% names(formals(utils::download.file))) { headers <- renv_bootstrap_download_custom_headers(url) if (length(headers) && is.character(headers)) args$headers <- headers } do.call(utils::download.file, args) } renv_bootstrap_download_custom_headers <- function(url) { headers <- getOption("renv.download.headers") if (is.null(headers)) return(character()) if (!is.function(headers)) stopf("'renv.download.headers' is not a function") headers <- headers(url) if (length(headers) == 0L) return(character()) if (is.list(headers)) headers <- unlist(headers, recursive = FALSE, use.names = TRUE) ok <- is.character(headers) && is.character(names(headers)) && all(nzchar(names(headers))) if (!ok) stop("invocation of 'renv.download.headers' did not return a named character vector") headers } renv_bootstrap_download_cran_latest <- function(version) { spec <- renv_bootstrap_download_cran_latest_find(version) type <- spec$type repos <- spec$repos baseurl <- utils::contrib.url(repos = repos, type = type) ext <- if (identical(type, "source")) ".tar.gz" else if (Sys.info()[["sysname"]] == "Windows") ".zip" else ".tgz" name <- sprintf("renv_%s%s", version, ext) url <- paste(baseurl, name, sep = "/") destfile <- file.path(tempdir(), name) status <- tryCatch( renv_bootstrap_download_impl(url, destfile), condition = identity ) if (inherits(status, "condition")) return(FALSE) # report success and return destfile } renv_bootstrap_download_cran_latest_find <- function(version) { # check whether binaries are supported on this system binary <- getOption("renv.bootstrap.binary", default = TRUE) && !identical(.Platform$pkgType, "source") && !identical(getOption("pkgType"), "source") && Sys.info()[["sysname"]] %in% c("Darwin", "Windows") types <- c(if (binary) "binary", "source") # iterate over types + repositories for (type in types) { for (repos in renv_bootstrap_repos()) { # build arguments for utils::available.packages() call args <- list(type = type, repos = repos) # add custom headers if available -- note that # utils::available.packages() will pass this to download.file() if ("headers" %in% names(formals(utils::download.file))) { headers <- renv_bootstrap_download_custom_headers(repos) if (length(headers) && is.character(headers)) args$headers <- headers } # retrieve package database db <- tryCatch( as.data.frame( do.call(utils::available.packages, args), stringsAsFactors = FALSE ), error = identity ) if (inherits(db, "error")) next # check for compatible entry entry <- db[db$Package %in% "renv" & db$Version %in% version, ] if (nrow(entry) == 0) next # found it; return spec to caller spec <- list(entry = entry, type = type, repos = repos) return(spec) } } # if we got here, we failed to find renv fmt <- "renv %s is not available from your declared package repositories" stop(sprintf(fmt, version)) } renv_bootstrap_download_cran_archive <- function(version) { name <- sprintf("renv_%s.tar.gz", version) repos <- renv_bootstrap_repos() urls <- file.path(repos, "src/contrib/Archive/renv", name) destfile <- file.path(tempdir(), name) for (url in urls) { status <- tryCatch( renv_bootstrap_download_impl(url, destfile), condition = identity ) if (identical(status, 0L)) return(destfile) } return(FALSE) } renv_bootstrap_download_tarball <- function(version) { # if the user has provided the path to a tarball via # an environment variable, then use it tarball <- Sys.getenv("RENV_BOOTSTRAP_TARBALL", unset = NA) if (is.na(tarball)) return() # allow directories if (dir.exists(tarball)) { name <- sprintf("renv_%s.tar.gz", version) tarball <- file.path(tarball, name) } # bail if it doesn't exist if (!file.exists(tarball)) { # let the user know we weren't able to honour their request fmt <- "- RENV_BOOTSTRAP_TARBALL is set (%s) but does not exist." msg <- sprintf(fmt, tarball) warning(msg) # bail return() } catf("- Using local tarball '%s'.", tarball) tarball } renv_bootstrap_github_token <- function() { for (envvar in c("GITHUB_TOKEN", "GITHUB_PAT", "GH_TOKEN")) { envval <- Sys.getenv(envvar, unset = NA) if (!is.na(envval)) return(envval) } } renv_bootstrap_download_github <- function(version) { enabled <- Sys.getenv("RENV_BOOTSTRAP_FROM_GITHUB", unset = "TRUE") if (!identical(enabled, "TRUE")) return(FALSE) # prepare download options token <- renv_bootstrap_github_token() if (is.null(token)) token <- "" if (nzchar(Sys.which("curl")) && nzchar(token)) { fmt <- "--location --fail --header \"Authorization: token %s\"" extra <- sprintf(fmt, token) saved <- options("download.file.method", "download.file.extra") options(download.file.method = "curl", download.file.extra = extra) on.exit(do.call(base::options, saved), add = TRUE) } else if (nzchar(Sys.which("wget")) && nzchar(token)) { fmt <- "--header=\"Authorization: token %s\"" extra <- sprintf(fmt, token) saved <- options("download.file.method", "download.file.extra") options(download.file.method = "wget", download.file.extra = extra) on.exit(do.call(base::options, saved), add = TRUE) } url <- file.path("https://api.github.com/repos/rstudio/renv/tarball", version) name <- sprintf("renv_%s.tar.gz", version) destfile <- file.path(tempdir(), name) status <- tryCatch( renv_bootstrap_download_impl(url, destfile), condition = identity ) if (!identical(status, 0L)) return(FALSE) renv_bootstrap_download_augment(destfile) return(destfile) } # Add Sha to DESCRIPTION. This is stop gap until #890, after which we # can use renv::install() to fully capture metadata. renv_bootstrap_download_augment <- function(destfile) { sha <- renv_bootstrap_git_extract_sha1_tar(destfile) if (is.null(sha)) { return() } # Untar tempdir <- tempfile("renv-github-") on.exit(unlink(tempdir, recursive = TRUE), add = TRUE) untar(destfile, exdir = tempdir) pkgdir <- dir(tempdir, full.names = TRUE)[[1]] # Modify description desc_path <- file.path(pkgdir, "DESCRIPTION") desc_lines <- readLines(desc_path) remotes_fields <- c( "RemoteType: github", "RemoteHost: api.github.com", "RemoteRepo: renv", "RemoteUsername: rstudio", "RemotePkgRef: rstudio/renv", paste("RemoteRef: ", sha), paste("RemoteSha: ", sha) ) writeLines(c(desc_lines[desc_lines != ""], remotes_fields), con = desc_path) # Re-tar local({ old <- setwd(tempdir) on.exit(setwd(old), add = TRUE) tar(destfile, compression = "gzip") }) invisible() } # Extract the commit hash from a git archive. Git archives include the SHA1 # hash as the comment field of the tarball pax extended header # (see https://www.kernel.org/pub/software/scm/git/docs/git-archive.html) # For GitHub archives this should be the first header after the default one # (512 byte) header. renv_bootstrap_git_extract_sha1_tar <- function(bundle) { # open the bundle for reading # We use gzcon for everything because (from ?gzcon) # > Reading from a connection which does not supply a 'gzip' magic # > header is equivalent to reading from the original connection conn <- gzcon(file(bundle, open = "rb", raw = TRUE)) on.exit(close(conn)) # The default pax header is 512 bytes long and the first pax extended header # with the comment should be 51 bytes long # `52 comment=` (11 chars) + 40 byte SHA1 hash len <- 0x200 + 0x33 res <- rawToChar(readBin(conn, "raw", n = len)[0x201:len]) if (grepl("^52 comment=", res)) { sub("52 comment=", "", res) } else { NULL } } renv_bootstrap_install <- function(version, tarball, library) { # attempt to install it into project library dir.create(library, showWarnings = FALSE, recursive = TRUE) output <- renv_bootstrap_install_impl(library, tarball) # check for successful install status <- attr(output, "status") if (is.null(status) || identical(status, 0L)) return(status) # an error occurred; report it header <- "installation of renv failed" lines <- paste(rep.int("=", nchar(header)), collapse = "") text <- paste(c(header, lines, output), collapse = "\n") stop(text) } renv_bootstrap_install_impl <- function(library, tarball) { # invoke using system2 so we can capture and report output bin <- R.home("bin") exe <- if (Sys.info()[["sysname"]] == "Windows") "R.exe" else "R" R <- file.path(bin, exe) args <- c( "--vanilla", "CMD", "INSTALL", "--no-multiarch", "-l", shQuote(path.expand(library)), shQuote(path.expand(tarball)) ) system2(R, args, stdout = TRUE, stderr = TRUE) } renv_bootstrap_platform_prefix <- function() { # construct version prefix version <- paste(R.version$major, R.version$minor, sep = ".") prefix <- paste("R", numeric_version(version)[1, 1:2], sep = "-") # include SVN revision for development versions of R # (to avoid sharing platform-specific artefacts with released versions of R) devel <- identical(R.version[["status"]], "Under development (unstable)") || identical(R.version[["nickname"]], "Unsuffered Consequences") if (devel) prefix <- paste(prefix, R.version[["svn rev"]], sep = "-r") # build list of path components components <- c(prefix, R.version$platform) # include prefix if provided by user prefix <- renv_bootstrap_platform_prefix_impl() if (!is.na(prefix) && nzchar(prefix)) components <- c(prefix, components) # build prefix paste(components, collapse = "/") } renv_bootstrap_platform_prefix_impl <- function() { # if an explicit prefix has been supplied, use it prefix <- Sys.getenv("RENV_PATHS_PREFIX", unset = NA) if (!is.na(prefix)) return(prefix) # if the user has requested an automatic prefix, generate it auto <- Sys.getenv("RENV_PATHS_PREFIX_AUTO", unset = NA) if (is.na(auto) && getRversion() >= "4.4.0") auto <- "TRUE" if (auto %in% c("TRUE", "True", "true", "1")) return(renv_bootstrap_platform_prefix_auto()) # empty string on failure "" } renv_bootstrap_platform_prefix_auto <- function() { prefix <- tryCatch(renv_bootstrap_platform_os(), error = identity) if (inherits(prefix, "error") || prefix %in% "unknown") { msg <- paste( "failed to infer current operating system", "please file a bug report at https://github.com/rstudio/renv/issues", sep = "; " ) warning(msg) } prefix } renv_bootstrap_platform_os <- function() { sysinfo <- Sys.info() sysname <- sysinfo[["sysname"]] # handle Windows + macOS up front if (sysname == "Windows") return("windows") else if (sysname == "Darwin") return("macos") # check for os-release files for (file in c("/etc/os-release", "/usr/lib/os-release")) if (file.exists(file)) return(renv_bootstrap_platform_os_via_os_release(file, sysinfo)) # check for redhat-release files if (file.exists("/etc/redhat-release")) return(renv_bootstrap_platform_os_via_redhat_release()) "unknown" } renv_bootstrap_platform_os_via_os_release <- function(file, sysinfo) { # read /etc/os-release release <- utils::read.table( file = file, sep = "=", quote = c("\"", "'"), col.names = c("Key", "Value"), comment.char = "#", stringsAsFactors = FALSE ) vars <- as.list(release$Value) names(vars) <- release$Key # get os name os <- tolower(sysinfo[["sysname"]]) # read id id <- "unknown" for (field in c("ID", "ID_LIKE")) { if (field %in% names(vars) && nzchar(vars[[field]])) { id <- vars[[field]] break } } # read version version <- "unknown" for (field in c("UBUNTU_CODENAME", "VERSION_CODENAME", "VERSION_ID", "BUILD_ID")) { if (field %in% names(vars) && nzchar(vars[[field]])) { version <- vars[[field]] break } } # join together paste(c(os, id, version), collapse = "-") } renv_bootstrap_platform_os_via_redhat_release <- function() { # read /etc/redhat-release contents <- readLines("/etc/redhat-release", warn = FALSE) # infer id id <- if (grepl("centos", contents, ignore.case = TRUE)) "centos" else if (grepl("redhat", contents, ignore.case = TRUE)) "redhat" else "unknown" # try to find a version component (very hacky) version <- "unknown" parts <- strsplit(contents, "[[:space:]]")[[1L]] for (part in parts) { nv <- tryCatch(numeric_version(part), error = identity) if (inherits(nv, "error")) next version <- nv[1, 1] break } paste(c("linux", id, version), collapse = "-") } renv_bootstrap_library_root_name <- function(project) { # use project name as-is if requested asis <- Sys.getenv("RENV_PATHS_LIBRARY_ROOT_ASIS", unset = "FALSE") if (asis) return(basename(project)) # otherwise, disambiguate based on project's path id <- substring(renv_bootstrap_hash_text(project), 1L, 8L) paste(basename(project), id, sep = "-") } renv_bootstrap_library_root <- function(project) { prefix <- renv_bootstrap_profile_prefix() path <- Sys.getenv("RENV_PATHS_LIBRARY", unset = NA) if (!is.na(path)) return(paste(c(path, prefix), collapse = "/")) path <- renv_bootstrap_library_root_impl(project) if (!is.null(path)) { name <- renv_bootstrap_library_root_name(project) return(paste(c(path, prefix, name), collapse = "/")) } renv_bootstrap_paths_renv("library", project = project) } renv_bootstrap_library_root_impl <- function(project) { root <- Sys.getenv("RENV_PATHS_LIBRARY_ROOT", unset = NA) if (!is.na(root)) return(root) type <- renv_bootstrap_project_type(project) if (identical(type, "package")) { userdir <- renv_bootstrap_user_dir() return(file.path(userdir, "library")) } } renv_bootstrap_validate_version <- function(version, description = NULL) { # resolve description file # # avoid passing lib.loc to `packageDescription()` below, since R will # use the loaded version of the package by default anyhow. note that # this function should only be called after 'renv' is loaded # https://github.com/rstudio/renv/issues/1625 description <- description %||% packageDescription("renv") # check whether requested version 'version' matches loaded version of renv sha <- attr(version, "sha", exact = TRUE) valid <- if (!is.null(sha)) renv_bootstrap_validate_version_dev(sha, description) else renv_bootstrap_validate_version_release(version, description) if (valid) return(TRUE) # the loaded version of renv doesn't match the requested version; # give the user instructions on how to proceed dev <- identical(description[["RemoteType"]], "github") remote <- if (dev) paste("rstudio/renv", description[["RemoteSha"]], sep = "@") else paste("renv", description[["Version"]], sep = "@") # display both loaded version + sha if available friendly <- renv_bootstrap_version_friendly( version = description[["Version"]], sha = if (dev) description[["RemoteSha"]] ) fmt <- heredoc(" renv %1$s was loaded from project library, but this project is configured to use renv %2$s. - Use `renv::record(\"%3$s\")` to record renv %1$s in the lockfile. - Use `renv::restore(packages = \"renv\")` to install renv %2$s into the project library. ") catf(fmt, friendly, renv_bootstrap_version_friendly(version), remote) FALSE } renv_bootstrap_validate_version_dev <- function(version, description) { expected <- description[["RemoteSha"]] if (!is.character(expected)) return(FALSE) pattern <- sprintf("^\\Q%s\\E", version) grepl(pattern, expected, perl = TRUE) } renv_bootstrap_validate_version_release <- function(version, description) { expected <- description[["Version"]] is.character(expected) && identical(expected, version) } renv_bootstrap_hash_text <- function(text) { hashfile <- tempfile("renv-hash-") on.exit(unlink(hashfile), add = TRUE) writeLines(text, con = hashfile) tools::md5sum(hashfile) } renv_bootstrap_load <- function(project, libpath, version) { # try to load renv from the project library if (!requireNamespace("renv", lib.loc = libpath, quietly = TRUE)) return(FALSE) # warn if the version of renv loaded does not match renv_bootstrap_validate_version(version) # execute renv load hooks, if any hooks <- getHook("renv::autoload") for (hook in hooks) if (is.function(hook)) tryCatch(hook(), error = warnify) # load the project renv::load(project) TRUE } renv_bootstrap_profile_load <- function(project) { # if RENV_PROFILE is already set, just use that profile <- Sys.getenv("RENV_PROFILE", unset = NA) if (!is.na(profile) && nzchar(profile)) return(profile) # check for a profile file (nothing to do if it doesn't exist) path <- renv_bootstrap_paths_renv("profile", profile = FALSE, project = project) if (!file.exists(path)) return(NULL) # read the profile, and set it if it exists contents <- readLines(path, warn = FALSE) if (length(contents) == 0L) return(NULL) # set RENV_PROFILE profile <- contents[[1L]] if (!profile %in% c("", "default")) Sys.setenv(RENV_PROFILE = profile) profile } renv_bootstrap_profile_prefix <- function() { profile <- renv_bootstrap_profile_get() if (!is.null(profile)) return(file.path("profiles", profile, "renv")) } renv_bootstrap_profile_get <- function() { profile <- Sys.getenv("RENV_PROFILE", unset = "") renv_bootstrap_profile_normalize(profile) } renv_bootstrap_profile_set <- function(profile) { profile <- renv_bootstrap_profile_normalize(profile) if (is.null(profile)) Sys.unsetenv("RENV_PROFILE") else Sys.setenv(RENV_PROFILE = profile) } renv_bootstrap_profile_normalize <- function(profile) { if (is.null(profile) || profile %in% c("", "default")) return(NULL) profile } renv_bootstrap_path_absolute <- function(path) { substr(path, 1L, 1L) %in% c("~", "/", "\\") || ( substr(path, 1L, 1L) %in% c(letters, LETTERS) && substr(path, 2L, 3L) %in% c(":/", ":\\") ) } renv_bootstrap_paths_renv <- function(..., profile = TRUE, project = NULL) { renv <- Sys.getenv("RENV_PATHS_RENV", unset = "renv") root <- if (renv_bootstrap_path_absolute(renv)) NULL else project prefix <- if (profile) renv_bootstrap_profile_prefix() components <- c(root, renv, prefix, ...) paste(components, collapse = "/") } renv_bootstrap_project_type <- function(path) { descpath <- file.path(path, "DESCRIPTION") if (!file.exists(descpath)) return("unknown") desc <- tryCatch( read.dcf(descpath, all = TRUE), error = identity ) if (inherits(desc, "error")) return("unknown") type <- desc$Type if (!is.null(type)) return(tolower(type)) package <- desc$Package if (!is.null(package)) return("package") "unknown" } renv_bootstrap_user_dir <- function() { dir <- renv_bootstrap_user_dir_impl() path.expand(chartr("\\", "/", dir)) } renv_bootstrap_user_dir_impl <- function() { # use local override if set override <- getOption("renv.userdir.override") if (!is.null(override)) return(override) # use R_user_dir if available tools <- asNamespace("tools") if (is.function(tools$R_user_dir)) return(tools$R_user_dir("renv", "cache")) # try using our own backfill for older versions of R envvars <- c("R_USER_CACHE_DIR", "XDG_CACHE_HOME") for (envvar in envvars) { root <- Sys.getenv(envvar, unset = NA) if (!is.na(root)) return(file.path(root, "R/renv")) } # use platform-specific default fallbacks if (Sys.info()[["sysname"]] == "Windows") file.path(Sys.getenv("LOCALAPPDATA"), "R/cache/R/renv") else if (Sys.info()[["sysname"]] == "Darwin") "~/Library/Caches/org.R-project.R/R/renv" else "~/.cache/R/renv" } renv_bootstrap_version_friendly <- function(version, shafmt = NULL, sha = NULL) { sha <- sha %||% attr(version, "sha", exact = TRUE) parts <- c(version, sprintf(shafmt %||% " [sha: %s]", substring(sha, 1L, 7L))) paste(parts, collapse = "") } renv_bootstrap_exec <- function(project, libpath, version) { if (!renv_bootstrap_load(project, libpath, version)) renv_bootstrap_run(project, libpath, version) } renv_bootstrap_run <- function(project, libpath, version) { # perform bootstrap bootstrap(version, libpath) # exit early if we're just testing bootstrap if (!is.na(Sys.getenv("RENV_BOOTSTRAP_INSTALL_ONLY", unset = NA))) return(TRUE) # try again to load if (requireNamespace("renv", lib.loc = libpath, quietly = TRUE)) { return(renv::load(project = project)) } # failed to download or load renv; warn the user msg <- c( "Failed to find an renv installation: the project will not be loaded.", "Use `renv::activate()` to re-initialize the project." ) warning(paste(msg, collapse = "\n"), call. = FALSE) } renv/R/lock.R0000644000176200001440000000504314731330073012503 0ustar liggesusers the$lock_registry <- new.env(parent = emptyenv()) renv_lock_acquire <- function(path) { # normalize path path <- renv_lock_path(path) dlog("lock", "%s [acquiring lock]", renv_path_pretty(path)) # if we already have this lock, increment our counter count <- the$lock_registry[[path]] %||% 0L if (count > 0L) { the$lock_registry[[path]] <- count + 1L return(TRUE) } # make sure parent directory exists ensure_parent_directory(path) # suppress warnings in this scope renv_scope_options(warn = -1L) # loop until we acquire the lock repeat tryCatch( renv_lock_acquire_impl(path) && break, error = function(cnd) Sys.sleep(0.2) ) # mark this path as locked by us the$lock_registry[[path]] <- 1L # notify the watchdog renv_watchdog_notify("LockAcquired", list(path = path)) # TRUE to mark successful lock dlog("lock", "%s [lock acquired]", renv_path_pretty(path)) TRUE } # https://rcrowley.org/2010/01/06/things-unix-can-do-atomically.html renv_lock_acquire_impl <- function(path) { # check for orphaned locks if (renv_lock_orphaned(path)) { dlog("lock", "%s: removing orphaned lock", path) unlink(path, recursive = TRUE, force = TRUE) } # attempt to create the lock dir.create(path, mode = "0755", showWarnings = FALSE) } renv_lock_release <- function(path) { # normalize path path <- renv_lock_path(path) # decrement our lock count count <- the$lock_registry[[path]] <- the$lock_registry[[path]] - 1L # remove the lock if we have no more locks if (count == 0L) { dlog("lock", "%s [lock released]", renv_path_pretty(path)) renv_lock_release_impl(path) } } renv_lock_release_impl <- function(path) { unlink(path, recursive = TRUE, force = TRUE) remaining <- intersect(path, ls(envir = the$lock_registry, all.names = TRUE)) rm(list = remaining, envir = the$lock_registry) renv_watchdog_notify("LockReleased", list(path = path)) } renv_lock_orphaned <- function(path) { timeout <- getOption("renv.lock.timeout", default = 60L) if (timeout <= 0L) return(TRUE) info <- renv_file_info(path) if (is.na(info$isdir)) return(FALSE) diff <- difftime(Sys.time(), info$mtime, units = "secs") diff >= timeout } renv_lock_refresh <- function(lock) { Sys.setFileTime(lock, Sys.time()) } renv_lock_unload <- function() { locks <- ls(envir = the$lock_registry, all.names = TRUE) unlink(locks, recursive = TRUE, force = TRUE) } renv_lock_path <- function(path) { file.path( renv_path_normalize(dirname(path), mustWork = TRUE), basename(path) ) } renv/R/lockfile-diff.R0000644000176200001440000000720714731330073014255 0ustar liggesusers renv_lockfile_diff <- function(old, new, compare = NULL) { compare <- compare %||% function(lhs, rhs) { list(before = lhs, after = rhs) } # ensure both lists have the same names, inserting missing # entries for those without any value nms <- union(names(old), names(new)) %||% character() if (length(nms)) { nms <- sort(nms) old[renv_vector_diff(nms, names(old))] <- list(NULL) new[renv_vector_diff(nms, names(new))] <- list(NULL) old <- old[nms] new <- new[nms] } # ensure that these have the same length for comparison if (is.list(old) && is.list(new)) length(old) <- length(new) <- max(length(old), length(new)) # check for differences diffs <- mapply( renv_lockfile_diff_impl, old, new, MoreArgs = list(compare = compare), SIMPLIFY = FALSE ) # drop NULL entries reject(diffs, empty) } renv_lockfile_diff_impl <- function(lhs, rhs, compare) { case( is.list(lhs) && empty(rhs) ~ renv_lockfile_diff(lhs, list(), compare), empty(lhs) && is.list(rhs) ~ renv_lockfile_diff(list(), rhs, compare), is.list(lhs) && is.list(rhs) ~ renv_lockfile_diff(lhs, rhs, compare), !identical(c(lhs), c(rhs)) ~ compare(lhs, rhs), NULL ) } renv_lockfile_diff_record <- function(before, after) { before <- renv_record_normalize(before) after <- renv_record_normalize(after) # first, compare on version / record existence type <- case( is.null(before) ~ "install", is.null(after) ~ "remove", before$Version < after$Version ~ "upgrade", before$Version > after$Version ~ "downgrade" ) if (!is.null(type)) return(type) # if we're running this as part of 'load()', and we're comparing # packages with unknown sources, then just ignore those -- this # is because we disable the 'guess repository' hack on startup, # to avoid a potentially expensive query of package repositories # # https://github.com/rstudio/renv/issues/1683 if (the$load_running) { unknown <- identical(before$Source, "unknown") || identical(after$Source, "unknown") if (unknown) return(NULL) } # check for a crossgrade -- where the package version is the same, # but details about the package's remotes have changed if (!setequal(renv_record_names(before), renv_record_names(after))) return("crossgrade") nm <- union(renv_record_names(before), renv_record_names(after)) if (!identical(before[nm], after[nm])) return("crossgrade") NULL } renv_lockfile_diff_packages <- function(old, new) { old <- renv_lockfile_records(old) new <- renv_lockfile_records(new) packages <- named(union(names(old), names(new))) actions <- lapply(packages, function(package) { before <- old[[package]]; after <- new[[package]] renv_lockfile_diff_record(before, after) }) Filter(Negate(is.null), actions) } renv_lockfile_override <- function(lockfile) { records <- renv_lockfile_records(lockfile) overrides <- renv_records_override(records) renv_lockfile_records(lockfile) <- overrides lockfile } renv_lockfile_repair <- function(lockfile) { records <- renv_lockfile_records(lockfile) # fix up records in lockfile renv_lockfile_records(lockfile) <- enumerate(records, function(package, record) { # if this package is from a repository, but doesn't specify an explicit # version, then use the latest-available version of that package source <- renv_record_source_normalize(record, record$Source) if (identical(source, "Repository") && is.null(record$Version)) { entry <- renv_available_packages_latest(package) record$Version <- entry$Version } # return normalized record record }) lockfile } renv/R/once.R0000644000176200001440000000037614731330073012503 0ustar liggesusers # mechanism for running a block of code only once the$once <- new.env(parent = emptyenv()) once <- function() { call <- sys.call(sys.parent())[[1L]] id <- as.character(call) once <- the$once[[id]] %||% TRUE the$once[[id]] <- FALSE once } renv/R/binding.R0000644000176200001440000000132214731330072013160 0ustar liggesusers renv_binding_lock <- function(envir, symbol) { .BaseNamespaceEnv$lockBinding(symbol, envir) } renv_binding_locked <- function(envir, symbol) { .BaseNamespaceEnv$bindingIsLocked(symbol, envir) } renv_binding_unlock <- function(envir, symbol) { .BaseNamespaceEnv$unlockBinding(symbol, envir) } renv_binding_replace <- function(envir, symbol, replacement) { # get the original definition original <- envir[[symbol]] # if the binding is locked, temporarily unlock it if (renv_binding_locked(envir, symbol)) { defer(renv_binding_lock(envir, symbol)) renv_binding_unlock(envir, symbol) } # update the binding assign(symbol, replacement, envir = envir) # return old definition original } renv/R/verbose.R0000644000176200001440000000047414731330073013223 0ustar liggesusers renv_verbose <- function() { verbose <- getOption("renv.verbose") if (!is.null(verbose)) return(as.logical(verbose)) verbose <- Sys.getenv("RENV_VERBOSE", unset = NA) if (!is.na(verbose)) return(as.logical(verbose)) if (testing()) return(FALSE) interactive() || !renv_tests_running() } renv/R/check.R0000644000176200001440000000152014731330072012623 0ustar liggesusers renv_check_unknown_source <- function(records, project = NULL) { # nothing to do if we have no records if (empty(records)) return(TRUE) # for testing, we ignore renv if (renv_tests_running()) records$renv <- NULL # keep only records which have unknown source unknown <- filter(records, function(record) { source <- renv_record_source(record) if (source != "unknown") return(FALSE) localpath <- tryCatch( renv_retrieve_cellar_find(record, project), error = function(e) "" ) if (file.exists(localpath)) return(FALSE) TRUE }) # if all records have a known source, return TRUE if (empty(unknown)) return(TRUE) # provide warning if (!renv_tests_running()) renv_warnings_unknown_sources(unknown) # return FALSE to indicate failed validation FALSE } renv/R/repair.R0000644000176200001440000001130014761163114013031 0ustar liggesusers #' Repair a project #' #' Use `repair()` to recover from some common issues that can occur with #' a project. Currently, two operations are performed: #' #' 1. Packages with broken symlinks into the cache will be re-installed. #' #' 2. Packages that were installed from sources, but appear to be from #' an remote source (e.g. GitHub), will have their `DESCRIPTION` files #' updated to record that remote source explicitly. #' #' @inheritParams renv-params #' #' @param lockfile The path to a lockfile (if any). When available, renv #' will use the lockfile when attempting to infer the remote associated #' with the inaccessible version of each missing package. When `NULL` #' (the default), the project lockfile will be used. #' #' @export repair <- function(library = NULL, lockfile = NULL, project = NULL) { renv_consent_check() renv_scope_error_handler() project <- renv_project_resolve(project) renv_project_lock(project = project) libpaths <- renv_path_normalize(library %||% renv_libpaths_all()) library <- libpaths[[1L]] writef(header("Library cache links")) renv_repair_links(library, lockfile, project) writef() writef(header("Package sources")) renv_repair_sources(library, lockfile, project) writef() invisible() } renv_repair_links <- function(library, lockfile, project) { # figure out which library paths (junction points?) appear to be broken paths <- list.files(library, full.names = TRUE) broken <- renv_file_broken(paths) packages <- basename(paths[broken]) if (empty(packages)) { writef("- No issues found with the project library's cache links.") return(invisible(packages)) } # try to find records for these packages in the lockfile # TODO: what if one of the requested packages isn't in the lockfile? lockfile <- lockfile %||% renv_lockfile_load(project = project) records <- renv_repair_records(packages, lockfile, project) # install these records install( packages = records, library = library, project = project ) } renv_repair_records <- function(packages, lockfile, project) { map(packages, function(package) { lockfile$Packages[[package]] %||% package }) } renv_repair_sources <- function(library, lockfile, project) { # get package description files db <- installed_packages(lib.loc = library, priority = NA_character_) descpaths <- with(db, file.path(LibPath, Package, "DESCRIPTION")) dcfs <- map(descpaths, renv_description_read) names(dcfs) <- map_chr(dcfs, `[[`, "Package") # try to infer sources as necessary inferred <- map(dcfs, renv_repair_sources_infer) inferred <- filter(inferred, Negate(is.null)) if (length(inferred) == 0L) { writef("- All installed packages appear to be from a known source.") return(TRUE) } # ask used renv_scope_options(renv.verbose = TRUE) bulletin( c( "The following package(s) do not have an explicitly-declared remote source.", "However, renv was available to infer remote sources from their DESCRIPTION file." ), sprintf("%s [%s]", format(names(inferred)), inferred), "`renv::restore()` may fail for packages without an explicitly-declared remote source." ) choice <- menu( choices = c( update = "Let renv infer the remote sources for these packages.", cancel = "Do nothing and resolve the situation another way." ), title = "What would you like to do?" ) cancel_if(identical(choice, "cancel")) enumerate(inferred, function(package, remote) { record <- renv_remotes_resolve(remote) record[["RemoteSha"]] <- NULL renv_package_augment(file.path(library, package), record) }) n <- length(inferred) writef("- Updated %i package DESCRIPTION %s.", n, nplural("file", n)) TRUE } renv_repair_sources_infer <- function(dcf) { # if this package appears to have a declared remote, use as-is for (field in c("RemoteType", "Repository", "biocViews")) if (!is.null(dcf[[field]])) return(NULL) # ok, this is a package installed from sources that "looks" like # the development version of a package; try to guess its remote guess <- function(pattern, field) { urls <- strsplit(dcf[[field]] %||% "", "\\s*,\\s*")[[1L]] for (url in urls) { matches <- regmatches(url, regexec(pattern, url, perl = TRUE))[[1L]] if (length(matches) == 3L) return(paste(matches[[2L]], matches[[3L]], sep = "/")) } } # first, check bug reports remote <- guess("^https://(?:www\\.)?github\\.com/([^/]+)/([^/]+)/issues$", "BugReports") if (!is.null(remote)) return(remote) # next, check the URL field remote <- guess("^https://(?:www\\.)?github\\.com/([^/]+)/([^/]+)", "URL") if (!is.null(remote)) return(remote) } renv/R/l10n.R0000644000176200001440000000032614731330073012324 0ustar liggesusers renv_l10n_mbcs <- function() { info <- l10n_info() info$MBCS } renv_l10n_utf8 <- function() { info <- l10n_info() info$`UTF-8` } renv_l10n_latin1 <- function() { info <- l10n_info() info$`Latin-1` } renv/R/watchdog-server.R0000644000176200001440000000612614731330073014662 0ustar liggesusers renv_watchdog_server_start <- function(client) { # silence warnings renv_scope_options(warn = -1L) # initialize logging renv_log_init() # create socket server server <- renv_socket_server() dlog("watchdog-server", "Listening on port %i.", server$port) # communicate information back to client dlog("watchdog-server", "Waiting for client...") metadata <- list(port = server$port, pid = server$pid) conn <- renv_socket_connect(port = client$port, open = "wb") defer(close(conn)) serialize(metadata, connection = conn) dlog("watchdog-server", "Synchronized with client.") # initialize locks lockenv <- new.env(parent = emptyenv()) # start listening for connections repeat tryCatch( renv_watchdog_server_run(server, client, lockenv), error = function(e) { dlog("watchdog-server", "Error: %s", conditionMessage(e)) } ) } renv_watchdog_server_run <- function(server, client, lockenv) { # check for parent exit if (!renv_process_exists(client$pid)) { dlog("watchdog-server", "Client process has exited; shutting down.") renv_watchdog_server_exit(server, client, lockenv) } # set file time on owned locks, so we can see they're not orphaned dlog("watchdog-server", "Refreshing lock times.") locks <- ls(envir = lockenv, all.names = TRUE) renv_lock_refresh(locks) # wait for connection dlog("watchdog-server", "Waiting for connection...") conn <- renv_socket_accept(server$socket, open = "rb", timeout = 1) defer(close(conn)) # read the request dlog("watchdog-server", "Received connection; reading data.") request <- unserialize(conn) dlog("watchdog-server", "Received request.") str(request) # handle the request renv_watchdog_server_run_impl(server, client, lockenv, request) } renv_watchdog_server_run_impl <- function(server, client, lockenv, request) { switch( request$method %||% "", ListLocks = { dlog("watchdog-server", "Executing 'ListLocks' request.") conn <- renv_socket_connect(port = request$port, open = "wb") defer(close(conn)) locks <- ls(envir = lockenv, all.names = TRUE) serialize(locks, connection = conn) }, LockAcquired = { dlog("watchdog-server", "Acquired lock on path '%s'.", request$data$path) assign(request$data$path, TRUE, envir = lockenv) }, LockReleased = { dlog("watchdog-server", "Released lock on path '%s'.", request$data$path) rm(list = request$data$path, envir = lockenv) }, Shutdown = { dlog("watchdog-server", "Received shutdown request; shutting down.") renv_watchdog_server_exit(server, client, lockenv) }, "" = { dlog("watchdog-server", "Received request with no method field available.") }, { dlog("watchdog-server", "Unknown method '%s'", request$method) } ) } renv_watchdog_server_exit <- function(server, client, lockenv) { # remove any existing locks locks <- ls(envir = lockenv, all.names = TRUE) unlink(locks, recursive = TRUE, force = TRUE) # shut down the socket server close(server$socket) # quit quit(status = 0) } renv/R/cran.R0000644000176200001440000000166414731330072012502 0ustar liggesusers # nocov start renv_cran_status <- function(email = NULL, package = NULL, view = "maintainer") { case( view == "maintainer" ~ renv_cran_status_maintainer(email, package), TRUE ~ stopf("unrecognized view '%s'", view) ) } renv_cran_status_maintainer <- function(email, package) { email <- email %||% renv_cran_status_maintainer_email(package = package) parts <- strsplit(email, "@", fixed = TRUE)[[1L]] fmt <- "https://cran.r-project.org/web/checks/check_results_%s_at_%s.html" url <- sprintf(fmt, parts[[1L]], parts[[2L]]) browseURL(url) } renv_cran_status_maintainer_email <- function(package = NULL) { mtr <- renv_package_description_field( package = package %||% "renv", field = "Maintainer" ) indices <- gregexpr("[<>]", mtr, perl = TRUE)[[1L]] substring(mtr, indices[[1L]] + 1L, indices[[2L]] - 1L) } # nocov end renv/R/package.R0000644000176200001440000003504114742235554013161 0ustar liggesusers # NOTE: When lib.loc is NULL, renv will also check to see if a package matching # the provided name is currently loaded. This function will also intentionally # check the library paths before checking loaded namespaces. # This differs from the behavior of `find.package()`. renv_package_find <- function(package, lib.loc = NULL) { map_chr(package, renv_package_find_impl, lib.loc = lib.loc) } renv_package_find_impl <- function(package, lib.loc = NULL) { # if we've been given the path to an existing package, use it as-is if (grepl("/", package) && file.exists(file.path(package, "DESCRIPTION"))) return(renv_path_normalize(package, mustWork = TRUE)) # first, look in the library paths for (libpath in (lib.loc %||% .libPaths())) { pkgpath <- file.path(libpath, package) descpath <- file.path(pkgpath, "DESCRIPTION") if (file.exists(descpath)) return(pkgpath) } # if that failed, check to see if it's loaded and use the associated path if (is.null(lib.loc) && package %in% loadedNamespaces()) { path <- renv_namespace_path(package) if (file.exists(path)) return(path) } # failed to find package "" } renv_package_installed <- function(package, lib.loc = NULL) { paths <- renv_package_find(package, lib.loc = lib.loc %||% renv_libpaths_all()) nzchar(paths) } renv_package_available <- function(package) { package %in% loadedNamespaces() || renv_package_installed(package) } renv_package_version <- function(package) { renv_package_description_field(package, "Version") } renv_package_description_field <- function(package, field) { path <- renv_package_find(package) if (!nzchar(path)) return(NULL) desc <- renv_description_read(path) desc[[field]] } renv_package_type <- function(path, quiet = FALSE, default = "source") { info <- renv_file_info(path) if (is.na(info$isdir)) stopf("no package at path '%s'", renv_path_aliased(path)) # for directories, check for Meta if (info$isdir) { hasmeta <- file.exists(file.path(path, "Meta")) type <- if (hasmeta) "binary" else "source" return(type) } # otherwise, guess based on contents of package methods <- list( tar = function(path) untar(tarfile = path, list = TRUE), zip = function(path) unzip(zipfile = path, list = TRUE)$Name ) # guess appropriate method when possible type <- renv_archive_type(path) if (type %in% c("tar", "zip")) methods <- methods[type] for (method in methods) { # suppress warnings to avoid issues with e.g. # 'skipping pax global extended headers' when # using internal tar files <- catch(suppressWarnings(method(path))) if (inherits(files, "error")) next hasmeta <- any(grepl("^[^/]+/Meta/?$", files)) type <- if (hasmeta) "binary" else "source" return(type) } if (!quiet) { fmt <- "failed to determine type of package '%s'; assuming source" warningf(fmt, renv_path_aliased(path)) } default } renv_package_priority <- function(package) { # treat 'R' as pseudo base package if (package == "R") return("base") # read priority from db db <- installed_packages() entry <- db[db$Package == package, ] entry$Priority %NA% "" } renv_package_tarball_name <- function(path) { desc <- renv_description_read(path) with(desc, sprintf("%s_%s.tar.gz", Package, Version)) } renv_package_ext <- function(type) { # always use '.tar.gz' for source packages type <- match.arg(type, c("binary", "source")) if (type == "source") return(".tar.gz") # otherwise, infer appropriate extension based on platform case( renv_platform_macos() ~ ".tgz", renv_platform_windows() ~ ".zip", renv_platform_unix() ~ ".tar.gz" ) } renv_package_pkgtypes <- function() { # only use binaries if the user has specifically requested it # and binaries are available for this installation of R # (users may want to install from sources explicitly to take # advantage of custom local compiler configurations) binaries <- !identical(.Platform$pkgType, "source") && !identical(getOption("pkgType"), "source") if (binaries) c("binary", "source") else "source" } renv_package_augment_standard <- function(path, record) { # check whether we tagged a url + type for this package url <- attr(record, "url", exact = TRUE) type <- attr(record, "type", exact = TRUE) name <- attr(record, "name", exact = TRUE) if (is.null(url) || is.null(type)) return(record) # skip if this isn't a repository remote if (!identical(record$Source, "Repository")) return(record) # skip if the DESCRIPTION file already has Remote fields # (mainly relevant for r-universe) descpath <- file.path(path, "DESCRIPTION") desc <- renv_description_read(descpath) if (any(grepl("^Remote", names(desc)))) return(record) # figure out base of repository URL pattern <- "/(?:bin|src)/" index <- regexpr(pattern, url, perl = TRUE) repos <- substring(url, 1L, index - 1L) # figure out the platform platform <- if (identical(type, "binary")) R.version$platform else "source" # build pak-compatible standard remote reference remotes <- list( RemoteType = "standard", RemotePkgRef = record$Package, RemoteRef = record$Package, RemoteRepos = repos, RemoteReposName = name, RemotePkgPlatform = platform, RemoteSha = record$Version ) overlay(record, remotes) } renv_package_augment <- function(installpath, record) { # figure out if we should write this as a 'standard' remote record <- renv_package_augment_standard(installpath, record) # check for remotes fields remotes <- record[grep("^Remote", names(record))] if (empty(remotes)) return(FALSE) # for backwards compatibility with older versions of Packrat, # we write out 'Github*' fields as well if (identical(record$Source, "GitHub")) { map <- list( "GithubHost" = "RemoteHost", "GithubRepo" = "RemoteRepo", "GithubUsername" = "RemoteUsername", "GithubRef" = "RemoteRef", "GithubSHA1" = "RemoteSha" ) enumerate(map, function(old, new) { remotes[[old]] <<- remotes[[old]] %||% remotes[[new]] }) } # ensure RemoteType field is written out remotes$RemoteType <- remotes$RemoteType %||% renv_record_source(record) remotes <- remotes[c("RemoteType", renv_vector_diff(names(remotes), "RemoteType"))] # update package items renv_package_augment_description(installpath, remotes) renv_package_augment_metadata(installpath, remotes) } renv_package_augment_impl <- function(data, remotes) { remotes <- remotes[map_lgl(remotes, Negate(is.null))] nonremotes <- grep("^(?:Remote|Github)", names(data), invert = TRUE) remotes[["Remotes"]] <- data[["Remotes"]] %||% remotes[["Remotes"]] c(data[nonremotes], remotes) } renv_package_augment_description <- function(path, remotes) { descpath <- file.path(path, "DESCRIPTION") before <- renv_description_read(descpath) after <- renv_package_augment_impl(before, remotes) if (identical(before, after)) return(FALSE) renv_dcf_write(after, file = descpath) } renv_package_augment_metadata <- function(path, remotes) { metapath <- file.path(path, "Meta/package.rds") if (!file.exists(metapath)) return(FALSE) meta <- readRDS(metapath) before <- as.list(meta$DESCRIPTION) after <- renv_package_augment_impl(before, remotes) if (identical(before, after)) return(FALSE) meta$DESCRIPTION <- map_chr(after, identity) saveRDS(meta, file = metapath, version = 2L) } # find recursive dependencies of a package. note that this routine # doesn't farm out to CRAN; it relies on the package and its dependencies # all being installed locally. returns a named vector mapping package names # to the path where they were discovered, or NA if those packages are not # installed renv_package_dependencies <- function(packages, libpaths = NULL, fields = NULL, callback = NULL, project = NULL) { visited <- new.env(parent = emptyenv()) ignored <- renv_project_ignored_packages(project = project) packages <- renv_vector_diff(packages, ignored) libpaths <- libpaths %||% renv_libpaths_all() fields <- fields %||% settings$package.dependency.fields(project = project) callback <- callback %||% function(package, location, project) location project <- renv_project_resolve(project) for (package in packages) renv_package_dependencies_impl(package, visited, libpaths, fields, callback, project) as.list(visited) } renv_package_dependencies_impl <- function(package, visited, libpaths, fields = NULL, callback = NULL, project = NULL) { # skip the 'R' package if (package == "R") return() # if we've already visited this package, bail if (!is.null(visited[[package]])) return() # default to unknown path for visited packages visited[[package]] <- "" # find the package -- note that we perform a permissive lookup here # because we want to capture potentially invalid / broken package installs # (that is, the 'package' we find might be an incomplete or broken package # installation at this point) location <- find(libpaths, function(libpath) { candidate <- file.path(libpath, package) if (renv_file_exists(candidate)) return(candidate) }) if (is.null(location)) return(callback(package, "", project)) # we know the path, so set it now visited[[package]] <- callback(package, location, project) # find its dependencies from the DESCRIPTION file deps <- renv_dependencies_discover_description(location, fields = "strong") subpackages <- deps$Package for (subpackage in subpackages) renv_package_dependencies_impl(subpackage, visited, libpaths, fields, callback, project) } renv_package_reload <- function(package, library = NULL) { status <- catch(renv_package_reload_impl(package, library)) !inherits(status, "error") && status } renv_package_reload_impl <- function(package, library) { if (renv_tests_running()) return(FALSE) # record if package is attached (and, if so, where) name <- paste("package", package, sep = ":") pos <- match(name, search()) # unload the package if (!is.na(pos)) renv_package_reload_impl_searchpath(package, library, pos) else renv_package_reload_impl_namespace(package, library) TRUE } renv_package_reload_impl_searchpath <- function(package, library, pos) { args <- list(pos = pos, unload = TRUE, force = TRUE) quietly(do.call(base::detach, args), sink = FALSE) args <- list(package = package, pos = pos, lib.loc = library, quietly = TRUE) quietly(do.call(base::library, args), sink = FALSE) } renv_package_reload_impl_namespace <- function(package, library) { unloadNamespace(package) loadNamespace(package, lib.loc = library) } renv_package_hook <- function(package, hook) { if (package %in% loadedNamespaces()) hook() else setHook(packageEvent(package, "onLoad"), hook) } renv_package_metadata <- function(package) { pkgpath <- renv_package_find(package) metapath <- file.path(pkgpath, "Meta/package.rds") readRDS(metapath) } renv_package_shlib <- function(package) { pkgpath <- renv_package_find(package) pkgname <- basename(package) if (pkgname == "data.table") pkgname <- "datatable" libname <- paste0(pkgname, .Platform$dynlib.ext) file.path(pkgpath, "libs", libname) } renv_package_built <- function(path) { info <- renv_file_info(path) # list files in package isarchive <- identical(info$isdir, FALSE) files <- if (isarchive) renv_archive_list(path) else list.files(path, full.names = TRUE, recursive = TRUE) # for a source package, the canonical way to determine if it has already # been built is the presence of a 'Packaged:' field in the DESCRIPTION file # ('Built:' for binary packages) but we want to avoid the overhead of # unpacking the package if at all possible pattern <- "/(?:MD5$|INDEX/|Meta/package\\.rds$)" matches <- grep(pattern, files) if (length(matches) != 0L) return(TRUE) # if the above failed, then we'll use the contents of the DESCRIPTION file descpaths <- grep("/DESCRIPTION$", files, value = TRUE) if (length(descpaths) == 0L) return(FALSE) n <- nchar(descpaths) descpath <- descpaths[n == min(n)] contents <- if (isarchive) renv_archive_read(path, descpath) else readLines(descpath, warn = FALSE) # check for signs it was built pattern <- "^(?:Packaged|Built):" matches <- grep(pattern, contents) if (length(matches) != 0L) return(TRUE) # does not appear to be a source package FALSE } renv_package_unpack <- function(package, path, subdir = "", force = FALSE) { # if this isn't an archive, nothing to do info <- renv_file_info(path) if (identical(info$isdir, TRUE)) return(path) # find DESCRIPTION files in the archive descpaths <- renv_archive_find(path, "(?:^|/)DESCRIPTION$") # try to resolve the path to the DESCRIPTION file in the archive descpath <- if (nzchar(subdir)) { pattern <- sprintf("(?:^|/)\\Q%s\\E/DESCRIPTION$", subdir) grep(pattern, descpaths, perl = TRUE, value = TRUE) } else { n <- nchar(descpaths) descpaths[n == min(n)] } # if this failed, check for a top-level DESCRIPTION file # this is done in case the archive has been already been re-packed, so that a # package originally located within a sub-directory is now at the top level if (!force && length(descpath) != 1L) { descpath <- grep("^[^/]+/DESCRIPTION$", descpaths, perl = TRUE, value = TRUE) if (length(descpath)) return(path) } # if this still failed, error if (length(descpath) != 1L) { fmt <- "internal error: couldn't find DESCRIPTION file for package '%s' in archive '%s'" stopf(fmt, package, path) } # create extraction directory old <- renv_scope_tempfile("renv-package-old-") new <- renv_scope_tempfile("renv-package-new-", scope = parent.frame()) ensure_directory(c(old, new)) # decompress archive to dir renv_archive_decompress(path, exdir = old) # rename (without sub-directory) oldpath <- file.path(old, dirname(descpath)) newpath <- file.path(new, package) file.rename(oldpath, newpath) # use newpath newpath } renv_package_libsdir <- function(package, arch = NULL) { package <- renv_package_find(package) arch <- arch %||% if (nzchar(.Platform$r_arch)) .Platform$r_arch paste(c(package, "libs", arch), collapse = "/") } renv/R/acls.R0000644000176200001440000000123514731330072012473 0ustar liggesusers renv_acls_reset <- function(source, target = dirname(source)) { # only run on Linux for now if (!renv_platform_linux()) return(FALSE) # skip if we don't have 'getfacl', 'setfacl' getfacl <- Sys.which("getfacl"); setfacl <- Sys.which("setfacl") if (!nzchar(getfacl) || !nzchar(setfacl)) return(FALSE) # build command fmt <- "getfacl %s 2> /dev/null | setfacl -R --set-file=- %s 2> /dev/null" cmd <- sprintf(fmt, renv_shell_path(target), renv_shell_path(source)) # execute it # TODO: Should we report errors? If so, how? catch( renv_system_exec( command = cmd, action = "resetting ACLs", quiet = TRUE ) ) } renv/R/use.R0000644000176200001440000001041114746263221012350 0ustar liggesusers the$use_libpath <- NULL #' @rdname embed #' #' @param ... #' The \R packages to be used with this script. Ignored if `lockfile` is #' non-`NULL`. #' #' @param lockfile #' The lockfile to use. When supplied, renv will use the packages as #' declared in the lockfile. #' #' @param library #' The library path into which the requested packages should be installed. #' When `NULL` (the default), a library path within the \R temporary #' directory will be generated and used. Note that this same library path #' will be re-used on future calls to `renv::use()`, allowing `renv::use()` #' to be used multiple times within a single script. #' #' @param isolate #' Boolean; should the active library paths be included in the set of library #' paths activated for this script? Set this to `TRUE` if you only want the #' packages provided to `renv::use()` to be visible on the library paths. #' #' @param sandbox #' Should the system library be sandboxed? See the sandbox documentation in #' [renv::config] for more details. You can also provide an explicit sandbox #' path if you want to configure where `renv::use()` generates its sandbox. #' By default, the sandbox is generated within the \R temporary directory. #' #' @param attach #' Boolean; should the set of requested packages be automatically attached? #' If `TRUE`, packages will be loaded and attached via a call #' to [library()] after install. Ignored if `lockfile` is non-`NULL`. #' #' @param verbose #' Boolean; be verbose while installing packages? #' #' @return #' This function is normally called for its side effects. #' #' @export use <- function(..., lockfile = NULL, library = NULL, isolate = TRUE, sandbox = TRUE, attach = FALSE, verbose = TRUE) { # allow use of the cache in this context renv_scope_options(renv.cache.linkable = TRUE) # set up sandbox if requested renv_use_sandbox(sandbox) # prepare library and activate library library <- library %||% renv_use_libpath() ensure_directory(library) # set library paths libpaths <- c(library, if (!isolate) .libPaths()) renv_libpaths_set(libpaths) # if we were supplied a lockfile, use it if (!is.null(lockfile)) { renv_scope_options(renv.verbose = verbose) records <- restore(lockfile = lockfile, clean = FALSE, prompt = FALSE) return(invisible(records)) } dots <- list(...) if (empty(dots)) return(invisible()) # resolve the provided remotes records <- lapply(dots, renv_remotes_resolve, latest = TRUE) names(records) <- map_chr(records, `[[`, "Package") # remove any remotes which already appear to be installed compat <- enum_lgl(records, function(package, record) { # check if the package is installed if (!renv_package_installed(package, lib.loc = library)) return(FALSE) # check if the installed package is compatible record <- resolve(record) current <- renv_snapshot_description(package = package) diff <- renv_lockfile_diff_record(record, current) # a null diff implies the two records are compatible is.null(diff) }) # drop the already-installed compatible records records <- records[!compat] if (empty(records)) return(invisible()) # install packages records <- local({ renv_scope_options(renv.verbose = verbose) install(packages = records, library = library, rebuild = character(), prompt = FALSE) }) # automatically load the requested remotes if (attach) { enumerate(records, function(package, remote) { library(package, character.only = TRUE) }) } # return set of installed packages invisible(records) } renv_use_libpath <- function() { (the$use_libpath <- the$use_libpath %||% tempfile("renv-use-libpath-")) } renv_use_sandbox <- function(sandbox) { if (identical(sandbox, FALSE)) return(FALSE) if (renv_sandbox_activated()) return(TRUE) sandbox <- if (is.character(sandbox)) sandbox else file.path(tempdir(), "renv-sandbox") renv_scope_options(renv.config.sandbox.enabled = TRUE) renv_sandbox_activate_impl(sandbox = sandbox) reg.finalizer(renv_envir_self(), function(envir) { tryCatch( renv_sandbox_unlock(sandbox), condition = identity ) }, onexit = TRUE) } renv/R/shims.R0000644000176200001440000000515214731330073012677 0ustar liggesusers the$shims <- new.env(parent = emptyenv()) # determine whether we can safely handle a call to install.packages() renv_shim_install_packages_compatible <- function(matched) { # check if the user has only specified arguments which we know how to handle ok <- c("", "dependencies", "pkgs", "lib", "repos", "type") unhandled <- setdiff(names(matched), ok) if (length(unhandled) != 0L) return(FALSE) # if 'repos' is explicitly NULL, assume this is a request for local install if ("repos" %in% names(matched) && is.null(matched[["repos"]])) return(FALSE) # ok, we can handle it TRUE } renv_shim_install_packages <- function(pkgs, ...) { # place Rtools on PATH renv_scope_rtools() # check for compatible calls matched <- match.call(utils::install.packages) if (!renv_shim_install_packages_compatible(matched)) { call <- sys.call() call[[1L]] <- quote(utils::install.packages) return(eval(call, envir = parent.frame())) } # otherwise, invoke our own installer call <- sys.call() call[[1L]] <- quote(renv::install) # fix up names aliases <- list(lib = "library") idx <- omit_if(match(names(aliases), names(call)), is.na) names(call)[idx] <- aliases[idx] # evaluate call eval(call, envir = parent.frame()) } renv_shim_update_packages <- function(lib.loc = NULL, ...) { # handle only 0-argument case if (nargs() != 0) { call <- sys.call() call[[1L]] <- quote(utils::update.packages) return(eval(call, envir = parent.frame())) } update(library = lib.loc) } renv_shim_remove_packages <- function(pkgs, lib) { # handle single-argument case if (nargs() != 1) { call <- sys.call() call[[1L]] <- quote(utils::remove.packages) return(eval(call, envir = parent.frame())) } remove(pkgs) } renv_shim_create <- function(shim, sham) { formals(shim) <- formals(sham) shim } renv_shims_enabled <- function(project) { config$shims.enabled() } renv_shims_activate <- function() { renv_shims_deactivate() install_shim <- renv_shim_create(renv_shim_install_packages, utils::install.packages) assign("install.packages", install_shim, envir = the$shims) update_shim <- renv_shim_create(renv_shim_update_packages, utils::update.packages) assign("update.packages", update_shim, envir = the$shims) remove_shim <- renv_shim_create(renv_shim_remove_packages, utils::remove.packages) assign("remove.packages", remove_shim, envir = the$shims) args <- list(the$shims, name = "renv:shims", warn.conflicts = FALSE) do.call(base::attach, args) } renv_shims_deactivate <- function() { while ("renv:shims" %in% search()) detach("renv:shims") } renv/R/mask.R0000644000176200001440000000312114753207671012514 0ustar liggesusers # functions which mask internal / base R equivalents, usually to provide # backwards compatibility or guard against common errors numeric_version <- function(x, strict = TRUE) { base::numeric_version(as.character(x), strict = strict) } sprintf <- function(fmt, ...) { message <- if (nargs() == 1L) fmt else base::sprintf(fmt, ...) ansify(message) } substring <- function(text, first, last = .Machine$integer.max) { if (!is.character(text)) text <- as.character(text) n <- length(text) if (n == 0L) return(text) m <- max(n, length(first), length(last)) text <- rep_len(text, length.out = m) substr(text, first, last) } unique <- function(x) { base::unique(x) } # a wrapper for 'utils::untar()' that throws an error if untar fails untar <- function(tarfile, files = NULL, list = FALSE, exdir = ".", tar = Sys.getenv("TAR")) { # delegate to utils::untar() result <- utils::untar( tarfile = tarfile, files = files, list = list, exdir = exdir, tar = tar ) # check for errors (tar returns a status code) if (is.integer(result) && result != 0L) { call <- stringify(sys.call()) stopf("'%s' returned status code %i", call, result) } # return other results as-is result } # prefer writing files as UTF-8 writeLines <- function(text, con = stdout(), sep = "\n", useBytes = FALSE) { if (is.character(con) && missing(useBytes)) base::writeLines(enc2utf8(text), con = con, sep = sep, useBytes = TRUE) else base::writeLines(text, con, sep, useBytes) } renv/R/rstudio.R0000644000176200001440000000253014731330073013242 0ustar liggesusers renv_rstudio_available <- function() { # NOTE: detecting whether we're running within RStudio is a bit # tricky because not all of the expected RStudio bits have been # initialized when the R session is being initialized (e.g. # when the .Rprofile is being executed) args <- commandArgs(trailingOnly = FALSE) args[[1L]] == "RStudio" || .Platform$GUI == "RStudio" } renv_rstudio_initialize <- function(project) { tools <- catch(as.environment("tools:rstudio")) if (inherits(tools, "error")) return(FALSE) if (is.null(tools$.rs.api.initializeProject)) return(FALSE) tools$.rs.api.initializeProject(project) TRUE } renv_rstudio_fixup <- function() { # if RStudio's tools are on the search path, we should try # to fix them up so that renv's own routines don't get seen tools <- catch(as.environment("tools:rstudio")) if (inherits(tools, "error")) return(FALSE) helper <- tools[[".rs.clearVar"]] if (is.null(helper)) return(FALSE) # if the helper environment has been fixed up (as e.g. by # newer versions of RStudio) then nothing to do if (identical(tools, environment(helper))) return(FALSE) # put common tools from base into the environment envir <- environment(helper) for (var in c("assign", "exists", "get", "remove", "paste")) envir[[var]] <- get(var, envir = baseenv()) TRUE } renv/R/init.R0000644000176200001440000002714614731330073012526 0ustar liggesusers the$init_running <- FALSE #' Use renv in a project #' #' @description #' Call `renv::init()` to start using renv in the current project. This will: #' #' 1. Set up project infrastructure (as described in [scaffold()]) including #' the project library and the `.Rprofile` that ensures renv will be #' used in all future sessions, #' #' 1. Discover the packages that are currently being used in your project #' (via [dependencies()]), and install them into the project library #' (as described in [hydrate()]), #' #' 1. Create a lockfile that records the state of the project library so it #' can be restored by others (as described in [snapshot()]), #' #' 1. Restart R (if running inside RStudio). #' #' If you call `renv::init()` with a project that is already using renv, it will #' attempt to do the right thing: it will restore the project library if it's #' missing, or otherwise ask you what to do. #' #' # Repositories #' #' If the default \R repositories have not already been set, renv will use #' the [Posit Public Package Manager](https://packagemanager.posit.co/) CRAN #' mirror for package installation. The primary benefit to using this mirror is #' that it can provide pre-built binaries for \R packages on a variety of #' commonly-used Linux distributions. This behavior can be configured or #' disabled if desired -- see the options in [renv::config()] for more details. #' #' @inherit renv-params #' #' @param project The project directory. When `NULL` (the default), the current #' working directory will be used. The \R working directory will be #' changed to match the requested project directory. #' #' @param settings A list of [settings] to be used with the newly-initialized #' project. #' #' @param bare Boolean; initialize the project with an empty project library, #' without attempting to discover and install \R package dependencies? #' #' @param force Boolean; force initialization? By default, renv will refuse #' to initialize the home directory as a project, to defend against accidental #' misusages of `init()`. #' #' @param repos The \R repositories to be used in this project. #' See **Repositories** for more details. #' #' @param bioconductor The version of Bioconductor to be used with this project. #' Setting this may be appropriate if renv is unable to determine that your #' project depends on a package normally available from Bioconductor. Set this #' to `TRUE` to use the default version of Bioconductor recommended by the #' BiocManager package. #' #' @param load Boolean; should the project be loaded after it is initialized? #' #' @param restart Boolean; attempt to restart the \R session after initializing #' the project? A session restart will be attempted if the `"restart"` \R #' option is set by the frontend hosting \R. #' #' @export #' #' @example examples/examples-init.R init <- function(project = NULL, ..., profile = NULL, settings = NULL, bare = FALSE, force = FALSE, repos = NULL, bioconductor = NULL, load = TRUE, restart = interactive()) { renv_consent_check() renv_scope_error_handler() renv_dots_check(...) renv_scope_binding(the, "init_running", TRUE) project <- renv_path_normalize(project %||% getwd()) renv_project_lock(project = project) # initialize profile if (!is.null(profile)) renv_profile_set(profile) # normalize repos repos <- renv_repos_normalize(repos %||% renv_init_repos()) renv_scope_options(repos = repos) # form path to lockfile, library library <- renv_paths_library(project = project) lockfile <- renv_lockfile_path(project) # ask user what type of project this is type <- settings$snapshot.type %||% renv_init_type(project) settings$snapshot.type <- type # initialize bioconductor pieces biocver <- renv_init_bioconductor(bioconductor, project) if (!is.null(biocver)) { # validate that this version of bioconductor is appropriate renv_bioconductor_validate(version = biocver) # make sure a Bioconductor package manager is installed renv_bioconductor_init(library = library) # retrieve bioconductor repositories appropriate for this project repos <- renv_bioconductor_repos(project = project, version = biocver) renv_scope_options(repos = repos) # notify user writef("- Using Bioconductor version '%s'.", biocver) settings[["bioconductor.version"]] <- biocver } # prepare and move into project directory renv_init_validate_project(project, force) renv_init_settings(project, settings) # for bare inits, just activate the project if (bare) { renv_imbue_impl(project) return(renv_init_fini(project, profile, load, restart)) } # compute and cache dependencies to (a) reveal problems early and (b) compute once renv_snapshot_dependencies(project, type = type, dev = TRUE) # determine appropriate action action <- renv_init_action(project, library, lockfile, bioconductor) cancel_if(empty(action) || identical(action, "cancel")) # compute library paths for this project libpaths <- renv_init_libpaths(project = project) # perform the action if (action == "init") { renv_scope_options(renv.config.dependency.errors = "ignored") renv_imbue_impl(project, library = library) hydrate(library = library, repos = repos, prompt = FALSE, report = FALSE, project = project) snapshot(library = libpaths, repos = repos, prompt = FALSE, project = project, force = TRUE) } else if (action == "restore") { ensure_directory(library) renv_sandbox_activate(project = project) restore(project = project, library = libpaths, repos = repos, prompt = FALSE) } # activate the newly-hydrated project renv_init_fini(project, profile, load, restart) } renv_init_fini <- function(project, profile, load, restart) { renv_activate_impl( project = project, profile = profile, version = renv_metadata_version(), load = load, restart = restart ) invisible(project) } renv_init_action <- function(project, library, lockfile, bioconductor) { # if the user has asked for bioconductor, treat this as a re-initialization if (!is.null(bioconductor)) return("init") # figure out appropriate action case( # if both the library and lockfile exist, ask user for intended action file.exists(lockfile) ~ renv_init_action_conflict_lockfile(project, library, lockfile), # if a private library exists but no lockfile, ask whether we should use it file.exists(library) ~ renv_init_action_conflict_library(project, library, lockfile), # otherwise, we just want to initialize the project ~ "init" ) } renv_init_action_conflict_lockfile <- function(project, library, lockfile) { if (!interactive()) return("nothing") title <- "This project already has a lockfile. What would you like to do?" choices <- c( restore = "Restore the project from the lockfile.", init = "Discard the lockfile and re-initialize the project.", nothing = "Activate the project without snapshotting or installing any packages.", cancel = "Abort project initialization." ) selection <- tryCatch( utils::select.list(choices, title = title, graphics = FALSE), interrupt = identity ) if (inherits(selection, "interrupt")) return(NULL) names(selection) } renv_init_action_conflict_library <- function(project, library, lockfile) { if (!interactive()) return("nothing") # if the project library exists, but it's empty, # treat this as a request to initialize the project # https://github.com/rstudio/renv/issues/1668 db <- installed_packages(lib.loc = library, priority = NA_character_) if (nrow(db) == 0L) return("init") # if only renv is installed, but it matches the version of renv being used renvonly <- NROW(db) == 1L && db[["Package"]] == "renv" && db[["Version"]] == renv_package_version("renv") if (renvonly) return("init") title <- "This project already has a private library. What would you like to do?" choices <- c( nothing = "Activate the project and use the existing library.", init = "Re-initialize the project with a new library.", cancel = "Abort project initialization." ) selection <- tryCatch( utils::select.list(choices, title = title, graphics = FALSE), interrupt = identity ) if (inherits(selection, "interrupt")) return(NULL) names(selection) } renv_init_validate_project <- function(project, force) { # allow all project directories when force = TRUE if (force) return(TRUE) # disallow attempts to initialize renv in the home directory home <- path.expand("~/") msg <- if (renv_file_same(project, home)) "refusing to initialize project in home directory" else if (renv_path_within(home, project)) sprintf("refusing to initialize project in directory '%s'", project) if (!is.null(msg)) { msg <- paste(msg, "-- use renv::init(force = TRUE) to override") stopf(msg) } } renv_init_settings <- function(project, settings) { defaults <- renv_settings_get(project) merged <- renv_settings_merge(defaults, settings) renv_settings_persist(project, merged) invisible(merged) } renv_init_bioconductor <- function(bioconductor, project) { # if we're re-initializing a project that appears to depend # on Bioconductor, then use the latest Bioconductor release if (is.null(bioconductor)) { lockpath <- renv_paths_lockfile(project = project) if (file.exists(lockpath)) { lockfile <- renv_lockfile_read(lockpath) bioconductor <- !is.null(lockfile$Bioconductor) } } # resolve bioconductor argument case( is.character(bioconductor) ~ bioconductor, identical(bioconductor, TRUE) ~ renv_bioconductor_version(project, refresh = TRUE), identical(bioconductor, FALSE) ~ NULL ) } renv_init_repos <- function(repos = getOption("repos")) { # if PPM is disabled, just use default repositories repos <- convert(repos, "list") if (!renv_ppm_enabled()) return(repos) # check whether the user has opted into using PPM by default enabled <- config$ppm.default() if (!enabled) return(repos) # check for default repositories # # note that if the user is using RStudio, we only want to override # the repositories if they haven't explicitly set their own repo URL # # https://github.com/rstudio/renv/issues/1782 rstudio <- structure( list(CRAN = "https://cran.rstudio.com/"), RStudio = TRUE ) isdefault <- identical(repos, list(CRAN = "@CRAN@")) || identical(repos, rstudio) if (isdefault) { repos[["CRAN"]] <- config$ppm.url() } repos } renv_init_type <- function(project) { # check if the user has already requested a snapshot type type <- renv_settings_get(project, name = "snapshot.type", default = NULL) if (!is.null(type)) return(type) # if we don't have a DESCRIPTION file, use the default if (!file.exists(file.path(project, "DESCRIPTION"))) return(settings$snapshot.type(project = project)) # otherwise, ask the user if they want to explicitly enumerate their # R package dependencies in the DESCRIPTION file choice <- menu( title = c( "This project contains a DESCRIPTION file.", "Which files should renv use for dependency discovery in this project?" ), choices = c( explicit = "Use only the DESCRIPTION file. (explicit mode)", implicit = "Use all files in this project. (implicit mode)" ) ) if (identical(choice, "cancel")) cancel() writef("- Using '%s' snapshot type. Please see `?renv::snapshot` for more details.\n", choice) choice } renv/R/r.R0000644000176200001440000002115014742057177012026 0ustar liggesusers R <- function() { bin <- normalizePath(R.home("bin"), winslash = "/") exe <- if (renv_platform_windows()) "R.exe" else "R" file.path(bin, exe) } r <- function(args, ...) { # ensure R_LIBS is set; unset R_LIBS_USER and R_LIBS_SITE # so that R_LIBS will always take precedence rlibs <- paste(renv_libpaths_all(), collapse = .Platform$path.sep) renv_scope_envvars(R_LIBS = rlibs, R_LIBS_USER = "NULL", R_LIBS_SITE = "NULL") # ensure Rtools is on the PATH for Windows renv_scope_rtools() # use the same tar for installation as currently configured tar <- Sys.getenv("R_INSTALL_TAR", unset = renv_tar_exe(default = "internal")) renv_scope_envvars(R_INSTALL_TAR = tar) # invoke r suppressWarnings(system2(R(), args, ...)) } r_exec_error <- function(package, output, label, extra) { # installation failed; write output for user fmt <- "Error %sing package '%s':" header <- sprintf(fmt, label, package) lines <- paste(rep("=", nchar(header)), collapse = "") # try to add diagnostic information if possible diagnostics <- r_exec_error_diagnostics(package, output) if (!empty(diagnostics)) { size <- min(getOption("width"), 78L) dividers <- paste(rep.int("-", size), collapse = "") output <- c(output, paste(dividers, diagnostics, collapse = "\n\n")) } # normalize 'extra' extra <- if (is.integer(extra)) paste("error code", extra) else paste(renv_path_pretty(extra), "does not exist") # stop with an error footer <- sprintf("%s of package '%s' failed [%s]", label, package, extra) all <- c(header, lines, "", output, footer) abort(all) } r_exec_error_diagnostics_fortran_library <- function() { checker <- function(output) { pattern <- "library not found for -l(quadmath|gfortran|fortran)" idx <- grep(pattern, output, ignore.case = TRUE) if (length(idx)) return(unique(output[idx])) } suggestion <- " R was unable to find one or more FORTRAN libraries during compilation. This often implies that the FORTRAN compiler has not been properly configured. Please see https://stackoverflow.com/q/35999874 for more information. " list( checker = checker, suggestion = suggestion ) } r_exec_error_diagnostics_fortran_binary <- function() { checker <- function(output) { pattern <- "gfortran: no such file or directory" idx <- grep(pattern, output, ignore.case = TRUE) if (length(idx)) return(unique(output[idx])) } suggestion <- " R was unable to find the gfortran binary. gfortran is required for the compilation of FORTRAN source files. Please check that gfortran is installed and available on the PATH. Please see https://stackoverflow.com/q/35999874 for more information. " list( checker = checker, suggestion = suggestion ) } r_exec_error_diagnostics_openmp <- function() { checker <- function(output) { pattern <- "unsupported option '-fopenmp'" idx <- grep(pattern, output, fixed = TRUE) if (length(idx)) return(unique(output[idx])) } suggestion <- " R is currently configured to use a compiler that does not have OpenMP support. You may need to disable OpenMP, or update your compiler toolchain. Please see https://support.bioconductor.org/p/119536/ for a related discussion. " list( checker = checker, suggestion = suggestion ) } r_exec_error_diagnostics <- function(package, output) { diagnostics <- list( r_exec_error_diagnostics_fortran_library(), r_exec_error_diagnostics_fortran_binary(), r_exec_error_diagnostics_openmp() ) suggestions <- uapply(diagnostics, function(diagnostic) { check <- catch(diagnostic$checker(output)) if (!is.character(check)) return() suggestion <- diagnostics$suggestion reasons <- paste("-", shQuote(check), collapse = "\n") paste(diagnostic$suggestion, "Reason(s):", reasons, sep = "\n") }) as.character(suggestions) } # install package called 'package' located at path 'path' r_cmd_install <- function(package, path, ...) { # normalize path to package path <- renv_path_normalize(path, mustWork = TRUE) # unpack .zip source archives before install # https://github.com/rstudio/renv/issues/1359 ftype <- renv_file_type(path) atype <- renv_archive_type(path) ptype <- renv_package_type(path) unpack <- ftype == "file" && atype == "zip" && ptype == "source" if (unpack) { newpath <- renv_package_unpack(package, path, force = TRUE) if (!identical(newpath, path)) { path <- newpath defer(unlink(path, recursive = TRUE)) } } # rename binary .zip files if necessary rename <- ftype == "file" && atype == "zip" && ptype == "binary" if (rename) { regexps <- .standard_regexps() fmt <- "^%s(?:_%s)?\\.zip$" pattern <- sprintf(fmt, regexps$valid_package_name, regexps$valid_package_version) if (!grepl(pattern, basename(path), perl = TRUE)) { dir <- renv_scope_tempfile(package) ensure_directory(dir) newpath <- file.path(dir, paste(package, "zip", sep = ".")) renv_file_copy(path, newpath) path <- newpath } } # resolve default library path library <- renv_libpaths_active() # validate that we have command line tools installed and # available for e.g. macOS if (renv_platform_macos() && renv_package_type(path) == "source") renv_xcode_check() # perform platform-specific pre-install checks renv_scope_install() # perform the install # note that we need to supply '-l' below as otherwise the library paths # could be changed by, for example, site-specific profiles args <- c( "--vanilla", "CMD", "INSTALL", "--preclean", "--no-multiarch", "--with-keep.source", r_cmd_install_option(package, "configure.args", TRUE), r_cmd_install_option(package, "configure.vars", TRUE), r_cmd_install_option(package, c("install.opts", "INSTALL_opts"), FALSE), "-l", renv_shell_path(library), ..., renv_shell_path(path) ) if (config$install.verbose()) { status <- r(args, stdout = "", stderr = "") if (!identical(status, 0L)) stopf("install of package '%s' failed", package) installpath <- file.path(library, package) if (!file.exists(installpath)) { fmt <- "install of package '%s' failed: %s does not exist" stopf(fmt, package, renv_path_pretty(installpath)) } installpath } else { output <- r(args, stdout = TRUE, stderr = TRUE) status <- attr(output, "status") %||% 0L if (!identical(status, 0L)) r_exec_error(package, output, "install", status) installpath <- file.path(library, package) if (!file.exists(installpath)) r_exec_error(package, output, "install", installpath) installpath } } r_cmd_build <- function(package, path, ...) { path <- renv_path_normalize(path, mustWork = TRUE) args <- c("--vanilla", "CMD", "build", "--md5", ..., renv_shell_path(path)) output <- r(args, stdout = TRUE, stderr = TRUE) status <- attr(output, "status") %||% 0L if (!identical(status, 0L)) r_exec_error(package, output, "build", status) pasted <- paste(output, collapse = "\n") pattern <- "[*] building .([a-zA-Z0-9_.-]+)." matches <- regexec(pattern, pasted) text <- regmatches(pasted, matches) tarball <- text[[1L]][[2L]] if (!file.exists(tarball)) r_exec_error(package, output, "build", tarball) file.path(getwd(), tarball) } r_cmd_install_option <- function(package, options, configure) { # read option -- first, check for package-specific option, then # fall back to 'global' option for (option in options) { value <- r_cmd_install_option_impl(package, option, configure) if (!is.null(value)) return(value) } } r_cmd_install_option_impl <- function(package, option, configure) { value <- getOption(paste(option, package, sep = ".")) %||% getOption(option) if (is.null(value)) return(NULL) # if the value is named, treat it as a list, # mapping package names to their configure arguments if (!is.null(names(value))) value <- as.list(value) # check for named values if (!is.null(names(value))) { value <- value[[package]] if (is.null(value)) return(NULL) } # if this is a configure option, format specially if (configure) { confkey <- sub(".", "-", option, fixed = TRUE) confval <- if (!is.null(names(value))) shQuote(paste(names(value), value, sep = "=", collapse = " ")) else shQuote(paste(value, collapse = " ")) return(sprintf("--%s=%s", confkey, confval)) } # otherwise, just paste it paste(value, collapse = " ") } r_cmd_config <- function(...) { renv_system_exec( command = R(), args = c("--vanilla", "CMD", "config", ...), action = "reading R CMD config" ) } renv/R/modify.R0000644000176200001440000000556714731330073013055 0ustar liggesusers #' Modify a Lockfile #' #' Modify a project's lockfile, either interactively or non-interactively. #' #' After edit, if the lockfile edited is associated with the active project, any #' state-related changes (e.g. to \R repositories) will be updated in the #' current session. #' #' @inherit renv-params #' #' @param changes A list of changes to be merged into the lockfile. #' When `NULL` (the default), the lockfile is instead opened for #' interactive editing. #' #' @export #' #' @examples #' \dontrun{ #' #' # modify an existing lockfile #' if (interactive()) #' renv::modify() #' #' } modify <- function(project = NULL, changes = NULL) { renv_scope_error_handler() project <- renv_project_resolve(project) renv_project_lock(project = project) renv_modify_impl(project, changes) invisible(project) } renv_modify_impl <- function(project, changes) { lockfile <- if (is.null(changes)) renv_modify_interactive(project) else renv_modify_noninteractive(project, changes) if (renv_project_loaded(project)) renv_modify_fini(lockfile) } renv_modify_interactive <- function(project) { # check for interactive session if (!interactive()) stop("can't modify lockfile in non-interactive session") # resolve path to lockfile lockpath <- renv_lockfile_path(project) if (!file.exists(lockpath)) stopf("lockfile '%s' does not exist", renv_path_aliased(lockpath)) # copy the lockfile to a temporary file dir <- renv_scope_tempfile("renv-lockfile-") ensure_directory(dir) templock <- file.path(dir, "renv.lock") file.copy(lockpath, templock) # edit the temporary lockfile renv_file_edit(templock) # check that the new lockfile can be read withCallingHandlers( lockfile <- catch(renv_lockfile_read(file = templock)), error = function(cnd) { stop(lines( "renv was unable to parse the modified lockfile:", conditionMessage(cnd), "Your changes will be discarded" )) } ) lockfile } renv_modify_noninteractive <- function(project, changes) { # resolve path to lockfile lockpath <- renv_lockfile_path(project) if (!file.exists(lockpath)) stopf("lockfile '%s' does not exist", renv_path_aliased(lockpath)) # read it lockfile <- renv_lockfile_read(file = lockpath) # merge changes merged <- overlay(lockfile, changes) # write updated lockfile to a temporary file templock <- renv_scope_tempfile("renv-lock-") renv_lockfile_write(merged, file = templock) # try reading it once more newlock <- renv_lockfile_read(file = templock) if (!identical(merged, newlock)) stop("modify produced an invalid lockfile") # overwrite the original lockfile file.rename(templock, lockpath) # finish up merged } renv_modify_fini <- function(lockfile) { # synchronize relevant changes into the session repos <- lockfile$R$Repositories options(repos = convert(repos, "character")) } renv/R/utils.R0000644000176200001440000003026514742242046012723 0ustar liggesusers `%>%` <- function(...) { dots <- eval(substitute(alist(...))) if (length(dots) != 2L) stopf("`%>%` called with invalid number of arguments") lhs <- dots[[1L]]; rhs <- dots[[2L]] if (!is.call(rhs)) stopf("right-hand side of rhs is not a call") data <- c(rhs[[1L]], lhs, as.list(rhs[-1L])) call <- as.call(data) nm <- names(rhs) if (length(nm)) names(call) <- c("", "", nm[-1L]) eval(call, envir = parent.frame()) } `%NA%` <- function(x, y) { if (length(x) && is.na(x)) y else x } `%&&%` <- function(x, y) { if (length(x)) y } lines <- function(...) { paste(..., sep = "\n") } is_named <- function(x) { nm <- names(x) !is.null(nm) && all(nzchar(nm)) } named <- function(object, names = object) { names(object) <- names object } empty <- function(x) { length(x) == 0L } zlength <- function(x) { length(x) != 0L } trim <- function(x) { gsub("^\\s+|\\s+$", "", x, perl = TRUE) } trimws <- function(x) { gsub("^\\s+|\\s+$", "", x, perl = TRUE) } case <- function(...) { dots <- eval(substitute(alist(...))) for (i in seq_along(dots)) { if (identical(dots[[i]], quote(expr = ))) next dot <- eval(dots[[i]], envir = parent.frame()) if (!inherits(dot, "formula")) return(dot) # Silence R CMD check note expr <- NULL cond <- NULL # use delayed assignments below so we can allow return statements to # be handled in the lexical scope where they were defined if (length(dot) == 2L) { do.call(delayedAssign, list("expr", dot[[2L]], eval.env = environment(dot))) return(expr) } do.call(delayedAssign, list("cond", dot[[2L]], eval.env = environment(dot))) do.call(delayedAssign, list("expr", dot[[3L]], eval.env = environment(dot))) if (cond) return(expr) } } compose <- function(wrapper, callback) { function(...) wrapper(callback(...)) } catch <- function(expr) { tryCatch( withCallingHandlers(expr, error = renv_error_capture), error = renv_error_tag ) } catchall <- function(expr) { tryCatch( withCallingHandlers( expr = expr, error = renv_error_capture, warning = renv_error_capture ), error = renv_error_tag, warning = renv_error_tag ) } # nocov start ask <- function(question, default = FALSE) { if (renv_tests_running()) return(TRUE) enabled <- getOption("renv.prompt.enabled", default = TRUE) if (!enabled) return(default) if (!interactive()) return(default) # can't prompt for input when autoloading; code run from `.Rprofile` should # not attempt to interact with the user # from `?Startup`: # "It is not intended that there be interaction with the user during startup # code. Attempting to do so can crash the R process." # https://github.com/rstudio/renv/issues/1879 if (autoloading()) return(default) # be verbose in this scope, as we're asking the user for input renv_scope_options(renv.verbose = TRUE) repeat { # solicit user's answer selection <- if (default) "[Y/n]" else "[y/N]" prompt <- sprintf("%s %s: ", question, selection) response <- tryCatch( tolower(trimws(readline(prompt))), interrupt = identity ) # check for interrupts; treat as abort request cancel_if(inherits(response, "interrupt")) # use default when no response if (!nzchar(response)) return(default) # check for 'yes' responses if (response %in% c("y", "yes")) { writef("") return(TRUE) } # check for 'no' responses if (response %in% c("n", "no")) { writef("") return(FALSE) } # ask the user again writef("- Unrecognized response: please enter 'y' or 'n', or type Ctrl + C to cancel.") } } proceed <- function(default = TRUE) { ask("Do you want to proceed?", default = default) } menu <- function(choices, title, default = 1L) { testing <- getOption("renv.menu.choice", integer()) selected <- if (length(testing)) { options(renv.menu.choice = testing[-1L]) testing[[1L]] } else if (testing()) { default } if (!is.null(selected)) { title <- paste(title, collapse = "\n") body <- paste(sprintf("%i: %s", seq_along(choices), choices), collapse = "\n") footer <- sprintf("Selection: %s\n", selected) writef(paste(c(title, body, footer), collapse = "\n\n")) return(names(choices)[selected]) } if (!interactive()) { value <- if (is.numeric(default)) names(choices)[default] else default return(value) } idx <- tryCatch( utils::menu(choices, paste(title, collapse = "\n"), graphics = FALSE), interrupt = function(cnd) 0L ) if (idx == 0L) return("cancel") names(choices)[idx] } # nocov end insert <- function(contents, pattern, replacement, anchor = NULL, fixed = FALSE) { # first, check to see if the pattern matches a line index <- grep(pattern, contents, perl = !fixed, fixed = fixed) if (length(index)) { contents[index] <- replacement return(contents) } # otherwise, check for the anchor, and insert after index <- if (!is.null(anchor)) grep(anchor, contents, perl = !fixed, fixed = fixed) if (!length(index)) return(c(contents, replacement)) c( head(contents, n = index), replacement, tail(contents, n = -index) ) } deparsed <- function(value, width = 60L) { paste(deparse(value, width.cutoff = width), collapse = "\n") } read <- function(file) { renv_scope_options(warn = -1L) contents <- readLines(file, warn = FALSE) paste(contents, collapse = "\n") } plural <- function(word, n) { suffixes <- c("", "s") indices <- as.integer(n != 1L) + 1L paste0(word, suffixes[indices]) } nplural <- function(word, n) { paste(n, plural(word, n)) } trunc <- function(text, n = 78) { long <- nchar(text) > n text[long] <- sprintf("%s <...>", substring(text[long], 1, n - 6)) text } endswith <- function(string, suffix) { substring(string, nchar(string) - nchar(suffix) + 1) == suffix } # like tools::file_ext, but includes leading '.', and preserves # '.tar.gz', '.tar.bz' and so on fileext <- function(path, default = "") { indices <- regexpr("[.]((?:tar[.])?[[:alnum:]]+)$", path, perl = TRUE) ifelse(indices > -1L, substring(path, indices), default) } visited <- function(name, envir) { value <- envir[[name]] %||% FALSE envir[[name]] <- TRUE value } zmap <- function(x, f) { callback <- function(x) do.call(f, x) lapply(x, callback) } rowapply <- function(X, FUN, ...) { lapply(seq_len(NROW(X)), function(I) { FUN(X[I, , drop = FALSE], ...) }) } comspec <- function() { Sys.getenv("COMSPEC", unset = Sys.which("cmd.exe")) } nullfile <- function() { if (renv_platform_windows()) "NUL" else "/dev/null" } quietly <- function(expr, sink = TRUE) { if (sink) { sink(file = nullfile()) defer(sink(NULL)) } withCallingHandlers( expr, warning = function(c) invokeRestart("muffleWarning"), message = function(c) invokeRestart("muffleMessage"), packageStartupMessage = function(c) invokeRestart("muffleMessage") ) } # NOTE: This function can be used in preference to `as.*()` if you'd like # to preserve attributes on the incoming object 'x'. convert <- function(x, type) { storage.mode(x) <- type x } remap <- function(x, map) { # TODO: use match? remapped <- x enumerate(map, function(key, val) { remapped[remapped == key] <<- val }) remapped } keep <- function(x, keys) { x[intersect(keys, names(x))] } keep_if <- function(x, f) { x[f(x)] } omit <- function(x, keys) { x[setdiff(names(x), keys)] } omit_if <- function(x, f) { x[!f(x)] } invoke <- function(callback, ...) { callback(...) } resolve <- function(object) { while (is.function(object)) object <- object() object } dequote <- function(strings) { for (quote in c("'", '"')) { # find strings matching pattern pattern <- paste0(quote, "(.*)", quote) matches <- grep(pattern, strings, perl = TRUE) if (empty(matches)) next # remove outer quotes strings[matches] <- gsub(pattern, "\\1", strings[matches], perl = TRUE) # un-escape inner quotes pattern <- paste0("\\", quote) strings[matches] <- gsub(pattern, quote, strings[matches], fixed = TRUE) } strings } nth <- function(x, i) { x[[i]] } find <- function(x, f, ...) { for (i in seq_along(x)) if (!is.null(value <- f(x[[i]], ...))) return(value) } recursing <- function() { nf <- sys.nframe() if (nf < 2L) return(FALSE) np <- sys.parent() fn <- sys.function(np) for (i in seq_len(np - 1L)) if (identical(fn, sys.function(i))) return(TRUE) FALSE } csort <- function(x, decreasing = FALSE, ...) { renv_scope_locale("LC_COLLATE", "C") sort(x, decreasing, ...) } fsub <- function(pattern, replacement, x, ignore.case = FALSE, useBytes = FALSE) { sub(pattern, replacement, x, ignore.case = ignore.case, useBytes = useBytes, fixed = TRUE) } rows <- function(data, indices) { # convert logical values if (is.logical(indices)) { if (length(indices) < nrow(data)) indices <- rep(indices, length.out = nrow(data)) indices <- which(indices, useNames = FALSE) } # build output list output <- vector("list", length(data)) for (i in seq_along(data)) output[[i]] <- .subset2(data, i)[indices] # copy relevant attributes attrs <- attributes(data) attrs[["row.names"]] <- .set_row_names(length(indices)) attributes(output) <- attrs # return new data.frame output } cols <- function(data, indices) { # perform subset output <- .subset(data, indices) # copy relevant attributes attrs <- attributes(data) attrs[["names"]] <- attr(output, "names", exact = TRUE) attributes(output) <- attrs # return output output } stringify <- function(object, collapse = " ") { if (is.symbol(object)) return(as.character(object)) paste( deparse(object, width.cutoff = 500L), collapse = collapse ) } env <- function(...) { list2env(list(...), envir = new.env(parent = emptyenv())) } env2list <- function(env) { as.list.environment(env, all.names = TRUE) } chop <- function(x, split = "\n", fixed = TRUE, perl = FALSE, useBytes = FALSE) { strsplit(x, split, !perl, perl, useBytes)[[1L]] } prof <- function(expr, ...) { profile <- tempfile("renv-profile-", fileext = ".Rprof") Rprof(profile, ...) result <- expr Rprof(NULL) print(summaryRprof(profile)) invisible(result) } recycle <- function(data) { # compute number of columns n <- lengths(data, use.names = FALSE) nrow <- max(n) # start recycling for (i in seq_along(data)) { if (n[[i]] == 0L) { length(data[[i]]) <- nrow } else if (n[[i]] != nrow) { data[[i]] <- rep.int(data[[i]], nrow / n[[i]]) } } data } take <- function(data, index = NULL) { if (is.null(index)) data else .subset2(data, index) } cancel <- function(verbose = TRUE) { renv_snapshot_auto_suppress_next() if (testing()) stop("Operation canceled", call. = FALSE) if (verbose) message("- Operation canceled.") invokeRestart("abort") } cancel_if <- function(cnd) { if (cnd) cancel() } rep_named <- function(names, x) { values <- rep_len(x, length(names)) names(values) <- names values } wait_until <- function(callback, ...) { repeat if (callback(...)) return(TRUE) } timer <- function(units = "secs") { .time <- Sys.time() .units <- units list( now = function() { Sys.time() }, elapsed = function() { difftime(Sys.time(), .time, units = .units) } ) } summon <- function() { envir <- do.call(attach, list(what = NULL, name = "renv")) renv <- renv_envir_self() list2env(as.list(renv), envir = envir) } overlay <- function(lhs, rhs) { modifyList(as.list(lhs), as.list(rhs)) } # the 'top' renv function in the call stack topfun <- function() { self <- renv_envir_self() frames <- sys.frames() for (i in seq_along(frames)) if (identical(self, parent.env(frames[[i]]))) return(sys.function(i)) } warnify <- function(cnd) { class(cnd) <- c("warning", "condition") warning(cnd) } # note: also handles stringy values like 'True' not <- function(value) { if (value) FALSE else TRUE } wait <- function(predicate, ...) { while (TRUE) if (predicate(...)) break } renv/R/shell.R0000644000176200001440000000022614731330073012660 0ustar liggesusers renv_shell_quote <- function(x) { if (length(x)) shQuote(x) } renv_shell_path <- function(x) { if (length(x)) shQuote(path.expand(x)) } renv/R/ppm.R0000644000176200001440000001671514731330073012357 0ustar liggesusers renv_ppm_normalize <- function(url) { sub("/__[^_]+__/[^/]+/", "/", url) } renv_ppm_transform <- function(repos = getOption("repos")) { map_chr(repos, function(url) { tryCatch( renv_ppm_transform_impl(url), error = function(e) url ) }) } renv_ppm_transform_impl <- function(url) { # if this function is being called as part of `install(..., type = "source')` # then we want to transform binary URLs to source URLs here if (identical(the$install_pkg_type, "source")) return(renv_ppm_normalize(url)) # repository URL transformation is only necessary on Linux os <- renv_ppm_os() if (!identical(os, "__linux__")) return(url) # check for a known platform platform <- renv_ppm_platform() if (is.null(platform)) return(url) # don't transform non-https URLs if (!grepl("^https?://", url)) return(url) # if this already appears to be a binary URL, then avoid # transforming it if (grepl("/__[^_]+__/", url)) return(url) # try to parse the repository URL parts <- catch(renv_url_parse(url)) if (inherits(parts, "error")) return(url) # only attempt to transform URLs that are formatted like PPM urls: # # https://ppm.company.org/cran/checkpoint/id # # in particular, there should be at least two trailing # alphanumeric path components pattern <- "/[^/]+/[^/]+" if (!grepl(pattern, parts$path)) return(url) # check if this is an 'ignored' URL; that is, a repository which we # know is not a PPM URL mirrors <- catch(getCRANmirrors(local.only = TRUE)) ignored <- c( getOption("renv.ppm.ignoredUrls", default = character()), settings$ppm.ignored.urls(), mirrors$URL, "http://cran.rstudio.com", "http://cran.rstudio.org", "https://cran.rstudio.com", "https://cran.rstudio.org" ) if (sub("/+$", "", url) %in% sub("/+$", "", ignored)) return(url) # if this is a 'known' PPM instance, then skip the query step known <- c( dirname(dirname(config$ppm.url())), getOption("renv.ppm.repos", default = NULL) ) if (any(startsWith(url, known))) { parts <- c(dirname(url), "__linux__", platform, basename(url)) binurl <- paste(parts, collapse = "/") return(binurl) } # try to query the status endpoint -- if this fails, # assume the repository is not a ppm URL base <- dirname(dirname(url)) status <- catch(renv_ppm_status(base)) if (inherits(status, "error")) return(url) # iterate through distros and check for a match for (distro in status$distros) { ok <- identical(distro$binaryURL, platform) && identical(distro$binaries, TRUE) if (ok) { parts <- c(dirname(url), "__linux__", platform, basename(url)) binurl <- paste(parts, collapse = "/") return(binurl) } } # no match; return url as-is url } renv_ppm_status <- function(base) { memoize( key = base, value = catch(renv_ppm_status_impl(base)) ) } renv_ppm_status_impl <- function(base) { # use a shorter delay to avoid hanging a session renv_scope_options( renv.config.connect.timeout = 10L, renv.config.connect.retry = 1L ) # attempt the download endpoint <- file.path(base, "__api__/status") destfile <- renv_scope_tempfile("renv-ppm-status-", fileext = ".json") quietly(download(endpoint, destfile)) # read the downloaded JSON renv_json_read(destfile) } renv_ppm_platform <- function(file = "/etc/os-release") { platform <- Sys.getenv("RENV_PPM_PLATFORM", unset = NA) if (!is.na(platform)) return(platform) platform <- Sys.getenv("RENV_RSPM_PLATFORM", unset = NA) if (!is.na(platform)) return(platform) if (renv_platform_windows()) return("windows") if (renv_platform_macos()) return("macos") renv_ppm_platform_impl(file) } renv_ppm_platform_impl <- function(file = "/etc/os-release") { if (file.exists(file)) { properties <- renv_properties_read( path = file, delimiter = "=", dequote = TRUE ) id <- properties$ID %||% "" case( identical(id, "ubuntu") ~ renv_ppm_platform_ubuntu(properties), identical(id, "centos") ~ renv_ppm_platform_centos(properties), identical(id, "rhel") ~ renv_ppm_platform_rhel(properties), identical(id, "rocky") ~ renv_ppm_platform_rocky(properties), identical(id, "almalinux") ~ renv_ppm_platform_alma(properties), grepl("suse\\b", id) ~ renv_ppm_platform_suse(properties), identical(id, "sles") ~ renv_ppm_platform_sles(properties), identical(id, "debian") ~ renv_ppm_platform_debian(properties), identical(id, "amzn") ~ renv_ppm_platform_amzn(properties) ) } } renv_ppm_platform_ubuntu <- function(properties) { codename <- properties$VERSION_CODENAME if (is.null(codename)) return(NULL) codename } renv_ppm_platform_centos <- function(properties) { id <- properties$VERSION_ID if (is.null(id)) return(NULL) paste0("centos", substring(id, 1L, 1L)) } renv_ppm_platform_rhel <- function(properties) { id <- properties$VERSION_ID if (is.null(id)) return(NULL) version <- ifelse(numeric_version(id) < "9", "centos", "rhel") paste0(version, substring(id, 1L, 1L)) } renv_ppm_platform_rocky <- function(properties) { id <- properties$VERSION_ID if (is.null(id)) return(NULL) version <- ifelse(numeric_version(id) < "9", "centos", "rhel") paste0(version, substring(id, 1L, 1L)) } renv_ppm_platform_alma <- function(properties) { id <- properties$VERSION_ID if (is.null(id)) return(NULL) version <- ifelse(numeric_version(id) < "9", "centos", "rhel") paste0(version, substring(id, 1L, 1L)) } renv_ppm_platform_suse <- function(properties) { id <- properties$VERSION_ID if (is.null(id)) return(NULL) parts <- strsplit(id, ".", fixed = TRUE)[[1L]] paste0("opensuse", parts[[1L]], parts[[2L]]) } renv_ppm_platform_sles <- function(properties) { id <- properties$VERSION_ID if (is.null(id)) return(NULL) parts <- strsplit(id, ".", fixed = TRUE)[[1L]] paste0("opensuse", parts[[1L]], parts[[2L]]) } renv_ppm_platform_debian <- function(properties) { codename <- properties$VERSION_CODENAME if (is.null(codename)) return(NULL) codename } renv_ppm_platform_amzn <- function(properties) { id <- properties$VERSION_ID if (is.null(id)) return(NULL) if (numeric_version(id) == "2") return("centos7") return(NULL) } renv_ppm_os <- function() { os <- Sys.getenv("RENV_PPM_OS", unset = NA) if (!is.na(os)) return(os) os <- Sys.getenv("RENV_RSPM_OS", unset = NA) if (!is.na(os)) return(os) if (renv_platform_windows()) "__windows__" else if (renv_platform_macos()) "__macos__" else if (renv_platform_linux()) "__linux__" } renv_ppm_enabled <- function() { # allow environment variable override enabled <- Sys.getenv("RENV_PPM_ENABLED", unset = NA) if (!is.na(enabled)) return(truthy(enabled, default = TRUE)) # support older options as well enabled <- Sys.getenv("RENV_RSPM_ENABLED", unset = NA) if (!is.na(enabled)) return(truthy(enabled, default = TRUE)) # TODO: can we remove this check? # https://github.com/rstudio/renv/issues/1132 if (!testing()) { disabled <- renv_platform_linux() && identical(renv_platform_machine(), "aarch64") if (disabled) return(FALSE) } # check for project setting enabled <- settings$ppm.enabled() if (!is.null(enabled)) return(enabled) # otherwise, use configuration option config$ppm.enabled() } renv/R/system.R0000644000176200001440000000412214731330073013074 0ustar liggesusers renv_system_exec <- function(command, args = NULL, action = "executing command", success = 0L, stream = FALSE, quiet = NULL) { # be quiet when running tests by default quiet <- quiet %||% renv_tests_running() # handle 'stream' specially if (stream) { # form stdout, stderr stdout <- stderr <- if (quiet) FALSE else "" # execute command status <- suppressWarnings( if (is.null(args)) system(command, ignore.stdout = quiet, ignore.stderr = quiet) else system2(command, args, stdout = stdout, stderr = stderr) ) # check for error status <- status %||% 0L if (!is.null(success) && !status %in% success) { fmt <- "error %s [error code %i]" stopf(fmt, action, status) } # return status code return(status) } # suppress warnings as some successful commands may return a non-zero exit # code, whereas R will always warn on such error codes output <- suppressWarnings( if (is.null(args)) system(command, intern = TRUE) else system2(command, args, stdout = TRUE, stderr = TRUE) ) # extract status code from result status <- attr(output, "status") %||% 0L # if this status matches an expected 'success' code, return output if (is.null(success) || status %in% success) return(output) # otherwise, notify the user that things went wrong abort( sprintf("error %s [error code %i]", action, status), body = renv_system_exec_details(command, args, output) ) } renv_system_exec_details <- function(command, args, output) { # get header, giving the command that was run cmdline <- paste(command, paste(args, collapse = " ")) underline <- paste(rep.int("=", min(80L, nchar(cmdline))), collapse = "") header <- c(cmdline, underline) # truncate output (avoid overwhelming console) body <- if (length(output) > 200L) c(head(output, n = 100L), "< ... >", tail(output, n = 100L)) else output c(header, "", body) } renv/R/rehash.R0000644000176200001440000000465514761163114013040 0ustar liggesusers #' Re-hash packages in the renv cache #' #' Re-hash packages in the renv cache, ensuring that any previously-cached #' packages are copied to a new cache location appropriate for this version of #' renv. This can be useful if the cache scheme has changed in a new version #' of renv, but you'd like to preserve your previously-cached packages. #' #' Any packages which are re-hashed will retain links to the location of the #' newly-hashed package, ensuring that prior installations of renv can still #' function as expected. #' #' @inheritParams renv-params #' #' @export rehash <- function(prompt = interactive(), ...) { renv_scope_error_handler() renv_dots_check(...) renv_scope_verbose_if(prompt) invisible(renv_rehash_impl(prompt)) } renv_rehash_impl <- function(prompt) { # check for cache migration oldcache <- renv_paths_cache(version = renv_cache_version_previous())[[1L]] newcache <- renv_paths_cache(version = renv_cache_version())[[1L]] if (file.exists(oldcache) && !file.exists(newcache)) renv_rehash_cache(oldcache, prompt, renv_file_copy, "copied") # re-cache packages as necessary renv_rehash_cache(newcache, prompt, renv_file_move, "moved") } renv_rehash_cache <- function(cache, prompt, action, label) { # re-compute package hashes old <- renv_cache_list(cache = cache) printf("- Re-computing package hashes ... ") new <- map_chr(old, renv_progress_callback(renv_cache_path, length(old))) writef("Done!") changed <- which(old != new & file.exists(old) & !file.exists(new)) if (empty(changed)) { writef("- Your cache is already up-to-date -- nothing to do.") return(TRUE) } if (prompt) { fmt <- "%s [%s -> %s]" packages <- basename(old)[changed] oldhash <- renv_path_component(old[changed], 2L) newhash <- renv_path_component(new[changed], 2L) bulletin( "The following packages will be re-cached:", sprintf(fmt, format(packages), format(oldhash), format(newhash)), sprintf("Packages will be %s to their new locations in the cache.", label) ) cancel_if(prompt && !proceed()) } sources <- old[changed] targets <- new[changed] names(sources) <- targets names(targets) <- sources printf("- Re-caching packages ... ") enumerate(targets, renv_progress_callback(action, length(targets))) writef("Done!") n <- length(targets) fmt <- "Successfully re-cached %s." writef(fmt, nplural("package", n)) renv_cache_clean_empty() TRUE } renv/R/sandbox.R0000644000176200001440000002227014746262576013234 0ustar liggesusers renv_sandbox_init <- function() { # check for envvar override enabled <- Sys.getenv("RENV_SANDBOX_LOCKING_ENABLED", unset = NA) if (!is.na(enabled)) { enabled <- truthy(enabled, default = FALSE) options(renv.sandbox.locking_enabled = enabled) } # don't use sandbox in watchdog process type <- Sys.getenv("RENV_PROCESS_TYPE") if (type == "watchdog-server") return() # if renv was launched with a sandbox path on the library paths, # then immediately try to activate the sandbox # https://github.com/rstudio/renv/issues/1565 for (libpath in .libPaths()) { if (file.exists(file.path(libpath, ".renv-sandbox"))) { renv_sandbox_activate_impl(sandbox = libpath) break } } } renv_sandbox_activate <- function(project = NULL) { # record start time before <- Sys.time() # attempt the activation status <- catch(renv_sandbox_activate_impl(project)) if (inherits(status, "error")) warnify(status) # record end time after <- Sys.time() # check for long elapsed time elapsed <- difftime(after, before, units = "secs") # if it took too long to activate the sandbox, warn the user if (elapsed > 10) { fmt <- heredoc(" renv took longer than expected (%s) to activate the sandbox. The sandbox can be disabled by setting: RENV_CONFIG_SANDBOX_ENABLED = FALSE within an appropriate start-up .Renviron file. See `?renv::config` for more details. ") warningf(fmt, renv_difftime_format(elapsed)) } # return status status } renv_sandbox_activate_impl <- function(project = NULL, sandbox = NULL) { # lock access to the sandbox if (config$sandbox.enabled()) { sandbox <- sandbox %||% renv_sandbox_path(project = project) lockfile <- paste(sandbox, "lock", sep = ".") ensure_parent_directory(lockfile) renv_scope_lock(lockfile) ensure_directory(sandbox) } # get current library paths oldlibs <- .libPaths() syslibs <- c(renv_libpaths_site(), renv_libpaths_system()) syslibs <- renv_path_normalize(syslibs) # override .Library.site base <- .BaseNamespaceEnv renv_binding_replace(base, ".Library.site", NULL) # generate sandbox if (config$sandbox.enabled()) { renv_sandbox_generate(sandbox) renv_binding_replace(base, ".Library", sandbox) } # update library paths newlibs <- renv_vector_diff(oldlibs, syslibs) renv_libpaths_set(newlibs) # protect against user profiles that might update library paths if (config$sandbox.enabled()) renv_sandbox_activate_check(newlibs) # return new library paths renv_libpaths_all() } renv_sandbox_activated <- function() { !identical(.Library, renv_libpaths_system()) } renv_sandbox_activate_check <- function(libs) { envir <- globalenv() danger <- exists(".First", envir = envir, inherits = FALSE) && autoloading() if (!danger) return(FALSE) .First <- get(".First", envir = envir, inherits = FALSE) wrapper <- function() { # scope the library paths as currently defined renv_scope_libpaths() # call the user-defined .First function status <- tryCatch(.First(), error = warnify) # double-check if we should restore .First (this is extra # paranoid but in theory .First could remove itself) if (identical(wrapper, get(".First", envir = envir))) assign(".First", .First, envir = envir) # return result of .First invisible(status) } assign(".First", wrapper, envir = envir) return(TRUE) } renv_sandbox_generate <- function(sandbox) { # make the library temporarily writable lockable <- renv_sandbox_lockable() if (lockable) { dlog("sandbox", "unlocking sandbox") renv_sandbox_unlock(sandbox) } # find system packages in the system library priority <- getOption("renv.sandbox.priority", default = c("base", "recommended")) syspkgs <- installed_packages( lib.loc = renv_libpaths_system(), priority = priority ) # link into sandbox sources <- with(syspkgs, file.path(LibPath, Package)) targets <- with(syspkgs, file.path(sandbox, Package)) names(targets) <- sources enumerate(targets, function(source, target) { if (!renv_file_same(source, target)) renv_file_link(source, target, overwrite = TRUE) }) # create marker indicating this is a sandbox # (or, if it already exists, re-create it and update its ctime / mtime) marker <- file.path(sandbox, ".renv-sandbox") file.create(marker) # update mtime on the sandbox itself as well Sys.setFileTime(sandbox, time = Sys.time()) # make the library unwritable again if (lockable) { dlog("sandbox", "locking sandbox") renv_sandbox_lock(sandbox) } # return sandbox path sandbox } renv_sandbox_deactivate <- function() { # get library paths sans .Library, .Library.site old <- renv_libpaths_all() syslibs <- renv_path_normalize(c(.Library, .Library.site)) # restore old bindings base <- .BaseNamespaceEnv renv_binding_replace(base, ".Library", renv_libpaths_system()) renv_binding_replace(base, ".Library.site", renv_libpaths_site()) # update library paths new <- renv_vector_diff(old, syslibs) renv_libpaths_set(new) renv_libpaths_all() } renv_sandbox_task <- function(...) { # check if we're enabled if (!renv_sandbox_activated()) return() # allow opt-out if necessary enabled <- getOption("renv.sandbox.task", default = TRUE) if (!enabled) return() # get sandbox path sandbox <- tail(.libPaths(), n = 1L) # make sure it exists if (!file.exists(sandbox)) { warning("the renv sandbox was deleted; it will be re-generated", call. = FALSE) ensure_directory(sandbox) renv_sandbox_generate(sandbox) } # update the sandbox write time / mtime Sys.setFileTime(sandbox, time = Sys.time()) } renv_sandbox_path <- function(project = NULL) { renv_paths_sandbox(project = project) } renv_sandbox_lockable <- function(sandbox = NULL) { getOption("renv.sandbox.locking_enabled", default = TRUE) } renv_sandbox_lock <- function(sandbox = NULL, project = NULL) { sandbox <- sandbox %||% renv_sandbox_path(project = project) mode <- file.mode(sandbox) & "577" Sys.chmod(sandbox, mode = mode) } renv_sandbox_locked <- function(sandbox = NULL, project = NULL) { sandbox <- sandbox %||% renv_sandbox_path(project = project) file.exists(sandbox) && file.access(sandbox, mode = 7L) != 0L } renv_sandbox_unlock <- function(sandbox = NULL, project = NULL) { sandbox <- sandbox %||% renv_sandbox_path(project = project) mode <- file.mode(sandbox) | "200" Sys.chmod(sandbox, mode = mode) } #' The default library sandbox #' #' @description #' An \R installation can have up to three types of library paths available #' to the user: #' #' - The _user library_, where \R packages downloaded and installed by the #' current user are installed. This library path is only visible to that #' specific user. #' #' - The _site library_, where \R packages maintained by administrators of a #' system are installed. This library path, if it exists, is visible to all #' users on the system. #' #' - The _default library_, where \R packages distributed with \R itself are #' installed. This library path is visible to all users on the system. #' #' Normally, only so-called "base" and "recommended" packages should be installed #' in the default library. (You can get a list of these packages with #' `installed.packages(priority = c("base", "recommended"))`). However, it is #' possible for users and administrators to install packages into the default #' library, if the filesystem permissions permit them to do so. (This, for #' example, is the default behavior on macOS.) #' #' Because the site and default libraries are visible to all users, having those #' accessible in renv projects can potentially break isolation -- that is, #' if a package were updated in the default library, that update would be visible #' to all \R projects on the system. #' #' To help defend against this, renv uses something called the "sandbox" to #' isolate renv projects from non-"base" packages that are installed into the #' default library. When an renv project is loaded, renv will: #' #' - Create a new, empty library path (called the "sandbox"), #' #' - Link only the "base" and "recommended" packages from the default library #' into the sandbox, #' #' - Mark the sandbox as read-only, so that users are unable to install packages #' into this library, #' #' - Instruct the \R session to use the "sandbox" as the default library. #' #' This process is mostly transparent to the user. However, because the sandbox #' is read-only, if you later need to remove the sandbox, you'll need to reset #' file permissions manually; for example, with `renv::sandbox$unlock()`. #' #' If you'd prefer to keep the sandbox unlocked, you can also set: #' #' ``` #' RENV_SANDBOX_LOCKING_ENABLED = FALSE #' ``` #' #' in an appropriate startup `.Renviron` or `Renviron.site` file. #' #' The sandbox can also be disabled entirely with: #' #' ``` #' RENV_CONFIG_SANDBOX_ENABLED = FALSE #' ``` #' #' The sandbox library path can also be configured using the `RENV_PATHS_SANDBOX` #' environment variable: see [paths] for more details. #' #' @format NULL #' @export sandbox <- list( path = renv_sandbox_path, lock = renv_sandbox_lock, locked = renv_sandbox_locked, unlock = renv_sandbox_unlock ) renv/R/manifest-convert.R0000644000176200001440000000456214731330073015044 0ustar liggesusers #' Generate `renv.lock` from an RStudio Connect `manifest.json` #' #' @description #' Use `renv_lockfile_from_manifest()` to convert a `manifest.json` file from #' an RStudio Connect content bundle into an `renv.lock` lockfile. #' #' This function can be useful when you need to recreate the package environment #' of a piece of content that is deployed to RStudio Connect. The content bundle #' contains a `manifest.json` file that is used to recreate the package #' environment. This function will let you convert that manifest file to an #' `renv.lock` file. Run `renv::restore()` after you've converted the file to #' restore the package environment. #' #' @param manifest #' The path to a `manifest.json` file. #' #' @param lockfile #' The path to the lockfile to be generated and / or updated. #' When `NA` (the default), the generated lockfile is returned as an \R #' object; otherwise, the lockfile will be written to the path specified by #' `lockfile`. #' #' @return #' An renv lockfile. #' #' @keywords internal renv_lockfile_from_manifest <- function(manifest = "manifest.json", lockfile = NA, project = NULL) { renv_scope_error_handler() project <- renv_project_resolve(project) # read the manifest (accept both lists and file paths) manifest <- case( is.character(manifest) ~ renv_json_read(manifest), is.list(manifest) ~ manifest, TRUE ~ renv_type_unexpected(manifest) ) # convert descriptions into records records <- map(manifest[["packages"]], function(entry) { desc <- entry[["description"]] renv_snapshot_description_impl(desc) }) # extract repositories from descriptions repos <- list() for (entry in manifest[["packages"]]) { if (is.null(entry[["Repository"]])) next src <- entry[["Source"]] %||% "CRAN" repo <- entry[["Repository"]] repos[[src]] <- repo } # extract version version <- format(manifest[["platform"]] %||% getRversion()) # create R field for lockfile r <- list(Version = version, Repositories = repos) # create the lockfile lock <- list(R = r, Packages = records) class(lock) <- "renv_lockfile" # return lockfile as R object if requested if (is.na(lockfile)) return(lock) # otherwise, write to file renv_lockfile_write(lock, file = lockfile) invisible(lock) } renv/R/defer.R0000644000176200001440000000367614731330072012651 0ustar liggesusers # environment hosting exit callbacks the$defer_callbacks <- new.env(parent = emptyenv()) renv_defer_init <- function() { # make sure we run callbacks set on the global environment on exit # but only in non-interactive sessions, just to be safe if (interactive()) return() reg.finalizer(.GlobalEnv, renv_defer_execute, onexit = TRUE) } defer <- function(expr, scope = parent.frame()) { handler <- renv_defer_add( list(expr = substitute(expr), envir = parent.frame()), envir = scope ) invisible(handler) } renv_defer_id <- function(envir) { format.default(envir) } renv_defer_get <- function(envir) { id <- renv_defer_id(envir) the$defer_callbacks[[id]] } renv_defer_set <- function(envir, handlers) { # get any previously-set handlers. if we don't see any handlers registered, # this must be our first time registering exit handlers on the environment, # and so we'll want to register an on.exit handler to call our handlers oldhandlers <- renv_defer_get(envir) if (is.null(oldhandlers)) { call <- as.call(list(renv_defer_execute, envir)) do.call(base::on.exit, list(substitute(call), TRUE), envir = envir) } # register the newly-set handlers id <- renv_defer_id(envir) the$defer_callbacks[[id]] <- handlers } renv_defer_remove <- function(envir) { id <- renv_defer_id(envir) rm(list = id, envir = the$defer_callbacks) } renv_defer_execute <- function(envir = parent.frame()) { # check for handlers -- may be NULL if they were intentionally executed # early via a call to `renv_defer_execute()` handlers <- renv_defer_get(envir) if (is.null(handlers)) return() # execute the existing handlers for (handler in handlers) tryCatch(eval(handler$expr, handler$envir), error = identity) # remove the handlers renv_defer_remove(envir) } renv_defer_add <- function(envir, handler) { handlers <- c(list(handler), renv_defer_get(envir)) renv_defer_set(envir, handlers) handler } renv/R/migrate.R0000644000176200001440000002214114761163114013204 0ustar liggesusers #' Migrate a project from packrat to renv #' #' Migrate a project's infrastructure from packrat to renv. #' #' # Migration #' #' When migrating Packrat projects to renv, the set of components migrated #' can be customized using the `packrat` argument. The set of components that #' can be migrated are as follows: #' #' \tabular{ll}{ #' #' **Name** \tab **Description** \cr #' #' `lockfile` \tab #' Migrate the Packrat lockfile (`packrat/packrat.lock`) to the renv lockfile #' (`renv.lock`). \cr #' #' `sources` \tab #' Migrate package sources from the `packrat/src` folder to the renv #' sources folder. Currently, only CRAN packages are migrated to renv -- #' packages retrieved from other sources (e.g. GitHub) are ignored. #' \cr #' #' `library` \tab #' Migrate installed packages from the Packrat library to the renv project #' library. #' \cr #' #' `options` \tab #' Migrate compatible Packrat options to the renv project. #' \cr #' #' `cache` \tab #' Migrate packages from the Packrat cache to the renv cache. #' \cr #' #' } #' #' @inherit renv-params #' #' @param packrat Components of the Packrat project to migrate. See the default #' argument list for components of the Packrat project that can be migrated. #' Select a subset of those components for migration as appropriate. #' #' @export #' #' @examples #' \dontrun{ #' #' # migrate Packrat project infrastructure to renv #' renv::migrate() #' #' } migrate <- function( project = NULL, packrat = c("lockfile", "sources", "library", "options", "cache")) { renv_consent_check() renv_scope_error_handler() project <- renv_project_resolve(project) renv_project_lock(project = project) project <- renv_path_normalize(project, mustWork = TRUE) if (file.exists(file.path(project, "packrat/packrat.lock"))) { packrat <- match.arg(packrat, several.ok = TRUE) renv_migrate_packrat(project, packrat) } invisible(project) } renv_migrate_packrat <- function(project = NULL, components = NULL) { project <- renv_project_resolve(project) if (!requireNamespace("packrat", quietly = TRUE)) stopf("migration requires the 'packrat' package to be installed") callbacks <- list( lockfile = renv_migrate_packrat_lockfile, sources = renv_migrate_packrat_sources, library = renv_migrate_packrat_library, options = renv_migrate_packrat_options, cache = renv_migrate_packrat_cache ) components <- components %||% names(callbacks) callbacks <- callbacks[components] for (callback in callbacks) callback(project) renv_migrate_packrat_infrastructure(project) renv_imbue_impl(project) fmt <- "- Project '%s' has been migrated from Packrat to renv." writef(fmt, renv_path_aliased(project)) writef("- Consider deleting the project 'packrat' folder if it is no longer needed.") invisible(TRUE) } renv_migrate_packrat_lockfile <- function(project) { plock <- file.path(project, "packrat/packrat.lock") if (!file.exists(plock)) return(FALSE) # read the lockfile contents <- read(plock) splat <- strsplit(contents, "\n{2,}")[[1]] dcf <- lapply(splat, function(section) { renv_dcf_read(text = section) }) # split into header + package fields header <- dcf[[1]] records <- dcf[-1L] # parse the repositories repos <- getOption("repos") if (!is.null(header$Repos)) { parts <- strsplit(header$Repos, "\\s*,\\s*")[[1]] repos <- renv_properties_read(text = parts, delimiter = "=") } # fix-up some record fields for renv fields <- c("Package", "Version", "Source") records <- lapply(records, function(record) { # remove an old packrat hash record$Hash <- NULL # add RemoteType for GitHub records if (any(grepl("^Github", names(record)))) record$RemoteType <- "github" # remap '^Github'-style records to '^Remote' map <- c( "GithubRepo" = "RemoteRepo", "GithubUsername" = "RemoteUsername", "GithubRef" = "RemoteRef", "GithubSha1" = "RemoteSha", "GithubSHA1" = "RemoteSha", "GithubSubdir" = "RemoteSubdir" ) names(record) <- remap(names(record), map) # keep only fields of interest keep <- c(fields, grep("^Remote", names(record), value = TRUE)) as.list(record[keep]) }) # pull out names for records names(records) <- extract_chr(records, "Package") # ensure renv is added records <- renv_snapshot_fixup_renv(records) # generate a blank lockfile lockfile <- structure(list(), class = "renv_lockfile") lockfile$R <- renv_lockfile_init_r(project) # update fields lockfile$R$Version <- header$RVersion lockfile$R$Repositories <- as.list(repos) renv_lockfile_records(lockfile) <- records # finish lockfile <- renv_lockfile_fini(lockfile, project) # write the lockfile lockpath <- renv_lockfile_path(project = project) renv_lockfile_write(lockfile, file = lockpath) } renv_migrate_packrat_sources <- function(project) { packrat <- asNamespace("packrat") srcdir <- packrat$srcDir(project = project) if (!file.exists(srcdir)) return(TRUE) pattern <- paste0( "^", # start "[^_]+", # package name "_", # separator "\\d+(?:[_.-]\\d+)*", # version "\\.tar\\.gz", # extension "$" # end ) suffixes <- list.files( srcdir, pattern = pattern, recursive = TRUE ) sources <- file.path(srcdir, suffixes) targets <- renv_paths_source("cran", suffixes) keep <- !file.exists(targets) sources <- sources[keep]; targets <- targets[keep] printf("- Migrating package sources from Packrat to renv ... ") copy <- renv_progress_callback(renv_file_copy, length(targets)) mapply(sources, targets, FUN = function(source, target) { ensure_parent_directory(target) copy(source, target) }) writef("Done!") TRUE } renv_migrate_packrat_library <- function(project) { packrat <- asNamespace("packrat") libdir <- packrat$libDir(project = project) if (!file.exists(libdir)) return(TRUE) sources <- list.files(libdir, full.names = TRUE) if (empty(sources)) return(TRUE) targets <- renv_paths_library(basename(sources), project = project) names(targets) <- sources targets <- targets[!file.exists(targets)] if (empty(targets)) { writef("- The renv library is already synchronized with the Packrat library.") return(TRUE) } # copy packages from Packrat to renv private library printf("- Migrating library from Packrat to renv ... ") ensure_parent_directory(targets) copy <- renv_progress_callback(renv_file_copy, length(targets)) enumerate(targets, copy) writef("Done!") # move packages into the cache if (renv_cache_config_enabled(project = project)) { printf("- Moving packages into the renv cache ... ") records <- lapply(targets, renv_description_read) sync <- renv_progress_callback(renv_cache_synchronize, length(targets)) lapply(records, sync, linkable = TRUE) writef("Done!") } TRUE } renv_migrate_packrat_options <- function(project) { packrat <- asNamespace("packrat") opts <- packrat$get_opts(project = project) settings$ignored.packages(opts$ignored.packages, project = project) } renv_migrate_packrat_cache <- function(project) { # find packages in the packrat cache packrat <- asNamespace("packrat") cache <- packrat$cacheLibDir() packages <- list.files(cache, full.names = TRUE) hashes <- list.files(packages, full.names = TRUE) sources <- list.files(hashes, full.names = TRUE) # sanity check: make sure the source folder is an R package ok <- file.exists(file.path(sources, "DESCRIPTION")) sources <- sources[ok] # construct cache target paths targets <- map_chr(sources, renv_cache_path) names(targets) <- sources # only copy to cache target paths that don't exist targets <- targets[!file.exists(targets)] if (empty(targets)) { writef("- The renv cache is already synchronized with the Packrat cache.") return(TRUE) } # cache each installed package if (renv_cache_config_enabled(project = project)) renv_migrate_packrat_cache_impl(targets) TRUE } renv_migrate_packrat_cache_impl <- function(targets) { # attempt to copy packages from Packrat to renv cache printf("- Migrating Packrat cache to renv cache ... ") ensure_parent_directory(targets) copy <- renv_progress_callback(renv_file_copy, length(targets)) result <- enumerate(targets, function(source, target) { status <- catch(copy(source, target)) broken <- inherits(status, "error") reason <- if (broken) conditionMessage(status) else "" list(source = source, target = target, broken = broken, reason = reason) }) writef("Done!") # report failures status <- bind(result) bad <- status[status$broken, ] if (nrow(bad) == 0) return(TRUE) bulletin( "The following packages could not be copied from the Packrat cache:", with(bad, sprintf("%s [%s]", format(source), reason)), "These packages may need to be reinstalled and re-cached." ) } renv_migrate_packrat_infrastructure <- function(project) { unlink(file.path(project, ".Rprofile")) renv_infrastructure_write(project) writef("- renv support infrastructure has been written.") TRUE } renv/R/status.R0000644000176200001440000002626014761163114013105 0ustar liggesusers the$status_running <- FALSE #' Report inconsistencies between lockfile, library, and dependencies #' #' @description #' `renv::status()` reports issues caused by inconsistencies across the project #' lockfile, library, and [dependencies()]. In general, you should strive to #' ensure that `status()` reports no issues, as this maximizes your chances of #' successfully `restore()`ing the project in the future or on another machine. #' #' `renv::load()` will report if any issues are detected when starting an #' renv project; we recommend resolving these issues before doing any #' further work on your project. #' #' See the headings below for specific advice on resolving any issues #' revealed by `status()`. #' #' # Missing packages #' #' `status()` first checks that all packages used by the project are installed. #' This must be done first because if any packages are missing we can't tell for #' sure that a package isn't used; it might be a dependency that we don't know #' about. Once you have resolve any installation issues, you'll need to run #' `status()` again to reveal the next set of potential problems. #' #' There are four possibilities for an uninstalled package: #' #' * If it's used and recorded, call `renv::restore()` to install the version #' specified in the lockfile. #' * If it's used and not recorded, call `renv::install()` to install it #' from CRAN or elsewhere. #' * If it's not used and recorded, call `renv::snapshot()` to #' remove it from the lockfile. #' * If it's not used and not recorded, there's nothing to do. This the most #' common state because you only use a small fraction of all available #' packages in any one project. #' #' If you have multiple packages in an inconsistent state, we recommend #' `renv::restore()`, then `renv::install()`, then `renv::snapshot()`, but #' that also suggests you should be running status more frequently. #' #' # Lockfile vs `dependencies()` #' #' Next we need to ensure that packages are recorded in the lockfile if and #' only if they are used by the project. Fixing issues of this nature only #' requires calling `snapshot()` because there are four possibilities for #' a package: #' #' * If it's used and recorded, it's ok. #' * If it's used and not recorded, call `renv::snapshot()` to add it to the #' lockfile. #' * If it's not used but is recorded, call `renv::snapshot()` to remove #' it from the lockfile. #' * If it's not used and not recorded, it's also ok, as it may be a #' development dependency. #' #' # Out-of-sync sources #' #' The final issue to resolve is any inconsistencies between the version of #' the package recorded in the lockfile and the version installed in your #' library. To fix these issues you'll need to either call `renv::restore()` #' or `renv::snapshot()`: #' #' * Call `renv::snapshot()` if your project code is working. This implies that #' the library is correct and you need to update your lockfile. #' * Call `renv::restore()` if your project code isn't working. This probably #' implies that you have the wrong package versions installed and you need #' to restore from known good state in the lockfile. #' #' If you're not sure which case applies, it's generally safer to call #' `renv::snapshot()`. If you want to rollback to an earlier known good #' status, see [renv::history()] and [renv::revert()]. #' #' # Different R Version #' #' renv will also notify you if the version of R used when the lockfile was #' generated, and the version of R currently in use, do not match. In this #' scenario, you'll need to consider: #' #' - Is the version of R recorded in the lockfile correct? If so, you'll want #' to ensure that version of R is installed and used when working in this #' project. #' #' - Otherwise, you can call `renv::snapshot()` to update the version of R #' recorded in the lockfile, to match the version of R currently in use. #' #' If you'd like to set the version of R recorded in a lockfile independently #' of the version of R currently in use, you can set the `r.version` project #' setting -- see [settings] for more details. #' #' @inherit renv-params #' #' @param library The library paths. By default, the library paths associated #' with the requested project are used. #' #' @param sources Boolean; check that each of the recorded packages have a #' known installation source? If a package has an unknown source, renv #' may be unable to restore it. #' #' @param cache Boolean; perform diagnostics on the global package cache? #' When `TRUE`, renv will validate that the packages installed into the #' cache are installed at the expected + proper locations, and validate the #' hashes used for those storage locations. #' #' @inheritParams dependencies #' #' @return This function is normally called for its side effects, but #' it invisibly returns a list containing the following components: #' #' * `library`: packages in your library. #' * `lockfile`: packages in the lockfile. #' * `synchronized`: are the library and lockfile in sync? #' #' @export #' #' @example examples/examples-init.R status <- function(project = NULL, ..., library = NULL, lockfile = NULL, sources = TRUE, cache = FALSE, dev = FALSE) { renv_scope_error_handler() renv_dots_check(...) renv_snapshot_auto_suppress_next() renv_scope_options(renv.prompt.enabled = FALSE) the$status_running <- TRUE defer(the$status_running <- FALSE) project <- renv_project_resolve(project) renv_project_lock(project = project) # check to see if we've initialized this project if (!renv_status_check_initialized(project, library, lockfile)) { result <- list( library = list(Packages = named(list())), lockfile = list(Packages = named(list())), synchronized = FALSE ) return(invisible(result)) } libpaths <- library %||% renv_libpaths_resolve() lockpath <- lockfile %||% renv_paths_lockfile(project = project) # get all dependencies, including transitive dependencies <- renv_snapshot_dependencies(project, dev = dev) packages <- sort(union(dependencies, "renv")) paths <- renv_package_dependencies(packages, libpaths = libpaths, project = project) packages <- as.character(names(paths)) # read project lockfile lockfile <- if (file.exists(lockpath)) renv_lockfile_read(lockpath) else renv_lockfile_init(project = project) # get lockfile capturing current library state library <- renv_lockfile_create( libpaths = libpaths, type = "all", prompt = FALSE, project = project ) # remove ignored packages ignored <- c( renv_project_ignored_packages(project), renv_packages_base(), if (renv_tests_running()) "renv" ) packages <- setdiff(packages, ignored) renv_lockfile_records(lockfile) <- omit(renv_lockfile_records(lockfile), ignored) renv_lockfile_records(library) <- omit(renv_lockfile_records(library), ignored) synchronized <- all( renv_status_check_consistent(lockfile, library, packages), renv_status_check_synchronized(lockfile, library), renv_status_check_version(lockfile) ) if (sources) { synchronized <- synchronized && renv_status_check_unknown_sources(project, lockfile) } if (cache) renv_status_check_cache(project) if (synchronized) writef("No issues found -- the project is in a consistent state.") else writef("See `?renv::status` for advice on resolving these issues.") result <- list( library = library, lockfile = lockfile, synchronized = synchronized ) invisible(result) } renv_status_check_unknown_sources <- function(project, lockfile) { renv_check_unknown_source(renv_lockfile_records(lockfile), project) } renv_status_check_consistent <- function(lockfile, library, used) { lockfile <- renv_lockfile_records(lockfile) library <- renv_lockfile_records(library) packages <- sort(unique(c(names(library), names(lockfile), used))) status <- data_frame( package = packages, installed = packages %in% names(library), recorded = packages %in% names(lockfile), used = packages %in% used ) ok <- status$installed & (status$used == status$recorded) if (all(ok)) return(TRUE) if (!renv_verbose()) return(FALSE) issues <- status[!ok, , drop = FALSE] missing <- issues$used & !issues$installed if (all(missing)) { bulletin( preamble = "The following package(s) are used in this project, but are not installed:", values = issues$package[missing] ) return(FALSE) } issues$installed <- ifelse(issues$installed, "y", "n") issues$recorded <- ifelse(issues$recorded, "y", "n") issues$used <- ifelse(issues$used, "y", if (any(missing)) "?" else "n") preamble <- "The following package(s) are in an inconsistent state:" writef(preamble) writef() print(issues, row.names = FALSE, right = FALSE) writef() FALSE } renv_status_check_initialized <- function(project, library = NULL, lockfile = NULL) { # only done if library and lockfile are NULL; that is, if the user # is calling `renv::status()` without arguments if (!is.null(library) || !is.null(lockfile)) return(TRUE) # resolve paths to lockfile, primary library path library <- library %||% renv_paths_library(project = project) lockfile <- lockfile %||% renv_paths_lockfile(project = project) # check whether the lockfile + library exist haslib <- all(file.exists(library)) haslock <- file.exists(lockfile) if (haslib && haslock) return(TRUE) # TODO: what about the case where the library exists but no packages are installed? # TODO: should this check for an 'renv/activate.R' script? # TODO: what if a different project is loaded? if (haslib && !haslock) { writef(c( "This project does not contain a lockfile.", "Use `renv::snapshot()` to create a lockfile." )) } else if (!haslib && haslock) { writef(c( "There are no packages installed in the project library.", "Use `renv::restore()` to install the packages defined in lockfile." )) } else { writef(c( "This project does not appear to be using renv.", "Use `renv::init()` to initialize the project." )) } FALSE } renv_status_check_synchronized <- function(lockfile, library) { lockfile <- renv_lockfile_records(lockfile) library <- renv_lockfile_records(library) actions <- renv_lockfile_diff_packages(lockfile, library) rest <- c("upgrade", "downgrade", "crossgrade") if (all(!rest %in% actions)) return(TRUE) pkgs <- names(actions[actions %in% rest]) formatter <- function(lhs, rhs) renv_record_format_pair(lhs, rhs, separator = "!=") renv_pretty_print_records_pair( preamble = "The following package(s) are out of sync [lockfile != library]:", old = lockfile[pkgs], new = library[pkgs], formatter = formatter ) FALSE } renv_status_check_version <- function(lockfile) { version <- lockfile$R$Version if (renv_version_eq(version, getRversion(), n = 2L)) return(TRUE) fmt <- "The lockfile was generated with R %s, but you're using R %s." writef(fmt, version, getRversion()) writef() FALSE } renv_status_check_cache <- function(project) { if (renv_cache_config_enabled(project = project)) renv_cache_diagnose() } renv/R/embed.R0000644000176200001440000001571314731330072012633 0ustar liggesusers#' Capture and re-use dependencies within a `.R`, `.Rmd` or `.qmd` #' #' @description #' Together, `embed()` and `use()` provide a lightweight way to specify and #' restore package versions within a file. `use()` is a lightweight lockfile #' specification that `embed()` can automatically generate and insert into a #' script or document. #' #' Calling `embed()` inspects the dependencies of the specified document then #' generates and inserts a call to `use()` that looks something like this: #' #' ```R #' renv::use( #' "digest@0.6.30", #' "rlang@0.3.4" #' ) #' ``` #' #' Then, when you next run your R script or render your `.Rmd` or `.qmd`, `use()` will: #' #' 1. Create a temporary library path. #' #' 1. Install the requested packages and their recursive dependencies into that #' library. #' #' 1. Activate the library, so it's used for the rest of the script. #' #' ## Manual usage #' #' You can also create calls to `use()` yourself, either specifying the #' packages needed by hand, or by supplying the path to a lockfile, #' `renv::use(lockfile = "/path/to/renv.lock")`. #' #' This can be useful in projects where you'd like to associate different #' lockfiles with different documents, as in a blog where you want each #' post to capture the dependencies at the time of writing. Once you've #' finished writing each, the post, you can use #' `renv::snapshot(lockfile = "/path/to/renv.lock")` #' to "save" the state that was active while authoring that bost, and then use #' `renv::use(lockfile = "/path/to/renv.lock")` in that document to ensure the #' blog post always uses those dependencies onfuture renders. #' #' `renv::use()` is inspired in part by the [groundhog](https://groundhogr.com/) #' package, which also allows one to specify a script's \R package requirements #' within that same \R script. #' #' @inherit renv-params #' #' @param path #' The path to an \R or R Markdown script. The default will use the current #' document, if running within RStudio. #' #' @param lockfile #' The path to an renv lockfile. When `NULL` (the default), the project #' lockfile will be read (if any); otherwise, a new lockfile will be generated #' from the current library paths. #' #' @export embed <- function(path = NULL, ..., lockfile = NULL, project = NULL) { path <- path %||% renv_embed_path() ext <- tolower(fileext(path)) method <- case( ext == ".r" ~ renv_embed_r, ext == ".rmd" ~ renv_embed_rmd, ext == ".qmd" ~ renv_embed_rmd ) if (is.null(method)) { fmt <- "don't know how to embed lockfile into file %s" stopf(fmt, renv_path_pretty(path)) } method( path = path, lockfile = lockfile, project = project, ... ) } renv_embed_path <- function() { tryCatch( renv_embed_path_impl(), error = function(e) NULL ) } renv_embed_path_impl <- function() { rstudio <- as.environment("tools:rstudio") rstudio$.rs.api.documentPath() } renv_embed_create <- function(path = NULL, lockfile = NULL, project = NULL) { # generate lockfile project <- renv_project_resolve(project) lockfile <- renv_embed_lockfile_resolve(lockfile, project) # figure out recursive package dependencies deps <- renv_dependencies_impl(path) packages <- sort(unique(deps$Package)) all <- renv_package_dependencies(packages) # keep only matched records lockfile$Packages <- keep(lockfile$Packages, c("renv", names(all))) # write compact use statement renv_lockfile_compact(lockfile) } renv_embed_r <- function(path, ..., lockfile = NULL, project = NULL) { # resolve project project <- renv_project_resolve(project) # read file contents contents <- readLines(path, warn = FALSE, encoding = "UTF-8") # generate embed embed <- renv_embed_create( path = path, lockfile = lockfile, project = project ) # check for existing 'renv::use' statement pattern <- "^\\s*(?:renv:{2,3})?use\\(\\s*$" index <- grep(pattern, contents, perl = TRUE) # if we don't have an index, just insert at start if (empty(index)) { contents <- c(embed, "", contents) writeLines(contents, con = path) return(TRUE) } # otherwise, try to replace an existing embedded lockfile start <- index # find the end of the block n <- length(contents) lines <- grep("^\\s*\\)\\s*$", contents, perl = TRUE) end <- min(lines[lines > start], n + 1L) # insert new lockfile contents <- c( head(contents, n = start - 1L), embed, tail(contents, n = n - end) ) writeLines(contents, con = path) return(TRUE) } renv_embed_create_rmd <- function(path = NULL, lockfile = NULL, project = NULL) { # create lockfile project <- renv_project_resolve(project) lockfile <- renv_embed_lockfile_resolve(lockfile, project) # create embed embed <- renv_embed_create( path = path, lockfile = lockfile, project = project ) # return embed c("```{r lockfile, include=FALSE}", embed, "```") } renv_embed_rmd <- function(path, ..., lockfile = NULL, project = NULL) { # resolve project project <- renv_project_resolve(project) # read file contents contents <- readLines(path, warn = FALSE, encoding = "UTF-8") # generate embed embed <- renv_embed_create_rmd( path = path, lockfile = lockfile, project = project ) # check for existing renv.lock in file # if it exists, we'll want to replace at this location; # otherwise, insert at end of document header <- "^\\s*```{r lockfile" footer <- "```" start <- grep(header, contents, perl = TRUE) # if we don't have an index, insert after YAML header (if any) if (empty(start)) { bounds <- which(trimws(contents) == "---") all <- if (length(bounds) >= 2) { index <- bounds[[2L]] c( head(contents, n = index), "", embed, "", tail(contents, n = length(contents) - index) ) } else { c(embed, "", contents) } writeLines(all, con = path) return(TRUE) } # otherwise, try to replace an existing embedded lockfile ends <- which(contents == footer) end <- min(ends[ends > start]) # insert new lockfile contents <- c( head(contents, n = start - 1L), embed, tail(contents, n = length(contents) - end) ) writeLines(contents, con = path) return(TRUE) } renv_embed_lockfile_resolve <- function(lockfile, project) { # if lockfile is character, assume it's the path to a lockfile if (is.character(lockfile)) return(renv_lockfile_read(lockfile)) # if lockfile is not NULL, assume lockfile object if (!is.null(lockfile)) return(lockfile) # check for lockfile in project path <- renv_lockfile_path(project) if (file.exists(path)) return(renv_lockfile_read(path)) # no lockfile available; just snapshot snapshot(project = project, lockfile = NULL) } renv/R/truthy.R0000644000176200001440000000153314731330073013112 0ustar liggesusers truthy <- function(value, default = FALSE) { # https://github.com/rstudio/renv/issues/1558 if (is.call(value)) { value <- tryCatch(renv_dependencies_eval(value), error = identity) if (inherits(value, "error")) return(default) } # skip empty vectors if (length(value) == 0L) return(default) # handle symbols if (is.symbol(value)) value <- as.character(value) # only look at first element in vector value <- value[[1L]] # handle some non-character types up-front if (is.call(value)) return(default) else if (is.na(value)) return(default) else if (!is.character(value)) return(as.logical(value)) # check for known truthy / falsy values if (value %in% c("TRUE", "True", "true", "T", "1")) TRUE else if (value %in% c("FALSE", "False", "false", "F", "0")) FALSE else default } renv/R/snapshot.R0000644000176200001440000011515114761163114013417 0ustar liggesusers # controls whether hashes are computed when computing a snapshot # can be scoped to FALSE when hashing is not necessary the$auto_snapshot_hash <- TRUE #' Record current state of the project library in the lockfile #' #' @description #' Call `renv::snapshot()` to update a [lockfile] with the current state of #' dependencies in the project library. The lockfile can be used to later #' [restore] these dependencies as required. #' #' It's also possible to call `renv::snapshot()` with a non-renv project, #' in which case it will record the current state of dependencies in the #' current library paths. This makes it possible to [restore] the current packages, #' providing lightweight portability and reproducibility without isolation. #' #' If you want to automatically snapshot after each change, you can #' set `config$config$auto.snapshot(TRUE)` -- see `?config` for more details. #' #' # Snapshot types #' #' Depending on how you prefer to manage your \R package dependencies, you may #' want to enable an alternate snapshot type.. The types available are as follows: #' #' \describe{ #' #' \item{`"implicit"`}{ #' (The default) Capture only packages which appear to be used in your project, #' as determined by `renv::dependencies()`. This ensures that only the packages #' actually required by your project will enter the lockfile; the downside #' if it might be slow if your project contains a large number of files. #' If speed becomes an issue, you might consider using `.renvignore` files to #' limit which files renv uses for dependency discovery, or switching to #' explicit mode, as described next. #' } #' #' \item{`"explicit"`}{ #' Only capture packages which are explicitly listed in the project #' `DESCRIPTION` file. This workflow is recommended for users who wish to #' manage their project's \R package dependencies directly, and can be used #' for both package and non-package \R projects. Packages used in this manner #' should be recorded in either the `Depends` or `Imports` field of the #' `DESCRIPTION` file. #' } #' #' \item{`"all"`}{ #' Capture all packages within the active \R libraries in the lockfile. #' This is the quickest and simplest method, but may lead to undesired #' packages (e.g. development dependencies) entering the lockfile. #' } #' #' \item{`"custom"`}{ #' Like `"implicit"`, but use a custom user-defined filter instead. The filter #' should be specified by the \R option `renv.snapshot.filter`, and should #' either be a character vector naming a function (e.g. `"package::method"`), #' or be a function itself. The function should only accept one argument (the #' project directory), and should return a vector of package names to include #' in the lockfile. #' } #' #' } #' #' You can change the snapshot type for the current project with [settings()]. #' For example, the following code will switch to using `"explicit"` snapshots: #' #' ``` #' renv::settings$snapshot.type("explicit") #' ``` #' #' When the `packages` argument is set, `type` is ignored, and instead only the #' requested set of packages, and their recursive dependencies, will be written #' to the lockfile. #' #' @inherit renv-params #' #' @param library The \R libraries to snapshot. When `NULL`, the active \R #' libraries (as reported by `.libPaths()`) are used. #' #' @param lockfile The location where the generated lockfile should be written. #' By default, the lockfile is written to a file called `renv.lock` in the #' project directory. When `NULL`, the lockfile (as an \R object) is returned #' directly instead. #' #' @param type The type of snapshot to perform: #' * `"implicit"`, (the default), uses all packages captured by [dependencies()]. #' * `"explicit"` uses packages recorded in `DESCRIPTION`. #' * `"all"` uses all packages in the project library. #' * `"custom"` uses a custom filter. #' #' See **Snapshot types** below for more details. #' #' @inheritParams dependencies #' #' @param repos The \R repositories to be recorded in the lockfile. Defaults #' to the currently active package repositories, as retrieved by #' `getOption("repos")`. #' #' #' @param packages A vector of packages to be included in the lockfile. When #' `NULL` (the default), all packages relevant for the type of snapshot being #' performed will be included. When set, the `type` argument is ignored. #' Recursive dependencies of the specified packages will be added to the #' lockfile as well. #' #' @param exclude A vector of packages to be explicitly excluded from the lockfile. #' Note that transitive package dependencies will always be included, to avoid #' potentially creating an incomplete / non-functional lockfile. #' #' @param update Boolean; if the lockfile already exists, then attempt to update #' that lockfile without removing any prior package records. #' #' @param force Boolean; force generation of a lockfile even when pre-flight #' validation checks have failed? #' #' @param reprex Boolean; generate output appropriate for embedding the lockfile #' as part of a [reprex](https://www.tidyverse.org/help/#reprex)? #' #' @return The generated lockfile, as an \R object (invisibly). Note that #' this function is normally called for its side effects. #' #' #' @seealso More on handling package [dependencies()] #' @family reproducibility #' #' @export #' #' @example examples/examples-init.R snapshot <- function(project = NULL, ..., library = NULL, lockfile = paths$lockfile(project = project), type = settings$snapshot.type(project = project), dev = FALSE, repos = getOption("repos"), packages = NULL, exclude = NULL, prompt = interactive(), update = FALSE, force = FALSE, reprex = FALSE) { renv_consent_check() renv_scope_error_handler() renv_dots_check(...) renv_snapshot_auto_suppress_next() project <- renv_project_resolve(project) renv_project_lock(project = project) renv_scope_verbose_if(prompt) repos <- renv_repos_validate(repos) renv_scope_options(repos = repos) # set up .renvignore defensively renv_load_cache_renvignore(project = project) if (!is.null(lockfile)) renv_activate_prompt("snapshot", library, prompt, project) libpaths <- renv_path_normalize(library %||% renv_libpaths_all()) if (config$snapshot.validate()) renv_snapshot_preflight(project, libpaths) # when packages is set, we treat this as an 'all' type snapshot, but # with explicit package filters turned on if (!is.null(packages)) { if (!missing(type)) { fmt <- "packages argument is set; type argument %s will be ignored" warningf(fmt, stringify(type)) } type <- "packages" } alt <- new <- renv_lockfile_create( project = project, type = type, libpaths = libpaths, packages = packages, exclude = exclude, prompt = prompt, force = force, dev = dev ) if (is.null(lockfile)) return(new) # if running as part of 'reprex', then render output inline if (reprex) return(renv_snapshot_reprex(new)) # check for missing dependencies and warn if any are discovered # (note: use 'new' rather than 'alt' here as we don't want to attempt # validation on uninstalled packages) valid <- renv_snapshot_validate(project, new, libpaths) renv_snapshot_validate_report(valid, prompt, force) # get prior lockfile state; be robust against invalid lockfiles old <- tryCatch( if (file.exists(lockfile)) renv_lockfile_read(lockfile), error = function(cnd) { extra <- "The report below will omit lockfile package versions." message <- paste(conditionMessage(cnd), extra, sep = "\n") warning(message, call. = FALSE) list() } ) if (length(old)) { # preserve records from alternate OSes in lockfile alt <- renv_snapshot_preserve(old, new) # check if there are any changes in the lockfile diff <- renv_lockfile_diff(old, alt) if (empty(diff)) { writef("- The lockfile is already up to date.") return(renv_snapshot_successful(alt, prompt, project)) } } # update new reference new <- alt # if we're only updating the lockfile, then merge any missing records # from 'old' back into 'new' if (update) for (package in names(old$Packages)) new$Packages[[package]] <- new$Packages[[package]] %||% old$Packages[[package]] # report actions to the user actions <- renv_lockfile_diff_packages(old, new) if (prompt || renv_verbose()) renv_snapshot_report_actions(actions, old, new) # request user confirmation cancel_if(length(actions) && file.exists(lockfile) && prompt && !proceed()) # write it out ensure_parent_directory(lockfile) renv_lockfile_write(new, file = lockfile) # ensure the lockfile is .Rbuildignore-d renv_infrastructure_write_rbuildignore(project) # ensure the activate script is up-to-date renv_infrastructure_write_activate(project, create = FALSE) # return new records renv_snapshot_successful(new, prompt, project) } renv_snapshot_preserve <- function(old, new) { records <- filter(old$Packages, renv_snapshot_preserve_impl) if (length(records)) new$Packages[names(records)] <- records new } renv_snapshot_preserve_impl <- function(record) { ostype <- tolower(record[["OS_type"]] %||% "") if (!nzchar(ostype)) return(FALSE) altos <- if (renv_platform_unix()) "windows" else "unix" identical(ostype, altos) } renv_snapshot_preflight <- function(project, libpaths) { lapply(libpaths, renv_snapshot_preflight_impl, project = project) } renv_snapshot_preflight_impl <- function(project, library) { renv_snapshot_preflight_library_exists(project, library) } renv_snapshot_preflight_library_exists <- function(project, library) { # check that we have a directory type <- renv_file_type(library, symlinks = FALSE) if (type == "directory") return(TRUE) # if the file exists but isn't a directory, fail if (nzchar(type)) { fmt <- "library '%s' exists but is not a directory" stopf(fmt, renv_path_aliased(library)) } # the directory doesn't exist; perhaps the user hasn't called init if (identical(library, renv_paths_library(project = project))) { fmt <- "project '%s' has no private library -- have you called `renv::init()`?" stopf(fmt, renv_path_aliased(project)) } # user tried to snapshot arbitrary but missing path fmt <- "library '%s' does not exist; cannot proceed" stopf(fmt, renv_path_aliased(library)) } renv_snapshot_validate <- function(project, lockfile, libpaths) { # allow user to disable snapshot validation, just in case enabled <- config$snapshot.validate() if (!enabled) return(TRUE) methods <- list( renv_snapshot_validate_bioconductor, renv_snapshot_validate_dependencies_available, renv_snapshot_validate_dependencies_compatible, renv_snapshot_validate_sources ) ok <- map_lgl(methods, function(method) { tryCatch( method(project, lockfile, libpaths), error = function(e) { warning(e); FALSE } ) }) all(ok) } renv_snapshot_validate_report <- function(valid, prompt, force) { # nothing to do if everything is valid if (valid) { dlog("snapshot", "passed pre-flight validation checks") return(TRUE) } # if we're forcing snapshot, ignore the failures if (force) { dlog("snapshot", "ignoring error in pre-flight validation checks as 'force = TRUE'") return(TRUE) } # if we were called during init, ignore failures if (the$init_running) { dlog("snapshot", "called during init; ignoring error in pre-flight validation checks") return(TRUE) } # in interactive sessions, if 'prompt' is set, then ask the user # if they would like to proceed if (interactive() && !testing() && prompt) { cancel_if(!proceed()) return(TRUE) } # otherwise, bail on error (need to use 'force = TRUE') stop("aborting snapshot due to pre-flight validation failure") } # nocov start renv_snapshot_validate_bioconductor <- function(project, lockfile, libpaths) { ok <- TRUE # check whether any packages are installed from Bioconductor records <- renv_lockfile_records(lockfile) sources <- extract_chr(records, "Source") if (!"Bioconductor" %in% sources) return(ok) # check for BiocManager or BiocInstaller package <- renv_bioconductor_manager() if (!package %in% names(records)) { text <- c( "One or more Bioconductor packages are used in your project,", "but the %s package is not available.", "", "Consider installing %s before snapshot.", "" ) caution(text, package) ok <- FALSE } # check that Bioconductor packages are from correct release version <- lockfile$Bioconductor$Version %||% renv_bioconductor_version(project = project) biocrepos <- renv_bioconductor_repos(version = version) renv_scope_options(repos = biocrepos) # collect Bioconductor records bioc <- records %>% filter(function(record) renv_record_source(record) == "bioconductor") %>% map(function(record) record[c("Package", "Version")]) %>% bind() # collect latest versions of these packages bioc$Latest <- vapply(bioc$Package, function(package) { entry <- catch(renv_available_packages_latest(package)) if (inherits(entry, "error")) return("") entry$Version }, FUN.VALUE = character(1)) # check for version mismatches (allow mismatch in minor version) bioc$Mismatch <- mapply(function(current, latest) { if (identical(latest, "")) return(TRUE) current <- renv_version_maj_min(current) latest <- renv_version_maj_min(latest) current != latest }, bioc$Version, bioc$Latest) bad <- bioc[bioc$Mismatch, ] if (nrow(bad)) { fmt <- "%s [installed %s != latest %s]" msg <- sprintf(fmt, format(bad$Package), format(bad$Version), bad$Latest) bulletin( "The following Bioconductor packages appear to be from a separate Bioconductor release:", msg, c( "renv may be unable to restore these packages.", paste("Bioconductor version:", version) ) ) ok <- FALSE } ok } # nocov end renv_snapshot_validate_dependencies_available <- function(project, lockfile, libpaths) { # use library to collect package dependency versions records <- renv_lockfile_records(lockfile) packages <- extract_chr(records, "Package") locs <- find.package(packages, lib.loc = libpaths, quiet = TRUE) deps <- bapply(locs, renv_dependencies_discover_description) if (empty(deps)) return(TRUE) splat <- split(deps, deps$Package) # exclude base R packages splat <- splat[renv_vector_diff(names(splat), renv_packages_base())] # check for required packages not currently installed requested <- names(splat) missing <- renv_vector_diff(requested, packages) if (empty(missing)) return(TRUE) # exclude ignored packages missing <- renv_vector_diff(missing, settings$ignored.packages(project = project)) if (empty(missing)) return(TRUE) usedby <- map_chr(missing, function(package) { revdeps <- sort(unique(basename(deps$Source)[deps$Package == package])) items <- revdeps; limit <- 3L if (length(revdeps) > limit) { rest <- length(revdeps) - limit suffix <- paste("and", length(revdeps) - 3L, plural("other", rest)) items <- c(revdeps[seq_len(limit)], suffix) } paste(items, collapse = ", ") }) bulletin( "The following required packages are not installed:", sprintf("%s [required by %s]", format(missing), usedby), "Consider reinstalling these packages before snapshotting the lockfile." ) FALSE } renv_snapshot_validate_dependencies_compatible <- function(project, lockfile, libpaths) { # use library to collect package dependency versions records <- renv_lockfile_records(lockfile) packages <- extract_chr(records, "Package") locs <- find.package(packages, lib.loc = libpaths, quiet = TRUE) deps <- bapply(locs, renv_dependencies_discover_description) if (empty(deps)) return(TRUE) splat <- split(deps, deps$Package) # exclude base R packages splat <- splat[renv_vector_diff(names(splat), renv_packages_base())] # collapse requirements for each package bad <- enumerate(splat, function(package, requirements) { # skip NULL records (should be handled above) record <- records[[package]] if (is.null(record)) return(NULL) version <- record$Version # drop packages without explicit version requirement requirements <- requirements[nzchar(requirements$Require), ] if (nrow(requirements) == 0) return(NULL) # add in requested version requirements$Requested <- version # generate expressions to evaluate fmt <- "package_version('%s') %s package_version('%s')" code <- with(requirements, sprintf(fmt, Requested, Require, Version)) parsed <- parse(text = code) ok <- map_lgl(parsed, eval, envir = baseenv()) # return requirements that weren't satisfied requirements[!ok, ] }) bad <- bind(bad) if (empty(bad)) return(TRUE) package <- basename(bad$Source) requires <- sprintf("%s (%s %s)", bad$Package, bad$Require, bad$Version) request <- bad$Requested fmt <- "%s requires %s, but version %s is installed" txt <- sprintf(fmt, format(package), format(requires), format(request)) bulletin( "The following package(s) have unsatisfied dependencies:", txt, "Consider updating the required dependencies as appropriate." ) FALSE } renv_snapshot_validate_sources <- function(project, lockfile, libpaths) { records <- renv_lockfile_records(lockfile) renv_check_unknown_source(records, project) } # NOTE: if packages are found in multiple libraries, # then the first package found in the library paths is # kept and others are discarded renv_snapshot_libpaths <- function(libpaths = NULL, project = NULL) { dynamic( key = list(libpaths = libpaths, project = project), value = renv_snapshot_libpaths_impl(libpaths, project) ) } renv_snapshot_libpaths_impl <- function(libpaths = NULL, project = NULL) { records <- uapply( libpaths, renv_snapshot_library, project = project ) dupes <- duplicated(names(records)) records[!dupes] } renv_snapshot_library <- function(library = NULL, records = TRUE, project = NULL) { # list packages in the library library <- renv_path_normalize(library %||% renv_libpaths_active()) paths <- list.files(library, full.names = TRUE) # remove 'base' packages paths <- paths[!basename(paths) %in% renv_packages_base()] # remove ignored packages ignored <- renv_project_ignored_packages(project = project) paths <- paths[!basename(paths) %in% ignored] # remove paths that are not valid package names pattern <- sprintf("^%s$", .standard_regexps()$valid_package_name) paths <- paths[grep(pattern, basename(paths))] # validate the remaining set of packages valid <- renv_snapshot_check(paths) # remove duplicates (so only first package entry discovered in library wins) duplicated <- duplicated(basename(valid)) packages <- valid[!duplicated] # early exit if we're just collecting the list of packages if (!records) return(basename(packages)) # snapshot description files descriptions <- file.path(packages, "DESCRIPTION") records <- lapply(descriptions, compose(catch, renv_snapshot_description)) names(records) <- basename(packages) # report any snapshot failures broken <- filter(records, inherits, what = "error") if (length(broken)) { messages <- map_chr(broken, conditionMessage) text <- sprintf("'%s': %s", names(broken), messages) bulletin( "renv was unable to snapshot the following packages:", text, "These packages will likely need to be repaired and / or reinstalled." ) stopf("snapshot of library %s failed", renv_path_pretty(library)) } # name results and return names(records) <- map_chr(records, `[[`, "Package") records } renv_snapshot_check <- function(paths) { paths <- grep("00LOCK", paths, invert = TRUE, value = TRUE) paths <- renv_snapshot_check_broken_link(paths) paths <- renv_snapshot_check_tempfile(paths) paths <- renv_snapshot_check_missing_description(paths) paths } renv_snapshot_check_broken_link <- function(paths) { broken <- !file.exists(paths) if (!any(broken)) return(paths) bulletin( "The following package(s) have broken symlinks into the cache:", basename(paths)[broken], "Use `renv::repair()` to try and reinstall these packages." ) paths[!broken] } renv_snapshot_check_tempfile <- function(paths) { names <- basename(paths) missing <- grepl("^file(?:\\w){12}", names) if (!any(missing)) return(paths) bulletin( "The following folder(s) appear to be left-over temporary directories:", map_chr(paths[missing], renv_path_pretty), "Consider removing these folders from your R library." ) paths[!missing] } renv_snapshot_check_missing_description <- function(paths) { desc <- file.path(paths, "DESCRIPTION") missing <- !file.exists(desc) if (!any(missing)) return(paths) bulletin( "The following package(s) are missing their DESCRIPTION files:", sprintf("%s [%s]", format(basename(paths[missing])), paths[missing]), c( "These may be left over from a prior, failed installation attempt.", "Consider removing or reinstalling these packages." ) ) paths[!missing] } renv_snapshot_description <- function(path = NULL, package = NULL) { # resolve path path <- path %||% renv_package_find(package, lib.loc = renv_libpaths_all()) if (!nzchar(path)) stopf("package '%s' is not installed", package) # read and snapshot DESCRIPTION file dcf <- renv_description_read(path, package) renv_snapshot_description_impl(dcf, path) } renv_snapshot_description_impl <- function(dcf, path = NULL) { version <- getOption("renv.lockfile.version", default = 2L) if (version == 1L) renv_snapshot_description_impl_v1(dcf, path) else if (version == 2L) renv_snapshot_description_impl_v2(dcf, path) else stopf("unsupported lockfile version '%s'", format(version)) } renv_snapshot_description_impl_v1 <- function(dcf, path = NULL) { # figure out the package source source <- renv_snapshot_description_source(dcf) dcf[names(source)] <- source # check for required fields required <- c("Package", "Version", "Source") missing <- renv_vector_diff(required, names(dcf)) if (length(missing)) { fmt <- "required fields %s missing from DESCRIPTION at path '%s'" stopf(fmt, paste(shQuote(missing), collapse = ", "), path %||% "") } # if this is a standard remote for a bioconductor package, # remove the other remote fields bioc <- !is.null(dcf[["biocViews"]]) && identical(dcf[["RemoteType"]], "standard") if (bioc) { fields <- grep("^Remote(?!s)", names(dcf), perl = TRUE, invert = TRUE) dcf <- dcf[fields] } # generate a hash if we can dcf[["Hash"]] <- if (the$auto_snapshot_hash) { if (is.null(path)) renv_hash_record(dcf) else renv_hash_description(path) } # generate a Requirements field -- primarily for use by 'pak' fields <- c("Depends", "Imports", "LinkingTo") deps <- bind(map(dcf[fields], renv_description_parse_field)) all <- unique(csort(unlist(deps$Package))) dcf[["Requirements"]] <- all # get remotes fields remotes <- local({ # if this seems to be a cran-like record, only keep remotes # when RemoteSha appears to be a hash (e.g. for r-universe) # note that RemoteSha may be a package version when installed # by e.g. pak if (renv_record_cranlike(dcf)) { sha <- dcf[["RemoteSha"]] if (is.null(sha) || nchar(sha) < 40L) return(character()) } # grab the relevant remotes git <- grep("^git", names(dcf), value = TRUE) remotes <- grep("^Remote(?!s)", names(dcf), perl = TRUE, value = TRUE) # don't include 'RemoteRef' if it's a non-informative remote if (identical(dcf[["RemoteRef"]], "HEAD")) remotes <- setdiff(remotes, "RemoteRef") c(git, remotes) }) # only keep relevant fields extra <- c("Repository", "OS_type") all <- c(required, extra, remotes, "Requirements", "Hash") keep <- renv_vector_intersect(all, names(dcf)) # return as list as.list(dcf[keep]) } renv_snapshot_description_impl_v2 <- function(dcf, path) { # figure out the package source source <- renv_snapshot_description_source(dcf) dcf[names(source)] <- source # check for required fields required <- c("Package", "Version", "Source") missing <- renv_vector_diff(required, names(dcf)) if (length(missing)) { fmt <- "required fields %s missing from DESCRIPTION at path '%s'" stopf(fmt, paste(shQuote(missing), collapse = ", "), path %||% "") } # if this is a standard remote for a bioconductor package, # remove the other remote fields bioc <- !is.null(dcf[["biocViews"]]) && identical(dcf[["RemoteType"]], "standard") if (bioc) { fields <- grep("^Remote(?!s)", names(dcf), perl = TRUE, invert = TRUE) dcf <- dcf[fields] } # drop fields that normally only appear in binary packages, # or fields which might differ from user to user, or might # differ depending on the mirror used for publication ignore <- c("Archs", "Built", "Date/Publication", "File", "MD5sum", "Packaged") dcf[ignore] <- NULL # drop remote fields for cranlike remotes if (renv_record_cranlike(dcf)) { sha <- dcf[["RemoteSha"]] if (is.null(sha) || nchar(sha) < 40L) { remotes <- grep("^Remote", names(dcf), perl = TRUE, value = TRUE) dcf[remotes] <- NULL } } # drop the old Github remote fields github <- grepl("^Github", names(dcf), perl = TRUE) dcf <- dcf[!github] # split fields which are normally declared as lists of packages depfields <- c("Depends", "Imports", "Suggests", "LinkingTo", "Enhances") for (depfield in depfields) { if (!is.null(dcf[[depfield]])) { fields <- strsplit(dcf[[depfield]], ",", fixed = TRUE) dcf[[depfield]] <- as.list(trimws(fields[[1L]])) } } # reorganize fields a bit dcf <- dcf[c(required, setdiff(names(dcf), required))] # return as list as.list(dcf) } renv_snapshot_description_source_custom <- function(dcf) { # only proceed for cranlike remotes if (!renv_record_cranlike(dcf)) return(NULL) # check for a declared repository URL remoterepos <- dcf[["RemoteRepos"]] if (is.null(remoterepos)) return(NULL) # if this package appears to be installed from Bioconductor, skip biocviews <- dcf[["biocViews"]] if (!is.null(biocviews)) return(NULL) # if this package appears to have been installed from a # repository which we have knowledge of, skip repos <- as.list(getOption("repos")) repository <- dcf[["Repository"]] if (!is.null(repository) && repository %in% names(repos)) return(NULL) # check whether this repository is already in use; # if so, we can skip declaring it name <- dcf[["RemoteReposName"]] declared <- if (is.null(name)) remoterepos %in% repos else name %in% names(repos) if (declared) return(NULL) list(Source = "Repository", Repository = remoterepos) } renv_snapshot_description_source <- function(dcf) { # check for packages installed from a repository not currently # encoded as part of the user's repository option, and include if required source <- renv_snapshot_description_source_custom(dcf) if (!is.null(source)) return(source) # check for a custom declared remote type if (!renv_record_cranlike(dcf)) { type <- dcf[["RemoteType"]] %||% "standard" return(list(Source = alias(type))) } # packages from Bioconductor are normally tagged with a 'biocViews' entry; # use that to infer a Bioconductor source if (!is.null(dcf[["biocViews"]])) return(list(Source = "Bioconductor")) # check for a declared repository repository <- dcf[["RemoteReposName"]] %||% dcf[["Repository"]] if (!is.null(repository)) return(list(Source = "Repository", Repository = repository)) # check for a valid package name package <- dcf[["Package"]] if (is.null(package)) return(list(Source = "unknown")) # if this is running as part of the synchronization check, skip CRAN queries # https://github.com/rstudio/renv/issues/812 if (the$project_synchronized_check_running) return(list(Source = "unknown")) # check to see if this is a base / recommended package; if so, assume that # the package was installed from CRAN at this point # # normally these would be caught by the 'Repository' check above, but it # seems like, in some cases, base / recommended packages might be installed # without those available # # https://github.com/rstudio/renv/issues/1948#issuecomment-2245134768 pkgs <- installed_packages( lib.loc = c(.Library, .Library.site), priority = c("base", "recommended"), field = "Package" ) if (package %in% pkgs) return(list(Source = "Repository", Repository = "CRAN")) # NOTE: this is sort of a hack that allows renv to declare packages which # appear to be installed from sources, but are actually available on the # active R package repositories, as though they were retrieved from that # repository. however, this is often what users intend, especially if # they haven't configured their repository to tag the packages it makes # available with the 'Repository:' field in the DESCRIPTION file. # # still, this has the awkward side-effect of a package's source potentially # depending on what repositories happen to be active at the time of snapshot, # so it'd be nice to tighten up the logic here if possible # # NOTE: local sources are also searched here as part of finding the 'latest' # available package, so we need to handle local packages discovered here tryCatch( renv_snapshot_description_source_hack(package, dcf), error = function(e) list(Source = "unknown") ) } renv_snapshot_description_source_hack <- function(package, dcf) { # check cellar for (type in renv_package_pkgtypes()) { cellar <- renv_available_packages_cellar(type) if (package %in% cellar$Package) return(list(Source = "Cellar")) } # check available packages latest <- catch(renv_available_packages_latest(package)) if (is.null(latest) || inherits(latest, "error")) return(list(Source = "unknown")) # check version; use unknown if it's too new if (renv_version_gt(dcf[["Version"]], latest[["Version"]])) return(list(Source = "unknown")) # ok, this package appears to be from a package repository list(Source = "Repository", Repository = latest[["Repository"]]) } # nocov start renv_snapshot_report_actions <- function(actions, old, new) { if (!renv_verbose()) return(invisible()) if (length(actions)) { lhs <- renv_lockfile_records(old) rhs <- renv_lockfile_records(new) renv_pretty_print_records_pair( "The following package(s) will be updated in the lockfile:", lhs[names(lhs) %in% names(actions)], rhs[names(rhs) %in% names(actions)] ) } oldr <- old$R$Version newr <- new$R$Version rdiff <- renv_version_compare(oldr %||% "0", newr %||% "0") if (rdiff != 0L) { n <- max(nchar(names(actions)), 0) fmt <- paste("-", format("R", width = n), " ", "[%s -> %s]") placeholder <- renv_record_placeholder() msg <- sprintf(fmt, oldr %||% placeholder, newr %||% placeholder) writef( c("The version of R recorded in the lockfile will be updated:", msg, "") ) } } # nocov end # compute the package dependencies inferred for a project, # respecting the snapshot type selected (or currently configured) # for the associated project renv_snapshot_dependencies <- function(project, type = NULL, dev = FALSE) { type <- type %||% settings$snapshot.type(project = project) packages <- dynamic( list(project = project, type = type, dev = dev), renv_snapshot_dependencies_impl(project, type, dev) ) if (!renv_tests_running()) packages <- unique(c(packages, "renv")) packages } renv_snapshot_dependencies_impl <- function(project, type = NULL, dev = FALSE) { if (type %in% "all") { packages <- installed_packages(field = "Package") return(setdiff(packages, renv_packages_base())) } if (type %in% "custom") { filter <- renv_snapshot_filter_custom_resolve() return(filter(project)) } path <- case( type %in% c("packrat", "implicit") ~ project, type %in% "explicit" ~ file.path(project, "DESCRIPTION"), ~ { fmt <- "internal error: unhandled snapshot type '%s' in %s" stopf(fmt, type, stringify(sys.call())) } ) # avoid errors when the project directory, or DESCRIPTION file, # does not exist (imply no dependencies) # # https://github.com/rstudio/renv/issues/1949 if (!file.exists(path)) return(character()) # count the number of files in each directory, so we can report # to the user if we scanned a folder containing many files count <- integer() packages <- withCallingHandlers( renv_dependencies_impl( path = path, root = project, field = "Package", errors = config$dependency.errors(), dev = dev ), # require user confirmation to proceed if there's a reported error renv.dependencies.problems = function(cnd) { if (identical(config$dependency.errors(), "ignored")) return() if (interactive() && !proceed()) cancel() }, # collect information about folders containing lots of files renv.dependencies.count = function(cnd) { count[[cnd$data$path]] <<- cnd$data$count }, # notify the user if we took a long time to discover dependencies renv.dependencies.elapsed_time = function(cnd) { # only relevant for implicit-type snapshots if (!type %in% c("packrat", "implicit")) return() # check for timeout elapsed <- cnd$data limit <- getOption("renv.dependencies.elapsed_time_threshold", default = 10L) if (elapsed < limit) return() # tally up directories with lots of files count <- count[order(count)] count <- count[count >= 200] # report to user lines <- c( "", "NOTE: Dependency discovery took %s during snapshot.", "Consider using .renvignore to ignore files, or switching to explicit snapshots.", "See `?renv::dependencies` for more information.", if (length(count)) c( "", sprintf("- %s: %s", format(names(count)), nplural("file", count)) ), "" ) # force output in this scope renv_scope_caution(TRUE) caution(lines, renv_difftime_format(elapsed)) } ) unique(packages) } # compute package records from the provided library paths, # normally to be included as part of an renv lockfile renv_snapshot_packages <- function(packages, libpaths, project) { ignored <- c( renv_packages_base(), renv_project_ignored_packages(project = project), if (renv_tests_running()) "renv" ) callback <- function(package, location, project) { if (nzchar(location) && !package %in% ignored) return(location) } # expand package dependency tree paths <- renv_package_dependencies( packages = packages, libpaths = libpaths, callback = callback, project = project ) # keep only packages with known locations paths <- paths %>% filter(is.character) %>% filter(nzchar) # diagnose issues with the scanned packages paths <- renv_snapshot_check(paths) # now, snapshot the remaining packages map(paths, renv_snapshot_description) } renv_snapshot_report_missing <- function(missing, type) { missing <- setdiff(missing, "renv") if (empty(missing)) return(invisible()) preamble <- "The following required packages are not installed:" postamble <- c( "Packages must first be installed before renv can snapshot them.", if (type %in% "explicit") "If these packages are no longer required, consider removing them from your DESCRIPTION file." else "Use `renv::dependencies()` to see where this package is used in your project." ) bulletin( preamble = preamble, values = sort(unique(missing)), postamble = postamble ) # only prompt the user to install if a restart is available restart <- findRestart("renv_recompute_records") if (is.null(restart)) return(invisible()) choices <- c( snapshot = "Snapshot, just using the currently installed packages.", install = "Install the packages, then snapshot.", cancel = "Cancel, and resolve the situation on your own." ) choice <- menu(choices, title = "What do you want to do?") if (choice == "snapshot") { # do nothing } else if (choice == "install") { install(include = missing, prompt = FALSE) invokeRestart(restart) } else { cancel() } invisible() } renv_snapshot_filter_custom_resolve <- function() { # check for custom filter filter <- getOption("renv.snapshot.filter", default = NULL) if (is.null(filter)) { fmt <- "snapshot of type '%s' requested, but '%s' is not registered" stopf(fmt, "custom", "renv.snapshot.filter") } # allow for filter naming a function to use if (is.character(filter)) filter <- eval(parse(text = filter), envir = baseenv()) # check we got a function if (!is.function(filter)) { fmt <- "snapshot of type '%s' requested, but '%s' is not a function" stopf(fmt, "custom", "renv.snapshot.filter") } # return resolved function filter } renv_snapshot_fixup <- function(records) { records <- renv_snapshot_fixup_renv(records) records } renv_snapshot_fixup_renv <- function(records) { # don't run when testing renv if (renv_tests_running()) return(records) # check for an existing valid record record <- records$renv if (is.null(record)) return(records) source <- renv_record_source(record) if (source != "unknown") return(records) # no valid record available; construct a synthetic one remote <- renv_metadata_remote() # add it to the set of records records$renv <- renv_remotes_resolve(remote) # return it records } renv_snapshot_reprex <- function(lockfile) { fmt <- "Lockfile generated by renv %s." version <- sprintf(fmt, renv_metadata_version_friendly()) text <- c( "
", "Lockfile", "```", renv_lockfile_write(lockfile, file = NULL), "```", version, "
" ) output <- paste(text, collapse = "\n") class(output) <- "knit_asis" attr(output, "knit_cacheable") <- NA output } renv_snapshot_successful <- function(records, prompt, project) { # update snapshot flag the$auto_snapshot_failed <- FALSE # perform python snapshot on success renv_python_snapshot(project, prompt) # return generated records invisible(records) } renv/R/config.R0000644000176200001440000001621114737275011013025 0ustar liggesusers #' User-level settings #' #' Configure different behaviors of renv. #' #' For a given configuration option: #' #' 1. If an \R option of the form `renv.config.` is available, #' then that option's value will be used; #' #' 2. If an environment variable of the form `RENV_CONFIG_` is available, #' then that option's value will be used; #' #' 3. Otherwise, the default for that particular configuration value is used. #' #' Any periods (`.`)s in the option name are transformed into underscores (`_`) #' in the environment variable name, and vice versa. For example, the #' configuration option `auto.snapshot` could be configured as: #' #' - `options(renv.config.auto.snapshot = <...>)` #' - `Sys.setenv(RENV_CONFIG_AUTO_SNAPSHOT = <...>)` #' #' Note that if both the \R option and the environment variable are defined, the #' \R option will be used instead. Environment variables can be more useful when #' you want a particular configuration to be automatically inherited by child #' processes; if that behavior is not desired, then the R option may be #' preferred. #' #' If you want to set and persist these options across multiple projects, it is #' recommended that you set them in a a startup `.Renviron` file; e.g. in your #' own `~/.Renviron`, or in the R installation's `etc/Rprofile.site` file. See #' [Startup] for more details. #' #' Configuration options can also be set within the project `.Rprofile`, but #' be aware the options should be set before `source("renv/activate.R")` is #' called. #' #' @eval renv_roxygen_config_section() #' #' @section Copy methods: #' #' If you find that renv is unable to copy some directories in your #' environment, you may want to try setting the `copy.method` option. By #' default, renv will try to choose a system tool that is likely to succeed in #' copying files on your system -- `robocopy` on Windows, and `cp` on Unix. #' renv will also instruct these tools to preserve timestamps and attributes #' when copying files. However, you can select a different method as #' appropriate. #' #' The following methods are supported: #' #' \tabular{ll}{ #' `auto` \tab Use `robocopy` on Windows, and `cp` on Unix-alikes. \cr #' `R` \tab Use \R's built-in `file.copy()` function. \cr #' `cp` \tab Use `cp` to copy files. \cr #' `robocopy` \tab Use `robocopy` to copy files. (Only available on Windows.) \cr #' `rsync` \tab Use `rsync` to copy files. \cr #' } #' #' You can also provide a custom copy method if required; e.g. #' #' ``` #' options(renv.config.copy.method = function(src, dst) { #' # copy a file from 'src' to 'dst' #' }) #' ``` #' #' Note that renv will always first attempt to copy a directory first to a #' temporary path within the target folder, and then rename that temporary path #' to the final target destination. This helps avoid issues where a failed #' attempt to copy a directory could leave a half-copied directory behind #' in the final location. #' #' @section Project-local settings: #' #' For settings that should persist alongside a particular project, the #' various settings available in [settings] can be used. #' #' @examples #' #' # disable automatic snapshots #' options(renv.config.auto.snapshot = FALSE) #' #' # disable with environment variable #' Sys.setenv(RENV_CONFIG_AUTO_SNAPSHOT = FALSE) #' #' @rdname config #' @name config NULL renv_config_get <- function(name, scope = "config", type = "*", default = NULL, args = NULL) { # check for R option of associated name optname <- tolower(name) optkey <- paste("renv", scope, optname, sep = ".") optval <- getOption(optkey) if (!is.null(optval)) return(renv_config_validate(name, optval, type, default, args)) # check for environment variable envname <- chartr(".", "_", toupper(name)) envkey <- paste("RENV", toupper(scope), envname, sep = "_") envval <- Sys.getenv(envkey, unset = NA) if (!is.na(envval) && nzchar(envval)) { decoded <- renv_config_decode_envvar(envkey, envval) return(renv_config_validate(name, decoded, type, default, args)) } # return default if nothing found default } renv_config_decode_envvar <- function(envname, envval) { map <- env( "NULL" = NULL, "NA" = NA, "NaN" = NaN, "true" = TRUE, "True" = TRUE, "TRUE" = TRUE, "false" = FALSE, "False" = FALSE, "FALSE" = FALSE ) if (exists(envval, envir = map, inherits = FALSE)) return(get(envval, envir = map, inherits = FALSE)) libvars <- c("RENV_CONFIG_EXTERNAL_LIBRARIES", "RENV_CONFIG_HYDRATE_LIBPATHS") pattern <- if (envname %in% libvars) "\\s*[:;,]\\s*" else "\\s*,\\s*" values <- strsplit(envval, pattern, perl = TRUE)[[1L]] # fix up single-letter paths for Windows # https://github.com/rstudio/renv/issues/2069 result <- stack(mode = "character") i <- 1L while (i <= length(values)) { if (nchar(values[[i]]) == 1L) { value <- paste(values[[i]], values[[i + 1L]], sep = ":") result$push(value) i <- i + 2L } else { value <- values[[i]] result$push(value) i <- i + 1L } } result$data() } renv_config_validate <- function(name, value, type, default, args) { # no validation required for type = '*' if (identical(type, "*")) return(value) # if 'value' is a function, invoke it with args if (is.function(value)) { value <- catch(do.call(value, args)) if (inherits(value, "error")) { warning(value, call. = FALSE) return(default) } } # parse the type string pattern <- paste0( "^", # start of specifier "([^[(]+)", # type name "[[(]", # opening bracket "([^])]+)", # length specifier "[])]", # closing bracket "$" # end of specifier ) m <- regexec(pattern, type) matches <- regmatches(type, m) fields <- matches[[1L]] # extract declared mode, size mode <- fields[[2L]] size <- fields[[3L]] # validate the requested size for this option if (!renv_config_validate_size(value, size)) { fmt <- "value for option '%s' does not satisfy constraint '%s'" warningf(fmt, name, type) } # convert NULL values to requested type if (is.null(value)) { value <- convert(value, mode) return(value) } # otherwise, validate that this is a valid option if (identical(storage.mode(value), mode)) return(value) # try converting converted <- catchall(convert(value, mode)) if (any(is.na(converted)) || inherits(converted, "condition")) { fmt <- "'%s' does not satisfy constraint '%s' for config '%s'; using default '%s' instead" warningf(fmt, stringify(value), type, name, stringify(default)) return(default) } # ok, validated + converted option converted } renv_config_validate_size <- function(value, size) { case( size == "*" ~ TRUE, size == "+" ~ length(value) > 0, size == "?" ~ length(value) %in% c(0, 1), TRUE ~ as.numeric(size) == length(value) ) } renv_config_install_staged <- function(default = TRUE) { values <- c( config$install.staged(default = NULL), config$install.transactional(default = NULL), default ) values[[1L]] } renv/R/parallel.R0000644000176200001440000000116014750473537013361 0ustar liggesusers renv_parallel_cores <- function() { if (renv_platform_windows()) return(1L) parallel <- config$updates.parallel() value <- if (identical(parallel, TRUE)) { requireNamespace("parallel", quietly = TRUE) getOption("mc.cores", default = 2L) } else if (identical(parallel, FALSE)) { 1L } else { parallel } ok <- is.numeric(value) && length(value) == 1L && !is.na(value) if (ok) value else 1L } renv_parallel_exec <- function(data, callback) { cores <- renv_parallel_cores() if (cores > 1L) parallel::mclapply(data, callback, mc.cores = cores) else lapply(data, callback) } renv/R/github.R0000644000176200001440000000000114731330073013022 0ustar liggesusers renv/R/nexus.R0000644000176200001440000000043514731330073012715 0ustar liggesusers renv_nexus_enabled <- function(repo) { # first, check a global option enabled <- getOption("renv.nexus.enabled", default = FALSE) if (enabled) return(TRUE) # otherwise, check cached repository information info <- renv_repos_info(repo) identical(info$nexus, TRUE) } renv/R/cli.R0000644000176200001440000000723514731330072012326 0ustar liggesusers renv_cli_install <- function(target = NULL) { # get path to bundled tool exe <- if (renv_platform_windows()) "bin/renv.bat" else "bin/renv" path <- system.file(exe, package = "renv") # copy into directory on PATH target <- target %||% path.expand("~/bin/renv") ensure_parent_directory(target) file.copy(path, target) writef("- renv binary copied to %s.", renv_path_pretty(target)) invisible(target) } renv_cli_exec <- function(clargs = commandArgs(trailingOnly = TRUE)) { invisible(renv_cli_exec_impl(clargs)) } renv_cli_exec_impl <- function(clargs) { # check for tool called without arguments, or called with '--help' usage <- length(clargs) == 0 || clargs[1L] %in% c("help", "--help") if (usage) return(renv_cli_usage()) # extract method method <- clargs[1L] # check request for help on requested method help <- clargs[2L] %in% c("help", "--help") if (help) return(renv_cli_help(method)) # check for known function in renv exports <- getNamespaceExports("renv") if (!method %in% exports) return(renv_cli_unknown(method, exports)) # begin building call args <- list(call("::", as.symbol("renv"), as.symbol(method))) for (clarg in clargs[-1L]) { # convert '--no-' into a FALSE parameter if (grepl("^--no-", clarg)) { key <- substring(clarg, 6L) args[[key]] <- FALSE } # convert '--param=value' flags else if (grepl("^--[^=]+=", clarg)) { index <- regexpr("=", clarg, fixed = TRUE) key <- substring(clarg, 3L, index - 1L) val <- substring(clarg, index + 1L) args[[key]] <- renv_cli_parse(val) } # convert '--flag' into a TRUE parameter else if (grepl("^--", clarg)) { key <- substring(clarg, 3L) args[[key]] <- TRUE } # convert 'param=value' flags else if (grepl("=", clarg, fixed = TRUE)) { index <- regexpr("=", clarg, fixed = TRUE) key <- substring(clarg, 1L, index - 1L) val <- substring(clarg, index + 1L) args[[key]] <- renv_cli_parse(val) } # take other parameters as-is else { args[[length(args) + 1L]] <- renv_cli_parse(clarg) } } # invoke method with parsed arguments expr <- as.call(args) eval(expr = expr, envir = globalenv()) } renv_cli_usage <- function() { usage <- " Usage: renv [method] [args...] [method] should be the name of a function exported from renv. [args...] should be arguments accepted by that function. Use renv [method] --help for more information about the associated function. Examples: # basic commands renv init # initialize a project renv snapshot # snapshot project library renv restore # restore project library renv status # check project status # install a package renv install dplyr # run a script in an renv project renv run path/to/script.R " writeLines(usage, con = stderr()) } renv_cli_help <- function(method) { print(help(method, package = "renv")) } renv_cli_unknown <- function(method, exports) { # report unknown command caution("renv: '%s' is not a known command.", method) # check for similar commands distance <- c(adist(method, exports)) names(distance) <- exports n <- min(distance) if (n > 2) return(1L) candidates <- names(distance)[distance == n] fmt <- "did you mean %s?" caution(fmt, paste(shQuote(candidates), collapse = " or ")) return(1L) } renv_cli_parse <- function(text) { # handle logical-like values up-front if (text %in% c("true", "True", "TRUE")) return(TRUE) else if (text %in% c("false", "False", "FALSE")) return(FALSE) # parse the expression value <- parse(text = text)[[1L]] if (is.language(value)) text else value } renv/R/python-virtualenv.R0000644000176200001440000000661014761163114015275 0ustar liggesusers renv_python_virtualenv_home <- function() { Sys.getenv("WORKON_HOME", unset = "~/.virtualenvs") } renv_python_virtualenv_path <- function(name) { # if the name contains a slash, use it as-is if (grepl("/", name, fixed = TRUE)) return(renv_path_canonicalize(name)) # treat names starting with '.' specially if (substring(name, 1L, 1L) == ".") return(renv_path_canonicalize(name)) # otherwise, resolve relative to virtualenv home home <- renv_python_virtualenv_home() file.path(home, name) } renv_python_virtualenv_validate <- function(path, version) { # get path to python executable python <- renv_python_exe(path) # compare requested + actual versions if (!is.null(version)) { request <- renv_version_maj_min(version) current <- renv_version_maj_min(renv_python_version(python)) if (request != current) { fmt <- "Project requested Python version '%s' but '%s' is currently being used" warningf(fmt, request, current) } } python } renv_python_virtualenv_create <- function(python, path) { ensure_parent_directory(path) python <- renv_path_canonicalize(python) version <- renv_python_version(python) module <- if (numeric_version(version) > "3.2") "venv" else "virtualenv" args <- c("-m", module, renv_shell_path(path)) renv_system_exec(python, args, "creating virtual environment") info <- renv_python_info(path) info$python } renv_python_virtualenv_update <- function(python) { # resolve python executable path python <- renv_python_exe(python) python <- renv_path_canonicalize(python) # resolve packages packages <- c("pip", "setuptools", "wheel") # don't upgrade these packages for older versions of python, as we may # end up installing versions of packages that aren't actually compatible # with the version of python we're running version <- renv_python_version(python) if (renv_version_lt(version, "3.6")) return(TRUE) # perform the install # make errors non-fatal as the environment will still be functional even # if we're not able to install or update these packages status <- catch(pip_install(packages, python = python)) if (inherits(status, "error")) warnify(status) TRUE } renv_python_virtualenv_snapshot <- function(project, prompt, python) { renv_scope_wd(project) path <- file.path(project, "requirements.txt") before <- character() if (file.exists(path)) before <- readLines(path, warn = FALSE) after <- pip_freeze(python = python) if (setequal(before, after)) { writef("- Python requirements are already up to date.") return(FALSE) } bulletin("The following will be written to requirements.txt:", after) cancel_if(prompt && !proceed()) writeLines(after, con = path) fmt <- "- Wrote Python packages to %s." writef(fmt, renv_path_pretty(path)) return(TRUE) } renv_python_virtualenv_restore <- function(project, prompt, python) { renv_scope_wd(project) path <- file.path(project, "requirements.txt") if (!file.exists(path)) return(FALSE) saved <- readLines(path, warn = FALSE) current <- pip_freeze(python = python) diff <- renv_vector_diff(saved, current) if (empty(diff)) { writef("- The Python library is already up to date.") return(FALSE) } bulletin("The following Python packages will be restored:", diff) cancel_if(prompt && !proceed()) pip_install_requirements(saved, python = python, stream = TRUE) TRUE } renv/R/activate.R0000644000176200001440000001237114731330072013354 0ustar liggesusers #' Activate or deactivate a project #' #' @description #' `activate()` enables renv for a project in both the current session and #' in all future sessions. You should not generally need to call `activate()` #' yourself as it's called automatically by [renv::init()], which is the best #' way to start using renv in a new project. #' #' `activate()` first calls [renv::scaffold()] to set up the project #' infrastructure. Most importantly, this creates a project library and adds a #' an auto-loader to `.Rprofile` to ensure that the project library is #' automatically used for all future instances of the project. It then restarts #' the session to use that auto-loader. #' #' `deactivate()` removes the infrastructure added by `activate()`, and #' restarts the session. By default it will remove the auto-loader from the #' `.Rprofile`; use `clean = TRUE` to also delete the lockfile and the project #' library. #' #' # Temporary deactivation #' #' If you need to temporarily disable autoload activation you can set #' the `RENV_CONFIG_AUTOLOADER_ENABLED` envvar, e.g. #' `Sys.setenv(RENV_CONFIG_AUTOLOADER_ENABLED = "false")`. #' #' @inherit renv-params #' #' @export #' #' @examples #' \dontrun{ #' #' # activate the current project #' renv::activate() #' #' # activate a separate project #' renv::activate(project = "~/projects/analysis") #' #' # deactivate the currently-activated project #' renv::deactivate() #' #' } activate <- function(project = NULL, profile = NULL) { renv_consent_check() renv_scope_error_handler() project <- renv_project_resolve(project) renv_project_lock(project = project) profile <- profile %||% renv_profile_get() renv_profile_set(profile) renv_activate_impl( project = project, profile = profile, version = NULL ) invisible(project) } renv_activate_impl <- function(project, profile, version = NULL, load = TRUE, restart = TRUE) { # prepare renv infrastructure renv_infrastructure_write( project = project, profile = profile, version = version ) # ensure renv is imbued into the new library path if necessary if (!renv_tests_running()) renv_imbue_self(project) # restart session if requested if (restart && !renv_tests_running()) return(renv_restart_request(project, reason = "renv activated")) if (renv_rstudio_available()) renv_rstudio_initialize(project) # try to load the project if (load) { setwd(project) load(project) } invisible(project) } renv_activate_version <- function(project) { # try to get version from activate.R methods <- list( renv_activate_version_lockfile, renv_activate_version_activate, renv_activate_version_metadata ) for (method in methods) { version <- catch(method(project)) if (is.character(version)) return(version) } fmt <- "failed to determine renv version for project %s" stopf(fmt, renv_path_pretty(project)) } renv_activate_version_activate <- function(project) { # get path to the activate script activate <- renv_paths_activate(project = project) if (!file.exists(activate)) return(NULL) # check for version contents <- readLines(activate, warn = FALSE) line <- grep("version <-", contents, fixed = TRUE, value = TRUE)[[1L]] version <- parse(text = line)[[1L]][[3L]] # check for sha as well line <- grep("attr(version, \"sha\")", contents, fixed = TRUE, value = TRUE) if (length(line)) { sha <- parse(text = line)[[1L]][[3L]] attr(version, "sha") <- sha } version } renv_activate_version_lockfile <- function(project) { path <- renv_lockfile_path(project) if (!file.exists(path)) return(NULL) # read the renv record lockfile <- renv_lockfile_read(path) records <- renv_lockfile_records(lockfile) renv_metadata_version_create(records[["renv"]]) } renv_activate_version_metadata <- function(project) { the$metadata$version } renv_activate_prompt <- function(action, library, prompt, project) { # check whether we should ask user to activate ask <- config$activate.prompt() && prompt && interactive() && is.null(library) && !renv_project_loaded(project) && !testing() # for snapshot, since users might want to snapshot their system library # in an renv-lite configuration, only prompt if it looks like they're # working within an renv project that hasn't been loaded if ("snapshot" %in% action) { libpath <- renv_paths_library(project = project) ask <- ask && file.exists(libpath) } if (!ask) return(FALSE) renv_activate_prompt_impl(action, project) } renv_activate_prompt_impl <- function(action, project = NULL) { fmt <- "It looks like you've called renv::%s() in a project that hasn't been activated yet." title <- c(sprintf(fmt, action), "How would you like to proceed?") choices <- c( activate = "Activate the project and use the project library.", continue = "Do not activate the project and use the current library paths.", cancel = "Cancel and resolve the situation another way." ) choice <- menu(choices, title, default = "continue") switch(choice, activate = { activate(project = project); TRUE }, continue = FALSE, cancel = cancel() ) } renv/R/lockfile-validate.R0000644000176200001440000000625214731330073015135 0ustar liggesusers #' Validate an renv lockfile against a schema #' #' @description #' `renv::lockfile_validate()` can be used to validate your `renv.lock` #' against a default or custom schema. It can be used to automate checks, #' check for obvious errors, and ensure that any custom fields you add fit #' your specific needs. #' #' @details #' See the [JSON Schema docs](https://json-schema.org/) for more information #' on JSON schemas, their use in validation, and how to write your own schema. #' #' `renv::lockfile_validate()` wraps ROpenSci's #' [`jsonvalidate`](https://docs.ropensci.org/jsonvalidate/) package, passing #' many of its parameters to that package's `json_validate()` function. Use #' `?jsonvalidate::json_validate` for more information. #' #' @inheritParams renv-params #' #' @param lockfile Contents of the lockfile, or a filename containing one. #' If not provided, it defaults to the project's lockfile. #' #' @param schema Contents of a renv schema, or a filename containing a schema. #' If not provided, renv's default schema is used. #' #' @param greedy Boolean. Continue after first error? #' #' @param error Boolean. Throw an error on parse failure? #' #' @param verbose Boolean. If `TRUE`, then an attribute `errors` will list #' validation failures as a `data.frame`. #' #' @param strict Boolean. Set whether the schema should be parsed strictly or #' not. If in strict mode schemas will error to "prevent any unexpected #' behaviours or silently ignored mistakes in user schema". For example it #' will error if encounters unknown formats or unknown keywords. See #' https://ajv.js.org/strict-mode.html for details. #' #' @return Boolean. `TRUE` if validation passes. `FALSE` if validation fails. #' #' @examples #' \dontrun{ #' #' # validate the project's lockfile #' renv::lockfile_validate() #' #' # validate the project's lockfile using a non-default schema #' renv::lockfile_validate(schema = "/path/to/your/custom/schema.json") #' #' # validate a lockfile using its path #' renv::lockfile_validate(lockfile = "/path/to/your/renv.lock") #' } #' #' @keywords internal #' @export lockfile_validate <- function(project = NULL, lockfile = NULL, # Use default project lockfile if not provided schema = NULL, # Use default schema if not provided greedy = FALSE, error = FALSE, verbose = FALSE, strict = FALSE) { project <- renv_project_resolve(project) lockfile <- lockfile %||% renv_lockfile_path(project = project) schema <- schema %||% system.file("schema", "draft-07.renv.lock.schema.json", package = "renv", mustWork = TRUE) # "ajv" engine required for schema specifications later than draft-04 jsonvalidate::json_validate(lockfile, schema, engine = "ajv", greedy = greedy, error = error, verbose = verbose, strict = strict) } renv/R/data_frame.R0000644000176200001440000000150714731330072013636 0ustar liggesusers data_frame <- function(...) { as_data_frame(list(...)) } as_data_frame <- function(data) { # split matrices into columns if (is.matrix(data)) { result <- vector("list", ncol(data)) names(result) <- colnames(data) dimnames(data) <- NULL for (i in seq_len(ncol(data))) result[[i]] <- data[, i] data <- result } # convert other objects to lists if (!is.list(data)) data <- as.list(data) # recycle columns n <- lengths(data, use.names = FALSE) nrow <- max(n) # start recycling for (i in seq_along(data)) { if (n[[i]] == 0L) { length(data[[i]]) <- nrow } else if (n[[i]] != nrow) { data[[i]] <- rep.int(data[[i]], nrow / n[[i]]) } } # set attributes class(data) <- "data.frame" attr(data, "row.names") <- .set_row_names(nrow) # return data data } renv/R/lockfile-write.R0000644000176200001440000001011014740260564014471 0ustar liggesusers the$lockfile_state <- new.env(parent = emptyenv()) renv_lockfile_state_get <- function(key) { if (exists(key, envir = the$lockfile_state)) get(key, envir = the$lockfile_state, inherits = FALSE) } renv_lockfile_state_set <- function(key, value) { assign(key, value, envir = the$lockfile_state, inherits = FALSE) } renv_lockfile_state_clear <- function() { rm(list = ls(the$lockfile_state), envir = the$lockfile_state) } renv_lockfile_write_preflight <- function(old, new) { diff <- renv_lockfile_diff(old, new) if (empty(diff)) return(new) packages <- diff$Packages if (empty(diff$Packages)) return(new) enumerate(packages, function(package, changes) { # avoid spurious changes lhs <- "CRAN" for (rhs in c("RSPM", "PPM", "P3M")) { spurious <- identical(changes, list(Repository = list(before = lhs, after = rhs))) || identical(changes, list(Repository = list(before = rhs, after = lhs))) if (spurious) new$Packages[[package]]$Repository <<- old$Packages[[package]]$Repository } }) new } renv_lockfile_write <- function(lockfile, file = stdout()) { # if we're updating an existing lockfile, try to avoid # "unnecessary" diffs that might otherwise be annoying if (is.character(file) && file.exists(file)) { old <- catch(renv_lockfile_read(file)) if (!inherits(old, "error")) lockfile <- renv_lockfile_write_preflight(old, lockfile) } lockfile <- renv_lockfile_sort(lockfile) result <- renv_lockfile_write_json(lockfile, file) if (is.character(file)) writef("- Lockfile written to %s.", renv_path_pretty(file)) result } renv_lockfile_write_json_prepare_repos <- function(repos) { prepared <- enumerate(repos, function(name, url) { url <- sub("/+$", "", url) list(Name = name, URL = url) }) unname(prepared) } renv_lockfile_write_json_prepare <- function(key, val) { if (key == "Repositories") renv_lockfile_write_json_prepare_repos(val) else if (is.list(val) && !is.null(names(val))) enumerate(val, renv_lockfile_write_json_prepare) else val } renv_lockfile_write_json <- function(lockfile, file = stdout()) { prepared <- enumerate(lockfile, renv_lockfile_write_json_prepare) box <- c("Requirements") config <- list(box = box) json <- renv_json_convert(prepared, config) if (is.null(file)) return(json) writeLines(json, con = file) } renv_lockfile_write_internal <- function(lockfile, file = stdout(), delim = "=") { if (is.character(file)) { file <- textfile(file) defer(close(file)) } emitter <- function(text) writeLines(text, con = file) renv_lockfile_state_set("delim", delim) renv_lockfile_state_set("emitter", emitter) defer(renv_lockfile_state_clear()) renv_lockfile_write_list(lockfile, section = character()) invisible(lockfile) } renv_lockfile_write_list <- function(entry, section) { enumerate(entry, renv_lockfile_write_atoms, section = section) enumerate(entry, renv_lockfile_write_lists, section = section) } renv_lockfile_write_atoms <- function(key, value, section) { sublists <- map_lgl(value, function(x) identical(class(x), "list")) if (all(sublists)) return() subsection <- c(section, key) label <- sprintf("[%s]", paste(subsection, collapse = "/")) renv_lockfile_write_emit(label) enumerate(value[!sublists], renv_lockfile_write_atom) renv_lockfile_write_emit() } renv_lockfile_write_atom <- function(key, value) { lhs <- key rhs <- if (is_named(value)) paste(sprintf("\n\t%s=%s", names(value), value), collapse = "") else paste(value, collapse = ", ") delim <- renv_lockfile_state_get("delim") text <- paste(lhs, rhs, sep = delim) renv_lockfile_write_emit(text) } renv_lockfile_write_lists <- function(key, value, section) { sublists <- map_lgl(value, function(x) identical(class(x), "list")) renv_lockfile_write_list(value[sublists], section = c(section, key)) } renv_lockfile_write_emit <- function(text = "") { emitter <- renv_lockfile_state_get("emitter") emitter(text) } renv/R/homebrew.R0000644000176200001440000000050214731330073013356 0ustar liggesusers renv_homebrew_root <- function() { # allow override root <- Sys.getenv("RENV_HOMEBREW_ROOT", unset = NA) if (!is.na(root)) return(root) # indirection for arm64 macOS if (renv_platform_macos() && renv_platform_machine() != "x86_64") return("/opt/homebrew") # default to /usr/local "/usr/local" } renv/R/regexps.R0000644000176200001440000000102414731330073013223 0ustar liggesusers renv_regexps_package_name <- function() { paste0("^", .standard_regexps()$valid_package_name, "$") } renv_regexps_package_version <- function() { paste0("^", .standard_regexps()$valid_package_version, "$") } renv_regexps_escape <- function(regexp) { pattern <- "([\\-\\[\\]\\{\\}\\(\\)\\*\\+\\?\\.\\,\\\\\\^\\$\\|\\#\\s])" gsub(pattern, "\\\\\\1", regexp, perl = TRUE) } renv_regexps_join <- function(regexps, capture = TRUE) { fmt <- if (capture) "(%s)" else "(?:%s)" sprintf(fmt, paste(regexps, collapse = "|")) } renv/R/hash.R0000644000176200001440000000613514740260564012510 0ustar liggesusers renv_hash_text <- function(text) { renv_bootstrap_hash_text(text) } renv_hash_fields <- function(dcf) { c( renv_hash_fields_default(), renv_hash_fields_remotes(dcf) ) } renv_hash_fields_default <- function() { c( "Package", "Version", "Title", "Author", "Maintainer", "Description", "Depends", "Imports", "Suggests", "LinkingTo" ) } renv_hash_fields_remotes <- function(dcf) { # if this seems to be a cran-like record, only keep remotes # when RemoteSha appears to be a hash (e.g. for r-universe) # note that RemoteSha may be a package version when installed # by e.g. pak if (renv_record_cranlike(dcf)) { sha <- dcf[["RemoteSha"]] if (is.null(sha) || nchar(sha) < 40L) return(character()) } # grab the relevant remotes remotes <- grep("^Remote", names(dcf), perl = TRUE, value = TRUE) # don't include 'RemoteRef' if it's a non-informative remote if (identical(dcf[["RemoteRef"]], "HEAD")) remotes <- setdiff(remotes, "RemoteRef") remotes } renv_hash_description <- function(path) { filebacked( context = "renv_hash_description", path = path, callback = renv_hash_description_impl ) } renv_hash_description_impl <- function(path) { record <- renv_description_read(path) renv_hash_record(record) } renv_hash_record <- function(record) { # find relevant fields for hashing fields <- renv_hash_fields(record) # collapse vector / list dependency fields depfields <- c("Depends", "Imports", "Suggests", "LinkingTo", "Enhances") for (depfield in depfields) { if (!is.null(record[[depfield]])) { value <- unlist(record[[depfield]]) record[[depfield]] <- paste(value, collapse = ", ") } } # retrieve these fields subsetted <- record[renv_vector_intersect(fields, names(record))] # sort names (use C locale to ensure consistent ordering) ordered <- subsetted[csort(names(subsetted))] # write to tempfile (use binary connection to ensure unix-style # newlines for cross-platform hash stability) tempfile <- tempfile("renv-description-hash-") contents <- paste(names(ordered), ordered, sep = ": ", collapse = "\n") # remove whitespace -- it's possible that tools (e.g. Packrat) that # mutate a package's DESCRIPTION file may also inadvertently change # the structure of whitespace within some fields; that whitespace is # normally not semantically meaningful so we remove that so such # DESCRIPTIONS can obtain the same hash value. (this ultimately # arises as 'write.dcf()' allows both 'indent' and 'width' to be # configured based on the 'width' option) contents <- gsub("[[:space:]]", "", contents) # create the file connection (use binary so that unix newlines are used # across platforms, for more stable hashing) con <- file(tempfile, open = "wb") # write to the file writeLines(enc2utf8(contents), con = con, useBytes = TRUE) # flush to ensure we've written to file flush(con) # close the connection and remove the file close(con) # ready for hasing hash <- unname(md5sum(tempfile)) # remove the old file unlink(tempfile) # return hash invisible(hash) } renv/R/installed-packages.R0000644000176200001440000000064014731330073015304 0ustar liggesusers installed_packages <- function(lib.loc = NULL, priority = NULL, field = NULL) { lib.loc <- lib.loc %||% .libPaths() result <- dynamic( key = list(lib.loc = lib.loc, priority = priority), value = { packages <- installed.packages(lib.loc = lib.loc, priority = priority) as_data_frame(packages) } ) take(result, field) } renv/R/bind.R0000644000176200001440000000354114731330072012467 0ustar liggesusers bind <- function(data, names = NULL, index = "Index") { # keep only non-empty data data <- Filter(NROW, data) if (!length(data)) return(NULL) # check for quick exit if (length(data) == 1L) { # no-name case if (is.null(names(data))) { rhs <- data[[1L]] names(rhs) <- names(rhs) %||% names return(as_data_frame(rhs)) } # named case lhs <- list(rep.int(names(data), times = NROW(data[[1L]]))) names(lhs) <- index rhs <- as.list(data[[1L]]) return(as_data_frame(c(lhs, rhs))) } # ensure all datasets have the same column names # try to preserve the ordering of names if possible # (try to find one dataset which has all column relevant column names) nms <- character() for (i in seq_along(data)) { names(data[[i]]) <- names(data[[i]]) %||% names nmsi <- names(data[[i]]) if (length(nmsi) > length(nms)) nms <- nmsi } # check now if we've caught all relevant names; if we didn't, # just fall back to a "dumb" union allnms <- unique.default(unlist(lapply(data, names), use.names = FALSE)) if (!setequal(nms, allnms)) nms <- allnms # we've collected all names; now fill with NAs as necessary filled <- map(data, function(datum) { datum[setdiff(nms, names(datum))] <- NA datum[nms] }) # we've collected and ordered each data.frame, now merge them rhs <- .mapply(c, filled, list(use.names = FALSE)) names(rhs) <- names(filled[[1L]]) if (is.null(names(data))) { names(rhs) <- names(rhs) %||% names return(as_data_frame(rhs)) } if (index %in% names(rhs)) { fmt <- "name collision: bound list already contains column called '%s'" stopf(fmt, index) } lhs <- list() rows <- function(item) nrow(item) %||% length(item[[1L]]) lhs[[index]] <- rep.int(names(filled), times = map_dbl(filled, rows)) as_data_frame(c(lhs, rhs)) } renv/R/condition.R0000644000176200001440000000034214731330072013535 0ustar liggesusers renv_condition_signal <- function(class = NULL, data = NULL) { condition <- list(message = character(), call = NULL, data = data) class(condition) <- c(class, "renv.condition", "condition") signalCondition(condition) } renv/R/json-read.R0000644000176200001440000000547314731330073013444 0ustar liggesusers renv_json_read <- function(file = NULL, text = NULL) { jlerr <- NULL # if jsonlite is loaded, use that instead if ("jsonlite" %in% loadedNamespaces()) { json <- tryCatch(renv_json_read_jsonlite(file, text), error = identity) if (!inherits(json, "error")) return(json) jlerr <- json } # otherwise, fall back to the default JSON reader json <- tryCatch(renv_json_read_default(file, text), error = identity) if (!inherits(json, "error")) return(json) # report an error if (!is.null(jlerr)) stop(jlerr) else stop(json) } renv_json_read_jsonlite <- function(file = NULL, text = NULL) { text <- paste(text %||% readLines(file, warn = FALSE), collapse = "\n") jsonlite::fromJSON(txt = text, simplifyVector = FALSE) } renv_json_read_patterns <- function() { list( # objects list("{", "\t\n\tobject(\t\n\t"), list("}", "\t\n\t)\t\n\t"), # arrays list("[", "\t\n\tarray(\t\n\t"), list("]", "\n\t\n)\n\t\n"), # maps list(":", "\t\n\t=\t\n\t") ) } renv_json_read_envir <- function() { envir <- new.env(parent = emptyenv()) envir[["+"]] <- `+` envir[["-"]] <- `-` envir[["object"]] <- function(...) { result <- list(...) names(result) <- as.character(names(result)) result } envir[["array"]] <- list envir[["true"]] <- TRUE envir[["false"]] <- FALSE envir[["null"]] <- NULL envir } renv_json_read_remap <- function(object, patterns) { # repair names if necessary if (!is.null(names(object))) { nms <- names(object) for (pattern in patterns) nms <- gsub(pattern[[2L]], pattern[[1L]], nms, fixed = TRUE) names(object) <- nms } # repair strings if necessary if (is.character(object)) { for (pattern in patterns) object <- gsub(pattern[[2L]], pattern[[1L]], object, fixed = TRUE) } # recurse for other objects if (is.recursive(object)) for (i in seq_along(object)) object[i] <- list(renv_json_read_remap(object[[i]], patterns)) # return remapped object object } renv_json_read_default <- function(file = NULL, text = NULL) { # read json text text <- paste(text %||% readLines(file, warn = FALSE), collapse = "\n") # convert into something the R parser will understand patterns <- renv_json_read_patterns() transformed <- text for (pattern in patterns) transformed <- gsub(pattern[[1L]], pattern[[2L]], transformed, fixed = TRUE) # parse it rfile <- tempfile("renv-json-", fileext = ".R") on.exit(unlink(rfile), add = TRUE) writeLines(transformed, con = rfile) json <- parse(rfile, keep.source = FALSE, srcfile = NULL)[[1L]] # evaluate in safe environment result <- eval(json, envir = renv_json_read_envir()) # fix up strings if necessary renv_json_read_remap(result, patterns) } renv/R/restart.R0000644000176200001440000000455514731330073013246 0ustar liggesusers # whether or not we're already trying to restart the session the$restarting <- FALSE renv_restart_request <- function(project = NULL, reason = "", ...) { project <- renv_project_resolve(project) # if we're running in RStudio, explicitly open the project # if it differs from the current project if (renv_rstudio_available()) { status <- renv_restart_request_rstudio(project, reason, ...) return(invisible(status)) } renv_restart_request_default(project, reason, ...) } renv_restart_request_default <- function(project, reason, ...) { # use 'restart' helper defined by front-end (if any) restart <- getOption("restart") if (is.function(restart)) return(renv_restart_invoke(restart)) # otherwise, ask the user to restart if (interactive()) { fmt <- "- %s -- please restart the R session." writef(fmt, sprintf(reason, ...)) } } renv_restart_request_rstudio <- function(project, reason, ...) { # if we're running tests, don't restart if (renv_tests_running()) return(renv_restart_request_default(project, reason, ...)) # if we don't have a tools env, bail tools <- catch(as.environment("tools:rstudio")) if (inherits(tools, "error")) return(renv_restart_request_default(project, reason, ...)) # if RStudio is too old, use default restart impl old <- is.null(tools$.rs.getProjectDirectory) || is.null(tools$.rs.api.openProject) if (old) return(renv_restart_request_default(project, reason, ...)) # if the requested project matches the current project, just # restart the R session -- but note that we cannot respect # the 'restart' option here as the version RStudio uses # tries to preserve session state that we need to change. # # https://github.com/rstudio/renv/issues/1530 projdir <- tools$.rs.getProjectDirectory() %||% "" if (renv_file_same(projdir, project)) { restart <- getOption("renv.restart.function", default = function() { tools$.rs.api.executeCommand("restartR", quiet = TRUE) }) return(renv_restart_invoke(restart)) } # otherwise, explicitly open the new project renv_restart_invoke(function() { invisible(tools$.rs.api.openProject(project, newSession = FALSE)) }) } renv_restart_invoke <- function(callback) { # avoid multiple attempts to restart in a single call, just in case if (!the$restarting) { the$restarting <- TRUE callback() } } renv/R/curl.R0000644000176200001440000000317214731330072012520 0ustar liggesusers the$curl_valid <- new.env(parent = emptyenv()) renv_curl_exe <- function() { curl <- Sys.getenv("RENV_CURL_EXECUTABLE", unset = NA) if (is.na(curl)) curl <- Sys.which("curl") if (!nzchar(curl)) return(renv_curl_exe_missing(curl)) renv_curl_validate(curl) } renv_curl_validate <- function(curl) { the$curl_valid[[curl]] <- the$curl_valid[[curl]] %||% { renv_curl_validate_impl(curl) } } renv_curl_validate_impl <- function(curl) { # make sure we can run this copy of curl # note that 'system2()' will give an error if curl isn't runnable at all output <- suppressWarnings( tryCatch( system2( command = curl, args = "--version", stdout = TRUE, stderr = TRUE ), error = identity ) ) if (!inherits(output, "error")) { status <- attr(output, "status") %||% 0L if (status == 0L) return(curl) } message <- if (inherits(output, "error")) conditionMessage(output) else output fmt <- "Error executing '%s --version': is your copy of curl functional?" footer <- sprintf(fmt, curl) all <- c("", header(paste(curl, "--version"), prefix = "$"), message, "", footer) defer( message(paste(all, collapse = "\n")), scope = renv_dynamic_envir() ) return(curl) } renv_curl_exe_missing <- function(curl) { if (!once()) return(invisible(curl)) parts <- c( "curl does not appear to be installed; downloads will fail.", "See for more information." ) msg <- paste(parts, collapse = "\n") warning(msg, call. = FALSE) invisible(curl) } renv/R/knitr.R0000644000176200001440000001114414731330073012701 0ustar liggesusers renv_knitr_options_header <- function(text, type) { # extract the inner options from the header patterns <- renv_knitr_patterns() rest <- sub(patterns[[type]]$chunk.begin, "\\1", text) # if this is an R Markdown document, parse the initial engine chunk # (default to 'r' when not set) engine <- "r" if (type == "md") { idx <- regexpr("(?:[ ,]|$)", rest) engine <- substring(rest, 1, idx - 1) rest <- sub("^,*\\s*", "", substring(rest, idx + 1)) } # parse the params params <- renv_knitr_options_header_impl(rest) # ensure an engine is set, if any params[["engine"]] <- params[["engine"]] %||% engine # return parsed params params } renv_knitr_options_header_impl <- function(rest) { # extract an unquoted label label <- "" pattern <- "(^\\s*[^=]+)(,|\\s*$)" matches <- regexec(pattern, rest)[[1]] if (!identical(c(matches), -1L)) { submatches <- regmatches(rest, list(matches))[[1]] label <- trimws(submatches[[2L]]) rest <- substring(rest, matches[[3L]] + 1L) } # parse as alist params <- catch(parse(text = sprintf("alist(%s)", rest))[[1]]) if (inherits(params, "error")) return(list()) # insert the label back in names(params) <- names(params) %||% rep.int("", length(params)) if (length(params) > 1 && names(params)[[2L]] == "") names(params)[[2L]] <- "label" # fix up 'label' if it's a missing value if (identical(params[["label"]], quote(expr = ))) params[["label"]] <- NULL # if we parsed a label, add it in if (is.null(params[["label"]]) && nzchar(label)) params[["label"]] <- label # evaluate the alist eval(params, envir = parent.frame()) } renv_knitr_options_chunk <- function(code) { # find chunk option lines pattern <- "^[[:space:]]*#+[|]" matches <- grep(pattern, code[nzchar(code)], value = TRUE) # remove prefix text <- gsub(pattern, "", matches) # try to guess whether it's YAML isyaml <- any(grepl("^[[:space:]]*[^[:space:]:]+:", text)) # first, try to parse as YAML, then as R code params <- if (isyaml) { # validate that we actually have the yaml package available if (!renv_dependencies_require("yaml")) return(list()) catch(renv_yaml_load(text)) } else { code <- paste(text, collapse = ", ") catch(renv_knitr_options_header_impl(code)) } # check for error and report if this is in dependency discovery if (inherits(params, "error")) { state <- renv_dependencies_state() if (!is.null(state)) { problem <- list(file = state$path %||% "", error = params) state$problems$push(problem) } return(list()) } # return parsed params params } renv_knitr_patterns <- function() { list( rnw = list( chunk.begin = "^\\s*<<(.*)>>=.*$", chunk.end = "^\\s*@\\s*(%+.*|)$", inline.code = "\\\\Sexpr\\{([^}]+)\\}", inline.comment = "^\\s*%.*", ref.chunk = "^\\s*<<(.+)>>\\s*$", header.begin = "(^|\n)\\s*\\\\documentclass[^}]+\\}", document.begin = "\\s*\\\\begin\\{document\\}" ), tex = list( chunk.begin = "^\\s*%+\\s*begin.rcode\\s*(.*)", chunk.end = "^\\s*%+\\s*end.rcode", chunk.code = "^\\s*%+", ref.chunk = "^%+\\s*<<(.+)>>\\s*$", inline.comment = "^\\s*%.*", inline.code = "\\\\rinline\\{([^}]+)\\}", header.begin = "(^|\n)\\s*\\\\documentclass[^}]+\\}", document.begin = "\\s*\\\\begin\\{document\\}" ), html = list( chunk.begin = "^\\s*", ref.chunk = "^\\s*<<(.+)>>\\s*$", inline.code = "", header.begin = "\\s*" ), md = list( chunk.begin = "^[\t >]*```+\\s*\\{([a-zA-Z0-9_]+( *[ ,].*)?)\\}\\s*$", chunk.end = "^[\t >]*```+\\s*$", ref.chunk = "^\\s*<<(.+)>>\\s*$", inline.code = "(?>\\s*$", inline.code = ":r:`([^`]+)`" ), asciidoc = list( chunk.begin = "^//\\s*begin[.]rcode(.*)$", chunk.end = "^//\\s*end[.]rcode\\s*$", chunk.code = "^//+", ref.chunk = "^\\s*<<(.+)>>\\s*$", inline.code = "`r +([^`]+)\\s*`|[+]r +([^+]+)\\s*[+]", inline.comment = "^//.*" ), textile = list( chunk.begin = "^###[.]\\s+begin[.]rcode(.*)$", chunk.end = "^###[.]\\s+end[.]rcode\\s*$", ref.chunk = "^\\s*<<(.+)>>\\s*$", inline.code = "@r +([^@]+)\\s*@", inline.comment = "^###[.].*" ) ) } renv/R/settings.R0000644000176200001440000003760314745761254013440 0ustar liggesusers the$settings <- new.env(parent = emptyenv()) renv_settings_default <- function(name) { default <- the$settings[[name]]$default renv_options_override("renv.settings", name, default) } renv_settings_defaults <- function() { keys <- ls(envir = the$settings, all.names = TRUE) vals <- lapply(keys, renv_settings_default) names(vals) <- keys vals[order(names(vals))] } renv_settings_validate <- function(name, value) { # NULL implies restore default value if (is.null(value)) return(renv_settings_default(name)) # run coercion method value <- the$settings[[name]]$coerce(value) # validate the user-provided value validate <- the$settings[[name]]$validate ok <- case( is.character(validate) ~ value %in% validate, is.function(validate) ~ validate(value), TRUE ) if (identical(ok, TRUE)) return(value) # validation failed; warn the user and use default fmt <- "%s is an invalid value for setting '%s'; using default %s instead" default <- renv_settings_default(name) warningf(fmt, deparsed(value), name, deparsed(default)) default } renv_settings_read <- function(path) { filebacked( context = "renv_settings_read", path = path, callback = renv_settings_read_impl ) } renv_settings_read_impl <- function(path) { # check that file exists if (!file.exists(path)) return(NULL) # read settings settings <- case( endswith(path, ".dcf") ~ renv_settings_read_impl_dcf(path), endswith(path, ".json") ~ renv_settings_read_impl_json(path), ~ stopf("don't know how to read settings file %s", renv_path_pretty(path)) ) # keep only known settings known <- ls(envir = the$settings, all.names = TRUE) settings <- keep(settings, known) # validate settings <- enumerate(settings, renv_settings_validate) # merge in defaults defaults <- renv_settings_defaults() missing <- renv_vector_diff(names(defaults), names(settings)) settings[missing] <- defaults[missing] # and return settings } renv_settings_read_impl_dcf <- function(path) { # try to read it dcf <- catch(renv_dcf_read(path)) if (inherits(dcf, "error")) { warning(dcf) return(NULL) } # decode encoded values enumerate(dcf, function(name, value) { case( value == "NULL" ~ NULL, value == "NA" ~ NA, value == "NaN" ~ NaN, value == "TRUE" ~ TRUE, value == "FALSE" ~ FALSE, ~ strsplit(value, "\\s*,\\s*")[[1]] ) }) } renv_settings_read_impl_json <- function(path) { json <- catch(renv_json_read(path)) if (inherits(json, "error")) { warning(json) return(NULL) } json } renv_settings_get <- function(project, name = NULL, default = NULL) { # when 'name' is NULL, return all settings if (is.null(name)) { names <- ls(envir = the$settings, all.names = TRUE) settings <- lapply(names, renv_settings_get, project = project) names(settings) <- names return(settings[order(names(settings))]) } # check for an override via option override <- renv_options_override("renv.settings", name) if (!is.null(override)) return(override) # try to read settings file path <- renv_settings_path(project) settings <- renv_settings_read(path) if (!is.null(settings)) return(settings[[name]]) # if a 'default' value was provided, use it if (!missing(default)) return(default) # no value recorded; use default renv_settings_default(name) } renv_settings_set <- function(project, name, value, persist = TRUE) { # read old settings settings <- renv_settings_get(project) # update setting value old <- settings[[name]] %||% renv_settings_default(name) new <- renv_settings_validate(name, value) settings[[name]] <- new # persist if requested if (persist) renv_settings_persist(project, settings) # save session-cached value path <- renv_settings_path(project) value <- renv_filebacked_set("renv_settings_read", path, settings) # invoke update callback if value changed if (!identical(old, new)) renv_settings_updated(project, name, old, new) # return value invisible(value) } renv_settings_updated <- function(project, name, old, new) { update <- the$settings[[name]]$update %||% function(...) {} update(project, old, new) } renv_settings_persist <- function(project, settings) { path <- renv_settings_path(project) settings <- settings[order(names(settings))] # figure out which settings are scalar scalar <- map_lgl(names(settings), function(name) { the$settings[[name]]$scalar }) # use that to determine which objects should be boxed config <- renv_json_config(box = names(settings)[!scalar]) # write json ensure_parent_directory(path) renv_json_write( object = settings, config = config, file = path ) } renv_settings_merge <- function(settings, merge) { settings[names(merge)] <- merge settings } renv_settings_path <- function(project) { renv_paths_settings(project = project) } # nocov start renv_settings_updated_cache <- function(project, old, new) { # if the cache is being disabled, then copy packages from their # symlinks back into the library. note that we don't use symlinks # on windows (we use hard links) so in that case there's nothing # to be done if (renv_platform_windows()) return(FALSE) library <- renv_paths_library(project = project) pkgpaths <- list.files(library, full.names = TRUE) cachepaths <- map_chr(pkgpaths, renv_cache_path) names(pkgpaths) <- cachepaths if (empty(pkgpaths)) { fmt <- "- The cache has been %s for this project." writef(fmt, if (new) "enabled" else "disabled") return(TRUE) } printf("- Synchronizing project library with the cache ... ") if (new) { # enabling the cache: for any package in the project library, replace # that copy with a symlink into the cache, moving the associated package # into the cache if appropriate # ignore existing symlinks; only copy 'real' packages into the cache pkgtypes <- renv_file_type(pkgpaths) cachepaths <- cachepaths[pkgtypes != "symlink"] # move packages from project library into cache callback <- renv_progress_callback(renv_cache_move, length(cachepaths)) enumerate(cachepaths, callback, overwrite = FALSE) } else { # disabling the cache: for any package which is a symlink into the cache, # replace that symlink with a copy of the cached package # figure out which package directories are symlinks pkgtypes <- renv_file_type(pkgpaths) pkgpaths <- pkgpaths[pkgtypes == "symlink"] # remove the existing symlinks unlink(pkgpaths) # overwrite these symlinks with packages from the cache callback <- renv_progress_callback(renv_file_copy, length(pkgpaths)) enumerate(pkgpaths, callback, overwrite = TRUE) } writef("Done!") fmt <- "- The cache has been %s for this project." writef(fmt, if (new) "enabled" else "disabled") } renv_settings_updated_ignore <- function(project, old, new) { renv_infrastructure_write_gitignore(project = project) } renv_settings_migrate <- function(project) { old <- renv_paths_renv("settings.dcf", project = project) if (!file.exists(old)) return() new <- renv_paths_renv("settings.json", project = project) if (file.exists(new)) return() # update settings settings <- renv_settings_read(old) renv_settings_persist(project, settings) } renv_settings_impl <- function(name, default, scalar, validate, coerce, update) { force(name) the$settings[[name]] <- list( default = default, coerce = coerce, scalar = scalar, validate = validate, update = update ) function(value, project = NULL, persist = TRUE) { project <- renv_project_resolve(project) if (missing(value)) renv_settings_get(project, name) else renv_settings_set(project, name, value, persist) } } # nocov end #' Project settings #' #' @description #' Define project-local settings that can be used to adjust the behavior of #' renv with your particular project. #' #' * Get the current value of a setting with (e.g.) `settings$snapshot.type()` #' * Set current value of a setting with (e.g.) #' `settings$snapshot.type("explicit")`. #' #' Settings are automatically persisted across project sessions by writing to #' `renv/settings.json`. You can also edit this file by hand, but you'll need #' to restart the session for those changes to take effect. #' #' ## `bioconductor.version` #' #' The Bioconductor version to be used with this project. Use this if you'd #' like to lock the version of Bioconductor used on a per-project basis. #' When unset, renv will try to infer the appropriate Bioconductor release #' using the BiocVersion package if installed; if not, renv uses #' `BiocManager::version()` to infer the appropriate Bioconductor version. #' #' ## `external.libraries` #' #' A vector of library paths, to be used in addition to the project's own #' private library. This can be useful if you have a package available for use #' in some system library, but for some reason renv is not able to install #' that package (e.g. sources or binaries for that package are not publicly #' available, or you have been unable to orchestrate the pre-requisites for #' installing some packages from source on your machine). #' #' ## `ignored.packages` #' #' A vector of packages, which should be ignored when attempting to snapshot #' the project's private library. Note that if a package has already been #' added to the lockfile, that entry in the lockfile will not be ignored. #' #' ## `package.dependency.fields` #' #' When explicitly installing a package with `install()`, what fields #' should be used to determine that packages dependencies? The default #' uses `Imports`, `Depends` and `LinkingTo` fields, but you also want #' to install `Suggests` dependencies for a package, you can set this to #' `c("Imports", "Depends", "LinkingTo", "Suggests")`. #' #' ## `ppm.enabled` #' #' Enable [Posit Package Manager](https://packagemanager.posit.co/) #' integration in this project? When `TRUE`, renv will attempt to transform #' repository URLs used by PPM into binary URLs as appropriate for the #' current Linux platform. Set this to `FALSE` if you'd like to continue using #' source-only PPM URLs, or if you find that renv is improperly transforming #' your repository URLs. You can still set and use PPM repositories with this #' option disabled; it only controls whether renv tries to transform source #' repository URLs into binary URLs on your behalf. #' #' ## `ppm.ignored.urls` #' #' When [Posit Package Manager](https://packagemanager.posit.co/) integration #' is enabled, `renv` will attempt to transform source repository URLs into #' binary repository URLs. This setting can be used if you'd like to avoid this #' transformation with some subset of repository URLs. #' #' ## `r.version` #' #' The version of \R to encode within the lockfile. This can be set as a #' project-specific option if you'd like to allow multiple users to use #' the same renv project with different versions of \R. renv will #' still warn the user if the major + minor version of \R used in a project #' does not match what is encoded in the lockfile. #' #' ## `snapshot.type` #' #' The type of snapshot to perform by default. See [snapshot] for more #' details. #' #' ## `use.cache` #' #' Enable the renv package cache with this project. When active, renv will #' install packages into a global cache, and link packages from the cache into #' your renv projects as appropriate. This can greatly save on disk space #' and install time when for \R packages which are used across multiple #' projects in the same environment. #' #' ## `vcs.manage.ignores` #' #' Should renv attempt to manage the version control system's ignore files #' (e.g. `.gitignore`) within this project? Set this to `FALSE` if you'd #' prefer to take control. Note that if this setting is enabled, you will #' need to manually ensure internal data in the project's `renv/` folder #' is explicitly ignored. #' #' ## `vcs.ignore.cellar` #' #' Set whether packages within a project-local package cellar are excluded #' from version control. See `vignette("package-sources", package = "renv")` #' for more information. #' #' ## `vcs.ignore.library` #' #' Set whether the renv project library is excluded from version control. #' #' ## `vcs.ignore.local` #' #' Set whether renv project-specific local sources are excluded from version #' control. #' #' # Defaults #' #' You can change the default values of these settings for newly-created renv #' projects by setting \R options for `renv.settings` or `renv.settings.`. #' For example: #' #' ```R #' options(renv.settings = list(snapshot.type = "all")) #' options(renv.settings.snapshot.type = "all") #' ``` #' #' If both of the `renv.settings` and `renv.settings.` options are set #' for a particular key, the option associated with `renv.settings.` is #' used instead. We recommend setting these in an appropriate startup profile, #' e.g. `~/.Rprofile` or similar. #' #' @return #' A named list of renv settings. #' #' @format NULL #' #' @export #' #' @examples #' #' \dontrun{ #' #' # view currently-ignored packaged #' renv::settings$ignored.packages() #' #' # ignore a set of packages #' renv::settings$ignored.packages("devtools", persist = FALSE) #' #' } settings <- list( bioconductor.version = renv_settings_impl( name = "bioconductor.version", default = NULL, scalar = TRUE, validate = is.character, coerce = as.character, update = NULL ), ignored.packages = renv_settings_impl( name = "ignored.packages", default = character(), scalar = FALSE, validate = is.character, coerce = as.character, update = NULL ), external.libraries = renv_settings_impl( name = "external.libraries", default = character(), scalar = FALSE, validate = is.character, coerce = as.character, update = NULL ), package.dependency.fields = renv_settings_impl( name = "package.dependency.fields", default = c("Imports", "Depends", "LinkingTo"), scalar = FALSE, validate = is.character, coerce = as.character, update = NULL ), ppm.enabled = renv_settings_impl( name = "ppm.enabled", default = NULL, scalar = TRUE, validate = is.logical, coerce = as.logical, update = NULL ), ppm.ignored.urls = renv_settings_impl( name = "ppm.ignored.urls", default = NULL, scalar = FALSE, validate = is.character, coerce = as.character, update = NULL ), r.version = renv_settings_impl( name = "r.version", default = NULL, scalar = TRUE, validate = is.character, coerce = as.character, update = NULL ), snapshot.type = renv_settings_impl( name = "snapshot.type", default = "implicit", scalar = TRUE, validate = c("all", "custom", "implicit", "explicit", "packrat", "simple"), coerce = as.character, update = NULL ), use.cache = renv_settings_impl( name = "use.cache", default = TRUE, scalar = TRUE, validate = is.logical, coerce = as.logical, update = renv_settings_updated_cache ), vcs.manage.ignores = renv_settings_impl( name = "vcs.manage.ignores", default = TRUE, scalar = TRUE, validate = is.logical, coerce = as.logical, update = NULL ), vcs.ignore.cellar = renv_settings_impl( name = "vcs.ignore.cellar", default = TRUE, scalar = TRUE, validate = is.logical, coerce = as.logical, update = renv_settings_updated_ignore ), vcs.ignore.library = renv_settings_impl( name = "vcs.ignore.library", default = TRUE, scalar = TRUE, validate = is.logical, coerce = as.logical, update = renv_settings_updated_ignore ), vcs.ignore.local = renv_settings_impl( name = "vcs.ignore.local", default = TRUE, scalar = TRUE, validate = is.logical, coerce = as.logical, update = renv_settings_updated_ignore ) ) renv/R/p3m.R0000644000176200001440000002201114731330073012244 0ustar liggesusers renv_p3m_enabled <- function() { !identical(getOption("pkgType"), "source") && config$ppm.enabled() } renv_p3m_database_path <- function() { renv_paths_p3m("packages.rds") } renv_p3m_database_encode <- function(database) { database <- as.list(database) encoded <- lapply(database, renv_p3m_database_encode_impl) encoded[order(names(encoded))] } renv_p3m_database_encode_impl <- function(entry) { entry <- as.list(entry) keys <- names(entry) vals <- unlist(entry) splat <- strsplit(keys, " ", fixed = TRUE) encoded <- data_frame( Package = map_chr(splat, `[[`, 1L), Version = map_chr(splat, `[[`, 2L), Date = as.integer(vals) ) encoded <- encoded[order(encoded$Package, encoded$Version), ] rownames(encoded) <- NULL encoded$Package <- as.factor(encoded$Package) encoded$Version <- as.factor(encoded$Version) encoded } renv_p3m_database_decode <- function(encoded) { decoded <- lapply(encoded, renv_p3m_database_decode_impl) list2env(decoded, parent = emptyenv()) } renv_p3m_database_decode_impl <- function(entry) { entry$Package <- as.character(entry$Package) entry$Version <- as.character(entry$Version) keys <- paste(entry$Package, entry$Version) vals <- as.list(entry$Date) names(vals) <- keys envir <- list2env(vals, parent = emptyenv()) attr(envir, "keys") <- keys envir } renv_p3m_database_save <- function(database, path = NULL) { path <- path %||% renv_p3m_database_path() ensure_parent_directory(path) encoded <- renv_p3m_database_encode(database) conn <- xzfile(path) defer(close(conn)) saveRDS(encoded, file = conn, version = 2L) } renv_p3m_database_load <- function(path = NULL) { filebacked( context = "renv_p3m_database_load", path = path %||% renv_p3m_database_path(), callback = renv_p3m_database_load_impl ) } renv_p3m_database_load_impl <- function(path) { # read from database file if it exists if (file.exists(path)) { encoded <- readRDS(path) return(renv_p3m_database_decode(encoded)) } # otherwise, initialize a new database new.env(parent = emptyenv()) } renv_p3m_database_dates <- function(version, all = TRUE) { # release dates for old versions of R releases <- c( "3.2" = "2015-04-16", "3.3" = "2016-05-03", "3.4" = "2017-04-21", "3.5" = "2018-04-23", "3.6" = "2019-04-26", "4.0" = "2020-04-24", "4.1" = "2021-05-18", "4.2" = "2022-04-22", "4.3" = "2023-04-21", "4.4" = "2024-04-24", "4.5" = "2025-05-18", # a guess "4.6" = "2026-05-18", # a guess NULL ) # find the start date index <- match(version, names(releases)) if (is.na(index)) stopf("no known release date for R %s", version) start <- as.Date(releases[[index]]) if (!all) return(start) # form end date (ensure not in future) # we look 2 releases in the future as R builds binaries for # the previous releases of R as well end <- min( as.Date(releases[[index + 2L]]), as.Date(Sys.time(), tz = "UTC") ) # generate list of dates seq(start, end, by = 1L) } renv_p3m_database_key <- function(platform, version) { sprintf("/bin/%s/contrib/%s", platform, version) } renv_p3m_database_update <- function(platform, version, dates = NULL) { # load database database <- renv_p3m_database_load() # get reference to entry in database (initialize if not yet created) suffix <- renv_p3m_database_key(platform, version) database[[suffix]] <- database[[suffix]] %||% new.env(parent = emptyenv()) entry <- database[[suffix]] # rough release dates for R releases dates <- as.list(dates %||% renv_p3m_database_dates(version)) for (date in dates) { # attempt to update our database entry for this date url <- renv_p3m_url(date, suffix) tryCatch( renv_p3m_database_update_impl(date, url, entry), error = warnify ) } # save at end printf("[%s]: saving database ... ", date) renv_p3m_database_save(database) writef("DONE") } renv_p3m_database_update_impl <- function(date, url, entry) { printf("[%s]: updating package database ... ", date) # get date as number of days since epoch idate <- as.integer(date) # retrieve available packages errors <- new.env(parent = emptyenv()) db <- renv_available_packages_query_impl(url, "binary", errors) if (is.null(db)) { writef("ERROR") return(FALSE) } # insert packages into database for (i in seq_len(nrow(db))) { # construct key for index name <- db[i, "Package"] vers <- db[i, "Version"] key <- paste(name, vers) # update database entry[[key]] <- max(entry[[key]] %||% 0L, idate) } writef("OK") TRUE } renv_p3m_url <- function(date, suffix) { default <- "https://packagemanager.posit.co/cran" root <- Sys.getenv("RENV_MRAN_URL", unset = default) snapshot <- file.path(root, date) paste(snapshot, suffix, sep = "") } renv_p3m_database_url <- function() { default <- "https://rstudio-buildtools.s3.amazonaws.com/renv/package-manager/packages.rds" Sys.getenv("RENV_MRAN_DATABASE_URL", unset = default) } renv_p3m_database_refresh <- function(explicit = TRUE) { if (explicit || renv_p3m_database_refresh_required()) renv_p3m_database_refresh_impl() } renv_p3m_database_refresh_required <- function() { dynamic( key = list(), value = renv_p3m_database_refresh_required_impl() ) } renv_p3m_database_refresh_required_impl <- function() { # if the cache doesn't exist, we must refresh path <- renv_p3m_database_path() if (!file.exists(path)) return(TRUE) # if we're using an older version of R, but we have newer package # versions available in the cache, we don't need to refresh db <- tryCatch(renv_p3m_database_load(), error = identity) if (!inherits(db, "error")) { keys <- names(db) versions <- unique(basename(keys)) if (any(versions > getRversion())) return(FALSE) } # read the file mtime info <- renv_file_info(path) if (is.na(info$mtime)) return(FALSE) # if it's older than a day, then we should update difftime(Sys.time(), info$mtime, units = "days") > 1 } renv_p3m_database_refresh_impl <- function() { url <- renv_p3m_database_url() path <- renv_p3m_database_path() if (nzchar(url) && nzchar(path)) { ensure_parent_directory(path) download(url = url, destfile = path, quiet = TRUE) } } renv_p3m_database_sync <- function(platform, version) { # read database database <- renv_p3m_database_load() # read entry for this platform + version combo key <- renv_p3m_database_key(platform, version) entry <- database[[key]] if (is.null(entry)) { database[[key]] <- new.env(parent = emptyenv()) entry <- database[[key]] } # get the last known updated date last <- max(0L, as.integer(as.list(entry))) if (identical(last, 0L)) { date <- renv_p3m_database_dates(version, all = FALSE) last <- as.integer(date) } # get yesterday's date now <- as.integer(as.Date(Sys.time(), tz = "UTC")) - 1L # sync up to the last version's release date dates <- as.integer(renv_p3m_database_dates(version)) now <- min(now, max(dates)) # if we've already in sync, nothing to do if (last >= now) return(FALSE) # invoke update for missing dates writef("==> Synchronizing database (%s/%s)", platform, version) dates <- as.Date(seq(last + 1L, now, by = 1L), origin = "1970-01-01") renv_p3m_database_update(platform, version, dates) writef("Finished synchronizing database (%s/%s)", platform, version) # return TRUE to indicate update occurred return(TRUE) } renv_p3m_database_sync_all <- function() { tryCatch( renv_p3m_database_sync_all_impl(), interrupt = identity ) } renv_p3m_database_sync_all_impl <- function() { # NOTE: this needs to be manually updated since the binary URL for # packages can change from version to version, especially on macOS # # R 3.2 # renv_p3m_database_sync("windows", "3.2") # renv_p3m_database_sync("macosx/mavericks", "3.2") # # # R 3.3 # renv_p3m_database_sync("windows", "3.3") # renv_p3m_database_sync("macosx/mavericks", "3.3") # # # R 3.4 # renv_p3m_database_sync("windows", "3.4") # renv_p3m_database_sync("macosx/el-capitan", "3.4") # # # R 3.5 # renv_p3m_database_sync("windows", "3.5") # renv_p3m_database_sync("macosx/el-capitan", "3.5") # # # R 3.6 # renv_p3m_database_sync("windows", "3.6") # renv_p3m_database_sync("macosx/el-capitan", "3.6") # # # R 4.0 # renv_p3m_database_sync("windows", "4.0") # renv_p3m_database_sync("macosx", "4.0") # R 4.1 renv_p3m_database_sync("windows", "4.1") renv_p3m_database_sync("macosx", "4.1") renv_p3m_database_sync("macosx/big-sur-arm64", "4.1") # R 4.2 renv_p3m_database_sync("windows", "4.2") renv_p3m_database_sync("macosx", "4.2") renv_p3m_database_sync("macosx/big-sur-arm64", "4.2") # R 4.3 renv_p3m_database_sync("windows", "4.3") renv_p3m_database_sync("macosx", "4.3") renv_p3m_database_sync("macosx/big-sur-arm64", "4.3") # R 4.4 renv_p3m_database_sync("windows", "4.4") renv_p3m_database_sync("macosx", "4.4") renv_p3m_database_sync("macosx/big-sur-arm64", "4.4") } renv/R/license.R0000644000176200001440000000063414731330073013176 0ustar liggesusers # used to generate the CRAN-compatible license file in R CMD build renv_license_generate <- function() { # only done if we're building if (!building()) return(FALSE) contents <- c( paste("YEAR:", format(Sys.Date(), "%Y")), "COPYRIGHT HOLDER: Posit Software, PBC" ) writeLines(contents, con = "LICENSE") return(TRUE) } if (identical(.packageName, "renv")) renv_license_generate() renv/R/diagnostics.R0000644000176200001440000001707414761163114014074 0ustar liggesusers #' Print a diagnostics report #' #' Print a diagnostics report, summarizing the state of a project using renv. #' This report can occasionally be useful when diagnosing issues with renv. #' #' @inheritParams renv-params #' #' @return This function is normally called for its side effects. #' #' @export diagnostics <- function(project = NULL) { renv_scope_error_handler() project <- renv_project_resolve(project) renv_project_lock(project = project) if (renv_file_type(project, symlinks = FALSE) != "directory") { fmt <- "project %s is not a directory" stopf(fmt, renv_path_pretty(project)) } renv_scope_options(renv.verbose = TRUE) reporters <- list( renv_diagnostics_os, renv_diagnostics_session, renv_diagnostics_project, renv_diagnostics_status, renv_diagnostics_packages, renv_diagnostics_sysreqs, renv_diagnostics_abi, renv_diagnostics_profile, renv_diagnostics_settings, renv_diagnostics_options, renv_diagnostics_envvars, renv_diagnostics_path, renv_diagnostics_cache ) fmt <- "Diagnostics Report [renv %s]" title <- sprintf(fmt, renv_metadata_version_friendly()) lines <- paste(rep.int("=", nchar(title)), collapse = "") writef(c(title, lines, "")) for (reporter in reporters) { tryCatch(reporter(project), error = renv_error_handler) writef() } } renv_diagnostics_os <- function(project) { if (renv_platform_linux()) { releases <- list.files("/etc", pattern = "-release$", full.names = TRUE) for (release in releases) { writef(header(release)) writeLines(readLines(release)) writef() } } } renv_diagnostics_session <- function(project) { writef(header("Session Info")) renv_scope_options(width = 80) print(sessionInfo()) } renv_diagnostics_project <- function(project) { writef(header("Project")) writef("Project path: %s", renv_path_pretty(project)) } renv_diagnostics_status <- function(project) { writef(header("Status")) status(project = project) } renv_diagnostics_packages <- function(project) { writef(header("Packages")) # collect state of lockfile, library, dependencies lockfile <- renv_diagnostics_packages_lockfile(project) libstate <- renv_diagnostics_packages_library(project) used <- unique(renv_diagnostics_packages_dependencies(project)$Package) # collect recursive package dependencies recdeps <- renv_package_dependencies( packages = used, project = project ) # bundle together all <- c( names(lockfile$Packages), names(libstate$Packages), names(recdeps), used ) # sort all <- csort(unique(all)) # check which packages are direct, indirect requirements deps <- rep.int(NA_character_, length(all)) names(deps) <- all deps[names(recdeps)] <- "indirect" deps[used] <- "direct" # build libpaths for installed packages libpaths <- dirname(map_chr(all, renv_package_find)) # use short form flibpaths <- factor(libpaths, levels = .libPaths()) # construct integer codes (to be reported in data output) libcodes <- as.integer(flibpaths) libcodes[!is.na(libcodes)] <- sprintf("[%i]", libcodes[!is.na(libcodes)]) # add in packages in library data <- data_frame( Library = renv_diagnostics_packages_version(libstate, all), Source = renv_diagnostics_packages_sources(libstate, all), Lockfile = renv_diagnostics_packages_version(lockfile, all), Source = renv_diagnostics_packages_sources(lockfile, all), Path = libcodes, Dependency = deps ) # we explicitly want to use rownames here row.names(data) <- names(deps) # print it out renv_scope_options(width = 9000) print(data, max = 10000) # print library codes fmt <- "[%s]: %s" writef() writef(fmt, format(seq_along(levels(flibpaths))), format(levels(flibpaths))) } renv_diagnostics_sysreqs <- function(project) { if (!renv_platform_linux()) return() writef(header("R System Requirements")) lockfile <- renv_lockfile_create(project) records <- renv_lockfile_records(lockfile) sysreqs <- map(records, `[[`, "SystemRequirements") ok <- renv_sysreqs_check(sysreqs, prompt = FALSE) invisible(ok) } renv_diagnostics_packages_version <- function(lockfile, all) { data <- rep.int(NA_character_, length(all)) names(data) <- all formatted <- map_chr(lockfile$Packages, `[[`, "Version") data[names(formatted)] <- formatted data } renv_diagnostics_packages_sources <- function(lockfile, all) { data <- rep.int(NA_character_, length(all)) names(data) <- all sources <- map_chr(lockfile$Packages, function(record) { record$Repository %||% record$Source %||% "" }) data[names(sources)] <- sources data } renv_diagnostics_packages_lockfile <- function(project) { lockpath <- renv_lockfile_path(project = project) if (!file.exists(lockpath)) { writef("This project has not yet been snapshotted: 'renv.lock' does not exist.") return(list()) } renv_lockfile_read(lockpath) } renv_diagnostics_packages_library <- function(project) { library <- renv_paths_library(project = project) if (!file.exists(library)) { fmt <- "The project library %s does not exist." writef(fmt, renv_path_pretty(library)) } snapshot(project = project, lockfile = NULL, type = "all") } renv_diagnostics_packages_dependencies <- function(project) { renv_dependencies_impl( project, errors = "reported", dev = TRUE ) } renv_diagnostics_abi <- function(project) { writef(header("ABI")) tryCatch( renv_abi_check(), error = function(e) { writef(conditionMessage(e)) } ) } renv_diagnostics_profile <- function(project) { writef(header("User Profile")) userprofile <- "~/.Rprofile" if (!file.exists(userprofile)) return(writef("[no user profile detected]")) deps <- renv_dependencies_impl( userprofile, errors = "reported", dev = TRUE ) if (empty(deps)) return(writef("[no R packages referenced in user profile")) renv_scope_options(width = 200) print(deps) } renv_diagnostics_settings <- function(project) { writef(header("Settings")) str(renv_settings_get(project)) } renv_diagnostics_options <- function(project) { writef(header("Options")) keys <- c( "defaultPackages", "download.file.method", "download.file.extra", "install.packages.compile.from.source", "pkgType", "repos", grep("^renv[.]", names(.Options), value = TRUE) ) vals <- .Options[keys] names(vals) <- keys str(vals) } renv_diagnostics_envvars <- function(project) { writef(header("Environment Variables")) envvars <- convert(as.list(Sys.getenv()), "character") useful <- c( "R_LIBS_USER", "R_LIBS_SITE", "R_LIBS", "HOME", "LANG", "MAKE", grep("_proxy", names(envvars), ignore.case = TRUE, value = TRUE), grep("^RENV_", names(envvars), value = TRUE) ) matches <- envvars[useful] if (empty(matches)) return(writef("[no renv environment variables available]")) names(matches) <- useful matches[is.na(matches)] <- "" matches <- matches[order(names(matches))] keys <- names(matches) vals <- matches formatted <- paste(format(keys), vals, sep = " = ") writef(formatted) } renv_diagnostics_path <- function(project) { writef(header("PATH")) path <- strsplit(Sys.getenv("PATH"), .Platform$path.sep, fixed = TRUE)[[1]] writef(paste("-", path)) } renv_diagnostics_cache <- function(project) { writef(header("Cache")) fmt <- "There are a total of %s installed in the renv cache." cachelist <- renv_cache_list() writef(fmt, nplural("package", length(cachelist))) writef("Cache path: %s", renv_path_pretty(renv_paths_cache())) } renv/R/new.R0000644000176200001440000000072314731330073012344 0ustar liggesusers new <- function(expr) { private <- new.env(parent = renv_envir_self()) public <- new.env(parent = private) for (expr in as.list(substitute(expr))[-1L]) { assigning <- renv_call_matches(expr, names = c("=", "<-")) if (!assigning) return(eval(expr, envir = public)) hidden <- is.symbol(expr[[2L]]) && substring(as.character(expr[[2L]]), 1L, 1L) == "." eval(expr, envir = if (hidden) private else public) } public } renv/R/lockfile.R0000644000176200001440000001647114740260564013361 0ustar liggesusers renv_lockfile_init <- function(project) { lockfile <- list() lockfile$R <- renv_lockfile_init_r(project) lockfile$Python <- renv_lockfile_init_python(project) lockfile$Packages <- list() class(lockfile) <- "renv_lockfile" lockfile } renv_lockfile_init_r_version <- function(project) { # NOTE: older versions of renv may have written out an empty array # for the R version in some cases, so we explicitly check that we # receive a length-one string here. version <- settings$r.version(project = project) if (!pstring(version)) version <- getRversion() format(version) } renv_lockfile_init_r_repos <- function(project, repos = getOption("repos")) { # unset the RStudio attribute if it was set attr(repos, "RStudio") <- NULL # make sure it's a character vector in this scope repos <- convert(repos, "character") # make sure a CRAN repository is set cran <- getOption("renv.repos.cran", "https://cloud.r-project.org") repos[repos == "@CRAN@"] <- cran # remove PPM bits from URL if (renv_ppm_enabled()) { pattern <- "/__[^_]+__/[^/]+/" repos <- sub(pattern, "/", repos) } # all done; return as list convert(repos, "list") } renv_lockfile_init_r <- function(project) { version <- renv_lockfile_init_r_version(project) repos <- renv_lockfile_init_r_repos(project) list(Version = version, Repositories = repos) } renv_lockfile_init_python <- function(project) { python <- Sys.getenv("RENV_PYTHON", unset = NA) if (is.na(python)) return(NULL) if (!file.exists(python)) return(NULL) info <- renv_python_info(python) if (is.null(info)) return(NULL) version <- renv_python_version(python) type <- info$type root <- info$root name <- renv_python_envname(project, root, type) fields <- list() fields$Version <- version fields$Type <- type fields$Name <- name fields } renv_lockfile_fini <- function(lockfile, project) { lockfile$Bioconductor <- renv_lockfile_fini_bioconductor(lockfile, project) lockfile } renv_lockfile_fini_bioconductor <- function(lockfile, project) { # check for explicit version in settings version <- settings$bioconductor.version(project = project) if (length(version)) return(list(Version = version)) # otherwise, check for a package which required Bioconductor records <- renv_lockfile_records(lockfile) if (empty(records)) return(NULL) for (package in c("BiocManager", "BiocInstaller")) if (!is.null(records[[package]])) return(list(Version = renv_bioconductor_version(project = project))) sources <- extract_chr(records, "Source") if ("Bioconductor" %in% sources) return(list(Version = renv_bioconductor_version(project = project))) # nothing found; return NULL NULL } renv_lockfile_path <- function(project) { renv_paths_lockfile(project = project) } renv_lockfile_save <- function(lockfile, project) { file <- renv_lockfile_path(project) renv_lockfile_write(lockfile, file = file) } renv_lockfile_load <- function(project, strict = FALSE) { path <- renv_lockfile_path(project) if (file.exists(path)) return(renv_lockfile_read(path)) if (strict) { abort(c( "This project does not contain a lockfile.", i = "Have you called `snapshot()` yet?" )) } manifest <- file.path(project, "manifest.json") if (file.exists(manifest)) { caution("No lockfile found; creating from `manifest.json`.") renv_lockfile_from_manifest(manifest, path) return(renv_lockfile_read(path)) } renv_lockfile_init(project = project) } renv_lockfile_sort <- function(lockfile) { # extract R records (nothing to do if empty) records <- renv_lockfile_records(lockfile) if (empty(records)) return(lockfile) # sort the records sorted <- records[csort(names(records))] renv_lockfile_records(lockfile) <- sorted # sort top-level fields fields <- unique(c("R", "Bioconductor", "Python", "Packages", names(lockfile))) lockfile <- lockfile[intersect(fields, names(lockfile))] # return post-sort lockfile } renv_lockfile_create <- function(project, type = NULL, libpaths = NULL, packages = NULL, exclude = NULL, prompt = NULL, force = NULL, dev = FALSE) { libpaths <- libpaths %||% renv_libpaths_all() type <- type %||% settings$snapshot.type(project = project) # use a restart, so we can allow the user to install packages before snapshot lockfile <- withRestarts( renv_lockfile_create_impl(project, type, libpaths, packages, exclude, prompt, force, dev = dev), renv_recompute_records = function() { renv_dynamic_reset() renv_lockfile_create_impl(project, type, libpaths, packages, exclude, prompt, force, dev = dev) } ) } renv_lockfile_create_impl <- function(project, type, libpaths, packages, exclude, prompt, force, dev = FALSE) { lockfile <- renv_lockfile_init(project) # compute the project's top-level package dependencies packages <- packages %||% renv_snapshot_dependencies( project = project, type = type, dev = dev ) # expand the recursive dependencies of these packages records <- renv_snapshot_packages( packages = setdiff(packages, exclude), libpaths = libpaths, project = project ) # check for missing packages ignored <- c(renv_project_ignored_packages(project), renv_packages_base(), exclude, "renv") missing <- setdiff(packages, c(names(records), ignored)) # cancel automatic snapshots if we have missing packages if (length(missing) && the$auto_snapshot_running) { cancel <- findRestart("cancel") if (isRestart(cancel)) invokeRestart(cancel) } # give user a chance to handle missing packages, if any # # we only run this in top-level calls to snapshot() since renv will internally # use snapshot() to create lockfiles, and missing packages are understood / # tolerated there. this code mostly exists so interactive usages of snapshot() # can recover and install missing packages if (identical(topfun(), snapshot)) renv_snapshot_report_missing(missing, type) records <- renv_snapshot_fixup(records) renv_lockfile_records(lockfile) <- records lockfile <- renv_lockfile_fini(lockfile, project) keys <- unique(c("R", "Bioconductor", names(lockfile))) lockfile <- lockfile[intersect(keys, names(lockfile))] class(lockfile) <- "renv_lockfile" lockfile } renv_lockfile_modify <- function(lockfile, records) { enumerate(records, function(package, record) { renv_lockfile_records(lockfile)[[package]] <<- record }) lockfile } renv_lockfile_compact <- function(lockfile) { records <- renv_lockfile_records(lockfile) remotes <- map_chr(records, renv_record_format_remote) remotes <- remotes[sort(names(remotes))] formatted <- sprintf(" %s = \"%s\"", format(names(remotes)), remotes) joined <- paste(formatted, collapse = ",\n") all <- c("renv::use(", joined, ")") paste(all, collapse = "\n") } renv_lockfile_records <- function(lockfile) { as.list(lockfile$Packages %||% lockfile) } `renv_lockfile_records<-` <- function(x, value) { x$Packages <- filter(value, zlength) invisible(x) } # for compatibility with older versions of RStudio renv_records <- renv_lockfile_records renv/R/isolate.R0000644000176200001440000000451614731330073013217 0ustar liggesusers #' Isolate a project #' #' Copy packages from the renv cache directly into the project library, so #' that the project can continue to function independently of the renv cache. #' #' After calling `isolate()`, renv will still be able to use the cache on #' future [install()]s and [restore()]s. If you'd prefer that renv copy #' packages from the cache, rather than use symlinks, you can set the renv #' configuration option: #' #' ``` #' options(renv.config.cache.symlinks = FALSE) #' ``` #' #' to force renv to copy packages from the cache, as opposed to symlinking #' them. If you'd like to disable the cache altogether for a project, you can #' use: #' #' ``` #' settings$use.cache(FALSE) #' ``` #' #' to explicitly disable the cache for the project. #' #' @inherit renv-params #' #' @export #' #' @examples #' \dontrun{ #' #' # isolate a project #' renv::isolate() #' #' } isolate <- function(project = NULL) { project <- renv_project_resolve(project) renv_project_lock(project = project) if (renv_platform_windows()) renv_isolate_windows(project) else renv_isolate_unix(project) invisible(project) } renv_isolate_unix <- function(project) { library <- renv_paths_library(project = project) targets <- list.files(library, full.names = TRUE) sources <- Sys.readlink(targets) islink <- !is.na(sources) & nzchar(sources) sources <- sources[islink] targets <- targets[islink] names(targets) <- sources if (length(targets)) { printf("- Copying packages into the private library ... ") unlink(targets) copy <- renv_progress_callback(renv_file_copy, length(targets)) enumerate(targets, copy, overwrite = TRUE) writef("Done!") } writef("- This project has been isolated from the cache.") invisible(project) } renv_isolate_windows <- function(project) { library <- renv_paths_library(project = project) targets <- list.files(library, full.names = TRUE) sources <- map_chr(targets, renv_cache_path) names(targets) <- sources if (length(targets)) { printf("- Copying packages into the private library ... ") targets <- targets[file.exists(sources)] unlink(targets) copy <- renv_progress_callback(renv_file_copy, length(targets)) enumerate(targets, copy, overwrite = TRUE) writef("Done!") } writef("- This project has been isolated from the cache.") invisible(project) } renv/R/remotes.R0000644000176200001440000006652214753434721013253 0ustar liggesusers #' Resolve a Remote #' #' Given a remote specification, resolve it into an renv package record that #' can be used for download and installation (e.g. with [install]). #' #' @param spec A remote specification. This should be a string, conforming #' to the Remotes specification as defined in #' . #' remote <- function(spec) { renv_scope_error_handler() renv_remotes_resolve(spec) } # take a short-form remotes spec, parse that into a remote, # and generate a corresponding package record renv_remotes_resolve <- function(spec, latest = FALSE, infer = FALSE) { # check for already-resolved specs if (is.null(spec) || is.list(spec)) return(spec) # check for a package name prefix and remove it regexps <- .standard_regexps() pattern <- sprintf("^%s=", regexps$valid_package_name) spec <- sub(pattern, "", spec) # remove a trailing slash # https://github.com/rstudio/renv/issues/1135 spec <- gsub("/+$", "", spec, perl = TRUE) # check if we should infer the package version infer <- infer && grepl(renv_regexps_package_name(), spec) && renv_package_installed(spec) if (infer) spec <- paste(spec, renv_package_version(spec), sep = "@") # check for archive URLs -- this is a bit hacky if (grepl("^(?:file|https?)://", spec)) { for (suffix in c(".zip", ".tar.gz", ".tgz", "/tarball")) if (endswith(spec, suffix)) return(renv_remotes_resolve_url(spec, quiet = TRUE)) } # remove github prefix spec <- gsub("^https?://(?:www\\.)?github\\.com/", "", spec) # check for paths to existing local files first <- substring(spec, 1L, 1L) local <- first %in% c("~", "/", ".") || renv_path_absolute(spec) if (local) { record <- catch(renv_remotes_resolve_path(spec)) if (!inherits(record, "error")) return(record) } # check for explicit local remotes if (grepl("^local::", spec)) { spec <- substring(spec, 8L) record <- catch(renv_remotes_resolve_path(spec)) if (!inherits(record, "error")) return(record) } # check for requests to install local packages -- note that depending on how # the R package was built / generated, it's possible that it might not adhere # to the "typical" R package names, so we try to be a bit flexible here ext <- "(?:\\.tar\\.gz|\\.tgz|\\.zip)$" if (grepl(ext, spec, perl = TRUE)) { pathlike <- tryCatch(file.exists(spec), condition = identity) if (identical(pathlike, TRUE)) { return(renv_remotes_resolve_path(spec)) } } # define error handler (tag error with extra context when possible) error <- function(e) { # build error message fmt <- "failed to resolve remote '%s'" prefix <- sprintf(fmt, spec) message <- paste(prefix, e$message, sep = " -- ") # otherwise, propagate the error stop(simpleError(message = message, call = e$call)) } # attempt the parse withCallingHandlers( renv_remotes_resolve_impl(spec, latest), error = error ) } renv_remotes_resolve_impl <- function(spec, latest = FALSE) { remote <- renv_remotes_parse(spec) # fixup for bioconductor isbioc <- identical(remote$type, "repository") && identical(remote$repository, "bioc") if (isbioc) remote$type <- "bioc" # treat HEAD refs as an implicit request to use the default branch # of the associated remote repository # https://github.com/rstudio/renv/issues/2040 if (identical(remote$ref, "HEAD")) remote$ref <- NULL resolved <- switch( remote$type, bioc = renv_remotes_resolve_bioc(remote), bitbucket = renv_remotes_resolve_bitbucket(remote), gitlab = renv_remotes_resolve_gitlab(remote), github = renv_remotes_resolve_github(remote), repository = renv_remotes_resolve_repository(remote, latest), git = renv_remotes_resolve_git(remote), url = renv_remotes_resolve_url(remote$url, quiet = TRUE), stopf("unknown remote type '%s'", remote$type %||% "") ) # ensure that attributes on the record are preserved, but drop NULL entries for (key in names(resolved)) if (is.null(resolved[[key]])) resolved[[key]] <- NULL resolved } renv_remotes_parse_impl <- function(spec, pattern, fields, perl = FALSE) { matches <- regexec(pattern, spec, perl = perl) strings <- regmatches(spec, matches)[[1]] if (empty(strings)) stopf("'%s' is not a valid remote", spec) if (length(fields) != length(strings)) stop("internal error: field length mismatch in renv_remotes_parse_impl") names(strings) <- fields remote <- as.list(strings) lapply(remote, function(item) if (nzchar(item)) item) } renv_remotes_parse_repos <- function(spec) { pattern <- paste0( "^", # start "(?:([^:]+)::)?", # optional repository name "([[:alnum:].]+)", # package name "(?:@([[:digit:]_.-]+))?", # optional package version "$" ) fields <- c("spec", "repository", "package", "version") renv_remotes_parse_impl(spec, pattern, fields) } renv_remotes_parse_remote <- function(spec) { pattern <- paste0( "^", "(?:([[:alpha:]][[:alnum:].]*[[:alnum:]])=)?", # optional package name "(?:([^@:]+)(?:@([^:]+))?::)?", # optional prefix, providing type + host "([^/#@:]+)", # a username "(?:/([^@#:]+))?", # a repository (allow sub-repositories) "(?::([^@#:]+))?", # optional subdirectory "(?:#([^@#:]+))?", # optional hash (e.g. pull request) "(?:@([^@#:]+))?", # optional ref (e.g. branch or commit) "$" ) fields <- c( "spec", "package", "type", "host", "user", "repo", "subdir", "pull", "ref" ) remote <- renv_remotes_parse_impl(spec, pattern, fields) if (!nzchar(remote$repo)) stopf("'%s' is not a valid remote", spec) renv_remotes_parse_finalize(remote) } renv_remotes_parse_gitssh <- function(spec) { pattern <- paste0( "^", "(?:([[:alpha:]][[:alnum:].]*[[:alnum:]])=)?", # optional package name "(?:(git)::)?", # optional git prefix "(", # url start "([^@]+)@", # user (typically, 'git') "([^:]+):", # host "([^:#@]+)", # the rest of the repo url ")", # url end "(?::([^@#:]+))?", # optional sub-directory "(?:#([^@#:]+))?", # optional hash (e.g. pull request) "(?:@([^@#:]+))?", # optional ref (e.g. branch or commit) "$" ) fields <- c( "spec", "package", "type", "url", "user", "host", "repo", "subdir", "pull", "ref" ) remote <- renv_remotes_parse_impl(spec, pattern, fields, perl = TRUE) if (!nzchar(remote$repo)) stopf("'%s' is not a valid remote", spec) remote$type <- remote$type %||% "git" renv_remotes_parse_finalize(remote) } renv_remotes_parse_git <- function(spec) { hostpattern <- paste0( "(", "(?:(?:(?!-))(?:xn--|_{1,1})?[a-z0-9-]{0,61}[a-z0-9]{1,1}\\.)*", "(?:xn--)?", "(?:[a-z0-9][a-z0-9\\-]{0,60}|[a-z0-9-]{1,30}\\.[a-z]{2,})", ")" ) pattern <- paste0( "^", "(?:([[:alpha:]][[:alnum:].]*[[:alnum:]])=)?", # optional package name "(?:(git)::)?", # optional git prefix "(", # URL start "(?:(https?|git|ssh)://)?", # protocol "(?:([^@]+)@)?", # login (probably git) hostpattern, # host "[/:]([\\w_.-]+)", # a username "(?:/([^@#:]+?))?", # a repository (allow sub-repositories) "(?:\\.(git))?", # optional .git extension ")", # URL end "(?::([^@#:]+))?", # optional sub-directory "(?:#([^@#:]+))?", # optional hash (e.g. pull request) "(?:@([^@#:]+))?", # optional ref (e.g. branch or commit) "$" ) fields <- c( "spec", "package", "type", "url", "protocol", "login", "host", "user", "repo", "ext", "subdir", "pull", "ref" ) remote <- renv_remotes_parse_impl(spec, pattern, fields, perl = TRUE) if (!nzchar(remote$repo)) stopf("'%s' is not a valid remote", spec) # If type has not been found & repo looks like a git repo, set it as git # (note that this parser also accepts entries which are not truly git # references, so we try to "fix up" after the fact) if ("git" %in% c(remote$login, remote$type, remote$ext, remote$protocol)) remote$type <- tolower(remote$type %||% "git") renv_remotes_parse_finalize(remote) } # NOTE: to avoid ambiguity with git remote specs, we require URL # remotes to begin with a 'url::' prefix renv_remotes_parse_url <- function(spec) { pattern <- paste0( "^", "(?:([[:alpha:]][[:alnum:].]*[[:alnum:]])=)?", # optional package name "(url)::", # type (required for URL remotes) "((https?)://([^:]+))", # url, protocol, path "(?::([^@#:]+))?", # optional subdir "$" ) fields <- c("spec", "package", "type", "url", "protocol", "path", "subdir") remote <- renv_remotes_parse_impl(spec, pattern, fields, perl = TRUE) if (!nzchar(remote$url)) stopf("'%s' is not a valid remote", spec) renv_remotes_parse_finalize(remote) } renv_remotes_parse_finalize <- function(remote) { # default remote type is github remote$type <- tolower(remote$type %||% "github") # custom finalization for different remote types case( remote$type == "github" ~ renv_remotes_parse_finalize_github(remote), TRUE ~ remote ) } renv_remotes_parse_finalize_github <- function(remote) { # split repo spec into pieces repo <- remote$repo %||% "" parts <- strsplit(repo, "/", fixed = TRUE)[[1]] if (length(parts) < 2) return(remote) # form subdir from tail of repo remote$repo <- paste(head(parts, n = 1L), collapse = "/") remote$subdir <- paste(tail(parts, n = -1L), collapse = "/") # return modified remote remote } renv_remotes_parse <- function(spec) { remote <- catch(renv_remotes_parse_repos(spec)) if (!inherits(remote, "error")) { remote$type <- "repository" return(remote) } remote <- catch(renv_remotes_parse_remote(spec)) if (!inherits(remote, "error")) { remote$type <- remote$type %||% "github" return(remote) } remote <- catch(renv_remotes_parse_gitssh(spec)) if (!inherits(remote, "error")) { remote$type <- remote$type %||% "git" return(remote) } remote <- catch(renv_remotes_parse_url(spec)) if (!inherits(remote, "error")) { remote$type <- remote$type %||% "url" return(remote) } remote <- catch(renv_remotes_parse_git(spec)) if (!inherits(remote, "error")) { remote$type <- remote$type %||% "git" return(remote) } stopf("failed to parse remote spec '%s'", spec) } renv_remotes_resolve_bioc_version <- function(version) { # initialize Bioconductor renv_bioconductor_init() BiocManager <- renv_scope_biocmanager() # handle versions like 'release' and 'devel' versions <- BiocManager$.version_map() row <- versions[versions$BiocStatus == version, ] if (nrow(row)) return(row$Bioc) # otherwise, use the default version BiocManager$version() } renv_remotes_resolve_bioc_plain <- function(remote) { list( Package = remote$package, Version = remote$version, Source = "Bioconductor" ) } renv_remotes_resolve_bioc <- function(remote) { # if we parsed this as a repository remote, use that directly if (!is.null(remote$package)) return(renv_remotes_resolve_bioc_plain(remote)) # otherwise, this was parsed as a regular remote, declaring the package # should be obtained from a particular Bioconductor release package <- remote$repo biocversion <- renv_remotes_resolve_bioc_version(remote$user) biocrepos <- renv_bioconductor_repos(version = biocversion) record <- renv_available_packages_latest(package, repos = biocrepos) # update fields record$Source <- "Bioconductor" record$Repository <- NULL # return the resolved record record } renv_remotes_resolve_bitbucket <- function(remote) { user <- remote$user repo <- remote$repo subdir <- remote$subdir ref <- remote$ref %||% getOption("renv.bitbucket.default_branch", "master") host <- remote$host %||% config$bitbucket.host() # scope authentication renv_scope_auth(repo) # get commit sha for ref fmt <- "%s/repositories/%s/%s/commit/%s" origin <- renv_retrieve_origin(host) url <- sprintf(fmt, origin, user, repo, ref) destfile <- renv_scope_tempfile("renv-bitbucket-") download(url, destfile = destfile, type = "bitbucket", quiet = TRUE) json <- renv_json_read(file = destfile) sha <- json$hash # get DESCRIPTION file fmt <- "%s/repositories/%s/%s/src/%s/DESCRIPTION" origin <- renv_retrieve_origin(host) url <- sprintf(fmt, origin, user, repo, ref) destfile <- renv_scope_tempfile("renv-description-") download(url, destfile = destfile, type = "bitbucket", quiet = TRUE) desc <- renv_dcf_read(destfile) list( Package = desc$Package, Version = desc$Version, Source = "Bitbucket", RemoteType = "bitbucket", RemoteHost = host, RemoteUsername = user, RemoteRepo = repo, RemoteSubdir = subdir, RemoteRef = ref, RemoteSha = sha ) } renv_remotes_resolve_repository <- function(remote, latest) { package <- remote$package if (package %in% renv_packages_base()) return(renv_remotes_resolve_base(package)) version <- remote$version repository <- remote$repository if (latest && is.null(version)) { remote <- renv_available_packages_latest(package) version <- remote$Version repository <- remote$Repository } list( Package = package, Version = version, Source = "Repository", Repository = repository ) } renv_remotes_resolve_base <- function(package) { list( Package = package, Version = renv_package_version(package), Source = "R" ) } renv_remotes_resolve_github_sha_pull <- function(host, user, repo, pull) { # scope authentication renv_scope_auth(repo) # make request fmt <- "%s/repos/%s/%s/pulls/%s" origin <- renv_retrieve_origin(host) url <- sprintf(fmt, origin, user, repo, pull) jsonfile <- renv_scope_tempfile("renv-json-") download(url, destfile = jsonfile, type = "github", quiet = TRUE) # read resulting JSON json <- renv_json_read(jsonfile) json$head$sha } renv_remotes_resolve_github_sha_ref <- function(host, user, repo, ref) { # scope authentication renv_scope_auth(repo) # build url for github commits endpoint fmt <- "%s/repos/%s/%s/commits/%s" origin <- renv_retrieve_origin(host) ref <- ref %||% renv_remotes_resolve_github_ref(host, user, repo) url <- sprintf(fmt, origin, user, repo, ref %||% "main") # prepare headers headers <- c(Accept = "application/vnd.github.sha") # make request to endpoint shafile <- renv_scope_tempfile("renv-sha-") download( url, destfile = shafile, type = "github", quiet = TRUE, headers = headers ) # read downloaded content sha <- renv_file_read(shafile) # check for JSON response (in case our headers weren't sent) if (nchar(sha) > 40L) { json <- renv_json_read(text = sha) sha <- json$sha } sha } renv_remotes_resolve_github_modules <- function(host, user, repo, subdir, sha) { # form path to .gitmodules file subdir <- subdir %||% "" parts <- c( if (nzchar(subdir)) URLencode(subdir), ".gitmodules" ) path <- paste(parts, collapse = "/") # scope authentication renv_scope_auth(repo) # add headers headers <- c(Accept = "application/vnd.github.raw") # get the file contents fmt <- "%s/repos/%s/%s/contents/%s?ref=%s" origin <- renv_retrieve_origin(host) url <- sprintf(fmt, origin, user, repo, path, sha) jsonfile <- renv_scope_tempfile("renv-json-") status <- suppressWarnings( catch( download(url, destfile = jsonfile, type = "github", quiet = TRUE, headers = headers) ) ) # just return a status code whether or not submodules are included !inherits(status, "error") } renv_remotes_resolve_github_description <- function(url, host, user, repo, subdir, sha) { # form DESCRIPTION path subdir <- subdir %||% "" parts <- c( if (nzchar(subdir)) URLencode(subdir), "DESCRIPTION" ) descpath <- paste(parts, collapse = "/") # scope authentication renv_scope_auth(repo) # add headers headers <- c( Accept = "application/vnd.github.raw", renv_download_auth_github(url) ) # get the DESCRIPTION contents fmt <- "%s/repos/%s/%s/contents/%s?ref=%s" origin <- renv_retrieve_origin(host) url <- sprintf(fmt, origin, user, repo, descpath, sha) destfile <- renv_scope_tempfile("renv-json-") download(url, destfile = destfile, type = "github", quiet = TRUE, headers = headers) # try to read the file; detect JSON versus raw content in case # headers were not sent for some reason contents <- renv_file_read(destfile) if (substring(contents, 1L, 1L) == "{") { json <- renv_json_read(text = contents) contents <- renv_base64_decode(json$content) } # normalize newlines contents <- gsub("\r\n", "\n", contents, fixed = TRUE) # read as DCF renv_dcf_read(text = contents) } renv_remotes_resolve_github_ref <- function(host, user, repo) { tryCatch( renv_remotes_resolve_github_ref_impl(host, user, repo), error = function(e) { warning(e) getOption("renv.github.default_branch", default = "main") } ) } renv_remotes_resolve_github_ref_impl <- function(host, user, repo) { # scope authentication renv_scope_auth(repo) # build url to repos endpoint fmt <- "%s/repos/%s/%s" origin <- renv_retrieve_origin(host) url <- sprintf(fmt, origin, user, repo) # download JSON data at endpoint jsonfile <- renv_scope_tempfile("renv-github-ref-", fileext = ".json") download(url, destfile = jsonfile, type = "github", quiet = TRUE) json <- renv_json_read(jsonfile) # read default branch json$default_branch %||% getOption("renv.github.default_branch", default = "main") } renv_remotes_resolve_github <- function(remote) { # resolve the reference associated with this repository host <- remote$host %||% config$github.host() user <- remote$user repo <- remote$repo spec <- remote$spec subdir <- remote$subdir # resolve ref ref <- remote$ref %||% renv_remotes_resolve_github_ref(host, user, repo) # handle '*release' refs if (identical(ref, "*release")) ref <- renv_remotes_resolve_github_release(host, user, repo, spec) # resolve the sha associated with the ref / pull pull <- remote$pull %||% "" sha <- case( nzchar(pull) ~ renv_remotes_resolve_github_sha_pull(host, user, repo, pull), nzchar(ref) ~ renv_remotes_resolve_github_sha_ref(host, user, repo, ref) ) # if an abbreviated sha was provided as the ref, expand it here if (nzchar(ref) && startsWith(sha, ref)) ref <- sha # check whether the repository has a .gitmodules file; if so, then we'll have # to use a plain 'git' client to retrieve the package modules <- renv_remotes_resolve_github_modules(host, user, repo, subdir, sha) # construct full url origin <- fsub("api.github.com", "github.com", renv_retrieve_origin(host)) parts <- c(origin, user, repo) url <- paste(parts, collapse = "/") # read DESCRIPTION desc <- renv_remotes_resolve_github_description(url, host, user, repo, subdir, sha) list( Package = desc$Package, Version = desc$Version, Source = if (modules) "git" else "GitHub", RemoteType = if (modules) "git" else "github", RemoteUrl = if (modules) url, RemoteHost = host, RemoteUsername = user, RemoteRepo = repo, RemoteSubdir = subdir, RemoteRef = ref, RemoteSha = sha ) } renv_remotes_resolve_github_release <- function(host, user, repo, spec) { # scope authentication renv_scope_auth(repo) # build url for github releases endpoint fmt <- "%s/repos/%s/%s/releases?per_page=1" origin <- renv_retrieve_origin(host) url <- sprintf(fmt, origin, user, repo) # prepare headers headers <- c(Accept = "application/vnd.github.raw+json") # make request to endpoint releases <- renv_scope_tempfile("renv-releases-") download( url = url, destfile = releases, type = "github", quiet = TRUE, headers = headers ) # get reference associated with this tag json <- renv_json_read(releases) if (empty(json)) { fmt <- "could not find any releases associated with remote '%s'" stopf(fmt, sub("[*]release$", "", spec)) } json[[1L]][["tag_name"]] } renv_remotes_resolve_git <- function(remote) { package <- remote$package %||% basename(remote$repo) url <- remote$url subdir <- remote$subdir # handle git ref pull <- remote$pull %||% "" ref <- remote$ref %||% "" # resolve ref from pull if set if (nzchar(pull)) ref <- renv_remotes_resolve_git_pull(ref) record <- list( Package = package, Version = "", Source = "git", RemoteType = "git", RemoteUrl = url, RemoteSubdir = subdir, RemoteRef = ref ) desc <- renv_remotes_resolve_git_description(record) record$Package <- desc$Package record$Version <- desc$Version record } renv_remotes_resolve_git_sha_ref <- function(record) { renv_git_preflight() origin <- record$RemoteUrl ref <- record$RemoteRef %||% record$RemoteSha args <- c("ls-remote", origin, ref) output <- local({ renv_scope_auth(record) renv_scope_git_auth() renv_system_exec("git", args, "checking git remote") }) if (empty(output)) return("") # format of output is, for example: # # $ git ls-remote https://github.com/rstudio/renv refs/tags/0.14.0 # 20ca74bdcc3c87848e5665effa2fc8ee8b039c69 refs/tags/0.14.0 # # take first line of output, split on tab character, and take leftmost entry strsplit(output[[1L]], "\t", fixed = TRUE)[[1L]][[1L]] } renv_remotes_resolve_git_description <- function(record) { path <- renv_scope_tempfile("renv-git-") ensure_directory(path) # TODO: is there a cheaper way for us to accomplish this? # it'd be nice if we could retrieve the contents of a single # file, without needing to pull an entire repository branch local({ renv_scope_options(renv.verbose = FALSE) renv_retrieve_git_impl(record, path) }) # subdir may be NULL subdir <- record$RemoteSubdir desc <- renv_description_read(path, subdir = subdir) desc } renv_remotes_resolve_git_pull <- function(pr) { fmt <- "pull/%1$s/head:pull/%1$s" sprintf(fmt, pr) } renv_remotes_resolve_gitlab_ref <- function(host, user, repo) { tryCatch( renv_remotes_resolve_gitlab_ref_impl(host, user, repo), error = function(e) { warning(e) getOption("renv.gitlab.default_branch", default = "master") } ) } renv_remotes_resolve_gitlab_ref_impl <- function(host, user, repo) { # scope authentication renv_scope_auth(repo) # get list of available branches fmt <- "%s/api/v4/projects/%s/repository/branches" origin <- renv_retrieve_origin(host) id <- URLencode(paste(user, repo, sep = "/"), reserved = TRUE) url <- sprintf(fmt, origin, id) destfile <- renv_scope_tempfile("renv-gitlab-commits-") download(url, destfile = destfile, type = "gitlab", quiet = TRUE) json <- renv_json_read(file = destfile) # iterate through and find the default for (info in json) if (identical(info$default, TRUE)) return(info$name) # if no default was found, use master branch # (for backwards compatibility with existing projects) getOption("renv.gitlab.default_branch", default = "master") } renv_remotes_resolve_gitlab <- function(remote) { host <- remote$host %||% config$gitlab.host() user <- remote$user repo <- remote$repo subdir <- remote$subdir %||% "" ref <- remote$ref %||% renv_remotes_resolve_gitlab_ref(host, user, repo) parts <- c(if (nzchar(subdir)) subdir, "DESCRIPTION") descpath <- URLencode(paste(parts, collapse = "/"), reserved = TRUE) # scope authentication renv_scope_auth(repo) # retrieve sha associated with this ref fmt <- "%s/api/v4/projects/%s/repository/commits/%s" origin <- renv_retrieve_origin(host) id <- URLencode(paste(user, repo, sep = "/"), reserved = TRUE) ref <- URLencode(ref, reserved = TRUE) url <- sprintf(fmt, origin, id, ref) destfile <- renv_scope_tempfile("renv-gitlab-commits-") download(url, destfile = destfile, type = "gitlab", quiet = TRUE) json <- renv_json_read(file = destfile) sha <- json$id # retrieve DESCRIPTION file fmt <- "%s/api/v4/projects/%s/repository/files/%s/raw?ref=%s" origin <- renv_retrieve_origin(host) id <- URLencode(paste(user, repo, sep = "/"), reserved = TRUE) url <- sprintf(fmt, origin, id, descpath, ref) destfile <- renv_scope_tempfile("renv-description-") download(url, destfile = destfile, type = "gitlab", quiet = TRUE) desc <- renv_dcf_read(destfile) list( Package = desc$Package, Version = desc$Version, Source = "GitLab", RemoteType = "gitlab", RemoteHost = host, RemoteUsername = user, RemoteRepo = repo, RemoteSubdir = subdir, RemoteRef = ref, RemoteSha = sha ) } renv_remotes_resolve_url <- function(url, quiet = FALSE) { tempfile <- renv_scope_tempfile("renv-url-") writeLines(url, con = tempfile) hash <- md5sum(tempfile) ext <- fileext(url, default = ".tar.gz") name <- paste(hash, ext, sep = "") path <- renv_paths_source("url", name) ensure_parent_directory(path) download(url, path, quiet = quiet) desc <- renv_description_read(path) list( Package = desc$Package, Version = desc$Version, Source = "URL", RemoteType = "url", RemoteUrl = url, Path = path ) } renv_remotes_resolve_path <- function(path) { # if this package lives within one of the cellar paths, # then treat it as a cellar source roots <- renv_cellar_roots() for (root in roots) if (renv_path_within(path, root)) return(renv_remotes_resolve_path_cellar(path)) # first, check for a common extension if (renv_archive_type(path) %in% c("tar", "zip")) return(renv_remotes_resolve_path_impl(path)) # otherwise, if this is the path to a package project, use the sources as-is if (renv_project_type(path) == "package") return(renv_remotes_resolve_path_impl(path)) stopf("there is no package at path '%s'", path) } renv_remotes_resolve_path_cellar <- function(path) { desc <- renv_description_read(path) list( Package = desc$Package, Version = desc$Version, Source = "Cellar", Cacheable = FALSE ) } renv_remotes_resolve_path_impl <- function(path) { desc <- renv_description_read(path) list( Package = desc$Package, Version = desc$Version, Source = "Local", RemoteType = "local", RemoteUrl = path, Cacheable = FALSE ) } renv/R/socket.R0000644000176200001440000000205314731330073013041 0ustar liggesusers # avoid R CMD check errors with older R if (getRversion() < "4.0") { utils::globalVariables(c("serverSocket", "socketAccept")) } renv_socket_server <- function(min = 49152, max = 65535) { # create the socket server port <- socket <- NULL for (i in 1:2000) catch({ port <- sample(min:max, size = 1L) socket <- serverSocket(port) break }) # if we still don't have a socket here, we failed if (is.null(socket)) stop("error creating socket server: couldn't find open port") # return information about the server list( socket = socket, port = port, pid = Sys.getpid() ) } renv_socket_connect <- function(port, open, timeout = getOption("timeout")) { socketConnection( host = "127.0.0.1", port = port, open = open, blocking = TRUE, encoding = "native.enc", timeout = timeout ) } renv_socket_accept <- function(socket, open, timeout = getOption("timeout")) { socketAccept( socket = socket, open = open, blocking = TRUE, encoding = "native.enc", timeout = timeout ) } renv/R/properties.R0000644000176200001440000000236614761163114013757 0ustar liggesusers renv_properties_read <- function(path = NULL, text = NULL, delimiter = ":", dequote = TRUE, trim = TRUE) { renv_scope_options(warn = -1L) # read file contents <- paste(text %||% readLines(path, warn = FALSE), collapse = "\n") # split on newlines; allow spaces to continue a value parts <- strsplit(contents, "\\n(?=\\S)", perl = TRUE)[[1L]] # remove comments and blank lines parts <- grep("^\\s*(?:#|$)", parts, perl = TRUE, value = TRUE, invert = TRUE) # split into key / value pairs index <- regexpr(delimiter, parts, fixed = TRUE) # if we couldn't match a delimiter, treat the whole thing as a key missed <- index == -1 index[missed] <- nchar(parts)[missed] + 1L # perform the subsetting keys <- substring(parts, 1L, index - 1L) vals <- substring(parts, index + 1L) # trim whitespace when requested if (trim) { keys <- trimws(keys) vals <- gsub("\n\\s*", " ", trimws(vals), perl = TRUE) } # strip quotes if requested if (dequote) { keys <- dequote(keys) vals <- dequote(vals) } # return as named list storage.mode(vals) <- "list" names(vals) <- keys vals } renv/R/platform.R0000644000176200001440000000276414761163114013411 0ustar liggesusers the$distro <- NULL the$os <- NULL the$platform <- NULL the$sysinfo <- NULL renv_platform_init <- function() { the$sysinfo <- as.list(Sys.info()) the$platform <- if (file.exists("/etc/os-release")) { renv_properties_read( path = "/etc/os-release", delimiter = "=", dequote = TRUE, trim = TRUE ) } the$os <- tolower(the$sysinfo$sysname) # NOTE: This is chosen to be compatible with the distribution field # used within r-system-requirements. if (the$os == "linux") { aliases <- list(rhel = "redhat") the$distro <- alias(the$platform$ID, aliases) } } renv_platform_unix <- function() { .Platform$OS.type == "unix" } renv_platform_windows <- function() { .Platform$OS.type == "windows" } renv_platform_macos <- function() { the$sysinfo[["sysname"]] == "Darwin" } renv_platform_linux <- function() { the$sysinfo[["sysname"]] == "Linux" } renv_platform_solaris <- function() { the$sysinfo[["sysname"]] == "SunOS" } renv_platform_wsl <- function() { pv <- "/proc/version" if (!file.exists(pv)) return(FALSE) renv_scope_options(warn = -1L) contents <- catch(readLines(pv, warn = FALSE)) if (inherits(contents, "error")) return(FALSE) any(grepl("(?:Microsoft|WSL)", contents, ignore.case = TRUE)) } renv_platform_prefix <- function() { renv_bootstrap_platform_prefix() } renv_platform_os <- function() { renv_bootstrap_platform_os() } renv_platform_machine <- function() { the$sysinfo[["machine"]] } renv/R/xcode.R0000644000176200001440000000236214731330073012656 0ustar liggesusers renv_xcode_available <- function() { # allow bypass if required check <- getOption("renv.xcode.available", default = NULL) if (!is.null(check)) return(check) # otherwise, check via xcode-select status <- suppressWarnings( system2("/usr/bin/xcode-select", "-p", stdout = FALSE, stderr = FALSE) ) identical(status, 0L) } renv_xcode_check <- function() { # allow bypass of xcode check if required check <- getOption("renv.xcode.check", default = TRUE) if (identical(check, FALSE)) return() # only run on macOS if (!renv_platform_macos()) return() # only run check once per session if (once()) return() cmd <- "/usr/bin/xcrun --find --show-sdk-path" status <- system(cmd, ignore.stdout = TRUE, ignore.stderr = TRUE) if (identical(status, 0L)) return() if (identical(status, 69L)) { msg <- " macOS is reporting that you have not yet agreed to the Xcode license. You must accept the Xcode license before R packages can be installed from source. Please run: sudo xcodebuild -license accept in the Terminal to accept the Xcode license. Set options(renv.xcode.check = FALSE) to disable this warning. " warning(msg) } fmt <- "%s returned exit code %i" warningf(fmt, cmd, status) } renv/R/warnings.R0000644000176200001440000000120714731330073013401 0ustar liggesusers renv_warnings_unknown_sources <- function(records) { if (empty(records)) return(FALSE) # TODO: Should this be documented? enabled <- renv_config_get( name = "unknown.sources", scope = "warnings", type = "logical[1]", default = TRUE ) if (!enabled) return(FALSE) renv_scope_options(renv.verbose = TRUE) renv_pretty_print_records( "The following package(s) were installed from an unknown source:", records, c( "renv may be unable to restore these packages in the future.", "Consider reinstalling these packages from a known source (e.g. CRAN)." ) ) return(TRUE) } renv/R/load.R0000644000176200001440000006165514761163114012510 0ustar liggesusers # are we currently running 'load()'? the$load_running <- FALSE #' Load a project #' #' @description #' `renv::load()` sets the library paths to use a project-local library, #' sets up the system library [sandbox], if needed, and creates shims #' for `install.packages()`, `update.packages()`, and `remove.packages()`. #' #' You should not generally need to call `renv::load()` yourself, as it's #' called automatically by the project auto-loader created by [renv::init()]/ #' [renv::activate()]. However, if needed, you can use `renv::load("")` #' to explicitly load an renv project located at a particular path. #' #' # Shims #' #' To help you take advantage of the package cache, renv places a couple of #' shims on the search path: #' #' * `install.packages()` instead calls `renv::install()`. #' * `remove.packages()` instead calls `renv::remove()`. #' * `update.packages()` instead calls `renv::update()`. #' #' This allows you to keep using your existing muscle memory for installing, #' updating, and remove packages, while taking advantage of renv features #' like the package cache. #' #' If you'd like to bypass these shims within an \R session, you can explicitly #' call the version of these functions from the utils package, e.g. with #' `utils::install.packages(<...>)`. #' #' If you'd prefer not to use the renv shims at all, they can be disabled by #' setting the R option `options(renv.config.shims.enabled = FALSE)` or by #' setting the environment variable `RENV_CONFIG_SHIMS_ENABLED = FALSE`. See #' `?config` for more details. #' #' @inherit renv-params #' #' @param quiet Boolean; be quiet during load? #' #' @export #' #' @examples #' \dontrun{ #' #' # load a project -- note that this is normally done automatically #' # by the project's auto-loader, but calling this explicitly to #' # load a particular project may be useful in some circumstances #' renv::load() #' #' } load <- function(project = NULL, quiet = FALSE, profile = NULL, ...) { # forward to base::load() if it looks like that was the intention base <- renv_load_base(sys.call(), envir = parent.frame()) if (!is.null(base)) return(invisible(base)) # eagerly load package namespaces which we rely on requireNamespace("compiler", quietly = TRUE) renv_scope_error_handler() project <- renv_path_normalize( project %||% renv_project_find(project), mustWork = TRUE ) if (!is.null(profile)) renv_profile_set(profile) action <- renv_load_action(project) if (action[[1L]] == "cancel") { cancel(verbose = !autoloading()) } else if (action[[1L]] == "init") { return(init(project)) } else if (action[[1L]] == "alt") { project <- action[[2L]] } else if (action[[1L]] == "none") { return(invisible(project)) } renv_project_lock(project = project) # indicate that we're now loading the project renv_scope_binding(the, "load_running", TRUE) # if load is being called via the autoloader, # then ensure RENV_PROJECT is unset # https://github.com/rstudio/renv/issues/887 if (autoloading()) renv_project_clear() # if we're loading a project different from the one currently loaded, # then unload the current project and reload the requested one switch <- !renv_metadata_embedded() && !is.null(the$project_path) && !identical(project, the$project_path) if (switch) return(renv_load_switch(project)) if (quiet || renv_load_quiet()) renv_scope_options(renv.verbose = FALSE) renv_envvars_save() # load a minimal amount of state when testing if (renv_tests_running()) return(renv_load_minimal(project)) # load rest of renv components renv_load_init(project) renv_load_path(project) renv_load_shims(project) renv_load_renviron(project) renv_load_profile(project) renv_load_settings(project) renv_load_project(project) renv_load_sandbox(project) renv_load_libpaths(project) renv_load_rprofile(project) renv_load_cache(project) # load components encoded in lockfile lockfile <- renv_lockfile_load(project) if (length(lockfile)) { renv_load_r(project, lockfile$R) renv_load_python(project, lockfile$Python) renv_load_bioconductor(project, lockfile$Bioconductor) } # allow failure to write infrastructure here to be non-fatal # https://github.com/rstudio/renv/issues/574#issuecomment-731159197 catch({ renv_infrastructure_write_rbuildignore(project) renv_infrastructure_write_gitignore(project) }) renv_load_finish(project, lockfile) invisible(project) } renv_load_action <- function(project) { # don't do anything in non-interactive sessions if (!interactive()) return("load") # if this project already contains an 'renv' folder, assume it's # already been initialized and we can directly load it renv <- renv_paths_renv(project = project, profile = FALSE) if (dir.exists(renv)) return("load") # check and see if we're being called within a sub-directory path <- renv_file_find(dirname(project), function(parent) { if (file.exists(file.path(parent, "renv"))) return(parent) }) # the project has not yet been initialized; notify the user and ask # what they would like to do fmt <- "The project located at %s has not yet been initialized." header <- sprintf(fmt, renv_path_pretty(project)) # if we're running the autoloader in RStudio, we cannot ask # the user for input at this stage -- instead, just notify them # of the choices available if (autoloading() && renv_rstudio_available()) { initmsg <- "- Use `renv::init()` to initialize this project." loadmsg <- "- Use `renv::load()` to load this project as-is." caution(c(header, initmsg, loadmsg)) return("none") } # otherwise, prompt the user and provide them choices to proceed title <- paste("", header, "", "What would you like to do?", sep = "\n") choices <- c( init = "Initialize this project with `renv::init()`.", load = "Continue loading this project as-is.", cancel = "Cancel loading this project." ) if (!is.null(path)) { fmt <- "Load the project located at %s instead." msg <- sprintf(fmt, renv_path_pretty(path)) choices <- c(choices, alt = msg) } selection <- tryCatch( utils::select.list(choices, title = title, graphics = FALSE), interrupt = identity ) if (inherits(selection, "interrupt")) { writef() selection <- choices["cancel"] } list(names(selection), path) } renv_load_minimal <- function(project) { renv_load_libpaths(project) lockfile <- renv_lockfile_load(project) if (length(lockfile)) { renv_load_r(project, lockfile$R) renv_load_python(project, lockfile$Python) } renv_load_finish(project, lockfile) invisible(project) } renv_load_r <- function(project, fields) { # check for missing fields if (is.null(fields)) { warning("missing required [R] section in lockfile") return(NULL) } # load repositories renv_load_r_repos(fields$Repositories) # load (check) version version <- fields$Version if (is.null(version)) { warning("no R version recorded in this lockfile") return(NULL) } # normalize versions as strings requested <- renv_version_maj_min(version) current <- renv_version_maj_min(getRversion()) # only compare major, minor versions if (!identical(requested, current)) { fmt <- "%s Using R %s (lockfile was generated with R %s)" writef(fmt, info_bullet(), getRversion(), version) } } renv_load_r_repos <- function(repos) { # force a character vector (https://github.com/rstudio/renv/issues/127) repos <- convert(repos, "character") # remove trailing slashes nms <- names(repos) repos <- sub("/+$", "", repos) names(repos) <- nms # transform PPM URLs if enabled # this ensures that install.packages() uses binaries by default on Linux, # where 'getOption("pkgType")' is "source" by default if (renv_ppm_enabled()) repos <- renv_ppm_transform(repos) # normalize option repos <- renv_repos_normalize(repos) # set sanitized repos options(repos = repos) # and return repos } renv_load_init <- function(project) { # warn if the project path cannot be translated into the native encoding, # as (especially on Windows) this will likely prevent renv from working actual <- enc2utf8(project) expected <- catch(enc2utf8(enc2native(actual))) if (identical(actual, expected)) return(TRUE) msg <- paste( "the project path cannot be represented in the native encoding;", "renv may not function as expected" ) warning(msg) } renv_load_path <- function(project) { # only required when running in RStudio if (!renv_rstudio_available()) return(FALSE) # on macOS, read paths from /etc/paths and friends # nocov start if (renv_platform_macos()) { # read the current PATH old <- Sys.getenv("PATH", unset = "") %>% strsplit(split = .Platform$path.sep, fixed = TRUE) %>% unlist() # get the new PATH entries files <- c( if (file.exists("/etc/paths")) "/etc/paths", list.files("/etc/paths.d", full.names = TRUE) ) new <- uapply(files, readLines, warn = FALSE) # mix them together, preferring things in /etc/paths mix <- unique(c(new, old)) # update the PATH Sys.setenv(PATH = paste(mix, collapse = .Platform$path.sep)) } # nocov end } renv_load_shims <- function(project) { if (renv_shims_enabled()) renv_shims_activate() } renv_load_renviron <- function(project) { environs <- c( renv_paths_root(".Renviron"), if (config$user.environ()) Sys.getenv("R_ENVIRON_USER", unset = "~/.Renviron"), file.path(project, ".Renviron") ) for (environ in environs) if (file.exists(environ)) readRenviron(environ) renv_envvars_normalize() } renv_load_profile <- function(project) { renv_bootstrap_profile_load(project = project) } renv_load_settings <- function(project) { # migrate settings.dcf => settings.json renv_settings_migrate(project = project) # load settings.R settings <- renv_paths_renv("settings.R", project = project) if (!file.exists(settings)) return(FALSE) tryCatch( eval(parse(settings), envir = baseenv()), error = warnify ) TRUE } renv_load_project <- function(project) { # update project list if enabled if (renv_cache_config_enabled(project = project)) { project <- renv_path_normalize(project) renv_load_project_projlist(project) } TRUE } renv_load_project_projlist <- function(project) { # read project list projects <- renv_paths_root("projects") projlist <- character() if (file.exists(projects)) projlist <- readLines(projects, warn = FALSE, encoding = "UTF-8") # if the project is already recorded, nothing to do if (project %in% projlist) return(TRUE) # sort with C locale (ensure consistent sorting across OSes) projlist <- csort(c(projlist, project)) # update the project list ensure_parent_directory(projects) catchall(writeLines(enc2utf8(projlist), con = projects, useBytes = TRUE)) TRUE } renv_load_rprofile <- function(project = NULL) { project <- renv_project_resolve(project) # bail if not enabled by user enabled <- identical(config$user.profile(), TRUE) if (!enabled) return(FALSE) # callr will manage sourcing of user profile, so don't try # to source the user profile if we're in callr callr <- Sys.getenv("CALLR_CHILD_R_LIBS", unset = NA) if (!is.na(callr)) return(FALSE) # check for existence of profile profile <- Sys.getenv("R_PROFILE_USER", unset = "~/.Rprofile") if (!file.exists(profile)) return(FALSE) renv_scope_libpaths() renv_load_rprofile_impl(profile) TRUE } renv_load_rprofile_impl <- function(profile) { # NOTE: We'd like to use a regular tryCatch() handler here, but # that will cause issues for user profiles which attempt to add # global calling handlers. For that reason, we just register a # bare restart handler, so at least we can catch the jump. # # https://github.com/rstudio/renv/issues/1036 status <- withRestarts( sys.source(profile, envir = globalenv()), abort = function() { structure(list(), class = "_renv_error") } ) if (inherits(status, "_renv_error")) { fmt <- "an error occurred while sourcing %s" warningf(fmt, renv_path_pretty(profile)) } FALSE } renv_load_libpaths <- function(project = NULL) { libpaths <- renv_init_libpaths(project) lapply(libpaths, renv_library_diagnose, project = project) Sys.setenv(R_LIBS_USER = paste(libpaths, collapse = .Platform$path.sep)) renv_libpaths_set(libpaths) } renv_load_sandbox <- function(project) { renv_sandbox_activate(project) } renv_load_python <- function(project, fields) { python <- tryCatch( renv_load_python_impl(project, fields), error = function(e) { warning(e) NULL } ) if (is.null(python)) return(FALSE) # set environment variables # - RENV_PYTHON is the version of Python renv was configured to use # - RETICULATE_PYTHON used to configure version of Python used by reticulate Sys.setenv( RENV_PYTHON = python, RETICULATE_PYTHON = python ) # place python + relevant utilities on the PATH bindir <- dirname(python) if (bindir %in% c("/usr/bin", "/usr/local/bin", "/opt/local/bin")) { # create a temporary directory to host symlinks toolspath <- tempfile("python-tools") ensure_directory(toolspath) # symlink common python binaries into that directory for (binary in c("python", "python3", "pip", "pip3")) { src <- file.path(bindir, binary) if (file.exists(src)) { tgt <- file.path(toolspath, binary) renv_file_link(src, tgt) } } # put it on the PATH renv_envvar_path_add("PATH", normalizePath(toolspath)) } else { bindir <- normalizePath(bindir, mustWork = FALSE) renv_envvar_path_add("PATH", bindir) } # on Windows, for conda environments, we may also have a Scripts directory # which will need to be pre-pended to the PATH if (renv_platform_windows()) { scriptsdir <- file.path(bindir, "Scripts") if (file.exists(scriptsdir)) renv_envvar_path_add("PATH", scriptsdir) } # for conda environments, we should try to find conda and place the conda # executable on the PATH, in case users want to use conda e.g. from # the terminal or even via R system calls # # we'll also need to set some environment variables to ensure that conda # uses this environment by default info <- renv_python_info(python) if (identical(info$type, "conda")) { conda <- renv_conda_find(python) if (file.exists(conda)) { renv_envvar_path_add("PATH", dirname(conda)) Sys.setenv(CONDA_PREFIX = info$root) } } TRUE } renv_load_python_impl <- function(project, fields) { # if RENV_PYTHON is already set, just use it python <- Sys.getenv("RENV_PYTHON", unset = NA) if (!is.na(python)) return(python) # set a default reticulate Python environment path envpath <- renv_paths_renv("python/r-reticulate", project = project) Sys.setenv(RETICULATE_MINICONDA_PYTHON_ENVPATH = envpath) # nothing more to do if no lockfile fields set if (is.null(fields)) return(NULL) # delegate based on type appropriately type <- fields$Type if (is.null(type)) return(NULL) python <- switch(type, system = renv_load_python_default(project, fields), virtualenv = renv_load_python_virtualenv(project, fields), conda = renv_load_python_condaenv(project, fields), stopf("unrecognized Python type '%s'", type) ) renv_path_canonicalize(python) } renv_load_python_default <- function(project, fields) { # if 'Name' points to a valid copy of Python, use it name <- fields$Name if (!is.null(name) && file.exists(name)) return(name) # otherwise, try to find a compatible version of Python renv_python_find(fields$Version) } renv_load_python_virtualenv <- function(project, fields) { renv_use_python_virtualenv_impl( project = project, name = fields[["Name"]] %NA% NULL, version = fields[["Version"]] %NA% NULL, python = fields[["Python"]] %NA% NULL ) } renv_load_python_condaenv <- function(project, fields) { renv_use_python_condaenv_impl( project = project, name = fields[["Name"]] %NA% NULL, version = fields[["Version"]] %NA% NULL, python = fields[["Python"]] %NA% NULL ) } renv_load_bioconductor <- function(project, bioconductor) { # we don't try to support older R anymore if (getRversion() < "3.4") return() # if we don't have a valid Bioconductor version, bail version <- bioconductor$Version if (is.null(version)) return() # initialize bioconductor renv_bioconductor_init() # validate version if necessary # avoid doing this in non-interactive sessions, as it can rely on # a web request to https://bioconductor.org/config.yaml, which can be slow validate <- getOption("renv.bioconductor.validate", default = interactive()) if (truthy(validate, default = FALSE)) renv_load_bioconductor_validate(project, version) # update the R repositories repos <- renv_bioconductor_repos(project, version) options(repos = repos) # notify the user sprintf("- Using Bioconductor '%s'.", version) } renv_load_bioconductor_validate <- function(project, version) { renv_bioconductor_validate(version, prompt = FALSE) } renv_load_switch <- function(project) { # skip when testing if (testing()) return(project) # safety check: avoid recursive unload attempts unloading <- getOption("renv.unload.project") if (!is.null(unloading)) { fmt <- "ignoring recursive attempt to load project '%s'" warningf(fmt, renv_path_pretty(project)) return(project) } # unset the RENV_PATHS_RENV environment variable # TODO: is there a path forward if different projects use # different RENV_PATHS_RENV paths? renvpath <- Sys.getenv("RENV_PATHS_RENV", unset = NA) Sys.unsetenv("RENV_PATHS_RENV") # validate that this project has an activate script script <- renv_paths_activate(project = project) if (!file.exists(script)) { fmt <- "project %s has no activate script and so cannot be activated" stopf(fmt, renv_path_pretty(project)) } # signal that we're unloading now # also ensure that the autoloader will be run when we source the active script # https://github.com/rstudio/renv/issues/1959 renv_scope_options( renv.unload.project = project, renv.config.autoloader.enabled = TRUE ) # perform the unload unload() # unload the current version of renv (but keep track of position # on search path in case we need to revert later) path <- renv_namespace_path("renv") pos <- match("package:renv", search()) unloadNamespace("renv") # move to new project directory owd <- setwd(project) on.exit(setwd(owd), add = TRUE) # source the activate script source(script) # check and see if renv was successfully loaded if (!"renv" %in% loadedNamespaces()) { fmt <- "could not load renv from project %s; reloading previously-loaded renv" warning(sprintf(fmt, project)) loadNamespace("renv", lib.loc = dirname(path)) Sys.setenv(RENV_PATHS_RENV = renvpath) if (!is.na(pos)) { args <- list(package = "renv", pos = pos, character.only = TRUE) do.call(base::library, args) } } } renv_load_cache_renvignore <- function(project) { if (testing() || checking()) return() if (!renv_cache_config_enabled(project)) return() caches <- renv_paths_cache() ensure_directory(caches) renv_renvignore_create( paths = caches, create = TRUE ) } renv_load_cache <- function(project) { renv_load_cache_renvignore(project) if (!interactive()) return(FALSE) oldcache <- renv_paths_cache(version = renv_cache_version_previous())[[1L]] newcache <- renv_paths_cache(version = renv_cache_version())[[1L]] if (!file.exists(oldcache) || file.exists(newcache)) return(FALSE) msg <- lines( "- The cache version has been updated in this version of renv.", "- Use `renv::rehash()` to migrate packages from the old renv cache." ) printf(msg) } renv_load_check <- function(project) { renv_load_check_description(project) } renv_load_check_description <- function(project) { descpath <- file.path(project, "DESCRIPTION") if (!file.exists(descpath)) return(TRUE) # read description file, with whitespace trimmed contents <- read(descpath) %>% trim() %>% chop() bad <- which(grepl("^\\s*$", contents, perl = TRUE)) if (empty(bad)) return(TRUE) values <- sprintf("[line %i is blank]", bad) bulletin( sprintf("%s contains blank lines:", renv_path_pretty(descpath)), values, c( "DESCRIPTION files cannot contain blank lines between fields.", "Please remove these blank lines from the file." ) ) return(FALSE) } renv_load_quiet <- function() { default <- identical(renv_verbose(), FALSE) || renv_session_quiet() config$startup.quiet(default = default) } renv_load_finish <- function(project = NULL, lockfile = NULL) { renv_project_set(project) renv_load_check(project) renv_load_report_project(project) renv_load_report_python(project) if (config$updates.check()) renv_load_report_updates(project) if (config$synchronized.check()) renv_load_report_synchronized(project, lockfile) renv_snapshot_auto_update(project = project) } renv_load_report_project <- function(project) { profile <- renv_profile_get() version <- renv_metadata_version_friendly(shafmt = "; sha: %s") if (!is.null(profile)) { fmt <- "- Project '%s' loaded. [renv %s; using profile '%s']" writef(fmt, renv_path_aliased(project), version, profile) } else { fmt <- "- Project '%s' loaded. [renv %s]" writef(fmt, renv_path_aliased(project), version) } } renv_load_report_python <- function(project) { # TODO } # nocov start renv_load_report_updates <- function(project) { lockpath <- renv_lockfile_path(project = project) if (!file.exists(lockpath)) return(FALSE) status <- update(project = project, check = TRUE) available <- inherits(status, "renv_updates") && length(status$diff) if (!available) return(FALSE) writef("- Use `renv::update()` to install updated packages.") if (!interactive()) print(status) TRUE } # nocov end renv_load_report_synchronized <- function(project = NULL, lockfile = NULL) { project <- renv_project_resolve(project) lockfile <- lockfile %||% renv_lockfile_load(project) # signal that we're running synchronization checks renv_scope_binding(the, "project_synchronized_check_running", TRUE) # be quiet when checking for dependencies in this scope # https://github.com/rstudio/renv/issues/1181 renv_scope_options(renv.config.dependency.errors = "ignored") # check for packages referenced in the lockfile which are not installed lockpkgs <- names(lockfile$Packages) libpkgs <- renv_snapshot_library( library = renv_libpaths_all(), project = project, records = FALSE ) # ignore renv lockpkgs <- setdiff(lockpkgs, "renv") libpkgs <- setdiff(libpkgs, "renv") # check for case where no packages are installed (except renv) if (length(intersect(lockpkgs, libpkgs)) == 0 && length(lockpkgs) > 0L) { caution("- None of the packages recorded in the lockfile are currently installed.") if (autoloading()) { caution("- Use `renv::restore()` to restore the project library.") return(FALSE) } response <- ask("Would you like to run `renv::restore()` to restore the project library?", default = FALSE) if (!response) return(FALSE) restore(project, prompt = FALSE, exclude = "renv") return(TRUE) } # check for case where one or more packages are missing missing <- setdiff(lockpkgs, basename(libpkgs)) if (length(missing)) { msg <- lines( "- One or more packages recorded in the lockfile are not installed.", "- Use `renv::status()` for more details." ) caution(msg) return(FALSE) } # otherwise, use status to detect if we're synchronized info <- local({ renv_scope_options(renv.verbose = FALSE) renv_scope_caution(FALSE) status(project = project, sources = FALSE) }) if (!identical(info$synchronized, TRUE)) { caution("- The project is out-of-sync -- use `renv::status()` for details.") return(FALSE) } TRUE } renv_load_base <- function(call, envir) { # if we were called without arguments, assume we should handle it if (length(call) == 0L) return(NULL) # if the call was namespace-qualified, assume we should handle it if (renv_call_matches(call[[1L]], names = c("::", ":::"))) return(NULL) # if any of the formals normally associated with base::load # were provided, then delegate to base::load() if (any(c("file", "envir") %in% names(call))) return(renv_load_base_impl(call, envir)) # attempt to match the call matched <- tryCatch(match.call(base::load, call), error = identity) if (inherits(matched, "error")) return(NULL) # check for a 'file' argument that looks like a file file <- eval(matched[["file"]], envir = envir) if (is.character(file) && endswith(file, ".RData")) return(renv_load_base_impl(call, envir)) } renv_load_base_impl <- function(call, envir) { call[[1L]] <- quote(base::load) eval(call, envir = envir) } renv/R/backports.R0000644000176200001440000000077114740260564013555 0ustar liggesusers if (is.null(.BaseNamespaceEnv$dir.exists)) { dir.exists <- function(paths) { info <- suppressWarnings(file.info(paths, extra_cols = FALSE)) info$isdir %in% TRUE } } if (is.null(.BaseNamespaceEnv$lengths)) { lengths <- function(x, use.names = TRUE) { vapply(x, length, numeric(1), USE.NAMES = use.names) } } if (is.null(.BaseNamespaceEnv$startsWith)) { startsWith <- function(x, prefix) { pattern <- sprintf("^\\Q%s\\E", prefix) grepl(pattern, x, perl = TRUE) } } renv/R/conda.R0000644000176200001440000000223514731330072012636 0ustar liggesusers # given the path to a Python installation managed by conda, attempt to # find the conda installation + executable used to create it renv_conda_find <- function(python) { tryCatch( renv_conda_find_impl(python), error = function(e) { warning(e) "" } ) } renv_conda_find_impl <- function(python) { # read the conda environment's history to try to find conda base <- dirname(python) if (!renv_platform_windows()) base <- dirname(base) history <- file.path(base, "conda-meta/history") if (!file.exists(history)) return("") contents <- readLines(history, n = 2L, warn = FALSE) if (length(contents) < 2) return("") line <- substring(contents[2L], 8L) index <- regexpr(" ", line, fixed = TRUE) if (index == -1L) return("") conda <- substring(line, 1L, index - 1L) if (renv_platform_windows()) conda <- file.path(dirname(conda), "conda.exe") # prefer condabin if it exists condabin <- file.path(dirname(conda), "../condabin", basename(conda)) if (file.exists(condabin)) conda <- condabin # bail if conda wasn't found if (!file.exists(conda)) return("") renv_path_canonicalize(conda) } renv/R/json.R0000644000176200001440000000013414731330073012520 0ustar liggesusers renv_json_quote <- function(text) { encodeString(text, quote = "\"", justify = "none") } renv/R/lockfile-api.R0000644000176200001440000000637414731330073014122 0ustar liggesusers # NOTE: These functions are used by the 'dockerfiler' package, even though # they are not exported. We retain these functions here just to avoid issues # during CRAN submission. We'll consider removing them in a future release. renv_lockfile_api <- function(lockfile = NULL) { .lockfile <- lockfile .self <- new.env(parent = emptyenv()) .self$repos <- function(..., .repos = NULL) { if (nargs() == 0) { repos <- .lockfile$R$Repositories return(repos) } repos <- .repos %||% list(...) if (is.null(names(repos)) || "" %in% names(repos)) stop("repositories must all be named", call. = FALSE) .lockfile$R$Repositories <<- as.list(convert(repos, "character")) invisible(.self) } .self$version <- function(..., .version = NULL) { if (nargs() == 0) { version <- .lockfile$R$Version return(version) } version <- .version %||% c(...) if (length(version) > 1) { stop("Version should be length 1 character e.g. `\"3.6.3\"`") } .lockfile$R$Version <<- version invisible(.self) } .self$add <- function(..., .list = NULL) { records <- renv_lockfile_records(.lockfile) dots <- .list %||% list(...) enumerate(dots, function(package, remote) { resolved <- renv_remotes_resolve(remote) records[[package]] <<- resolved }) renv_lockfile_records(.lockfile) <<- records invisible(.self) } .self$remove <- function(packages) { records <- renv_lockfile_records(.lockfile) %>% omit(packages) renv_lockfile_records(.lockfile) <<- records invisible(.self) } .self$write <- function(file = stdout()) { renv_lockfile_write(.lockfile, file = file) invisible(.self) } .self$data <- function() { .lockfile } class(.self) <- "renv_lockfile_api" .self } #' Programmatically Create and Modify a Lockfile #' #' This function provides an API for creating and modifying `renv` lockfiles. #' This can be useful when you'd like to programmatically generate or modify #' a lockfile -- for example, because you want to update or change a package #' record in an existing lockfile. #' #' @inheritParams renv-params #' #' @param file The path to an existing lockfile. When no lockfile is provided, #' a new one will be created based on the current project context. If you #' want to create a blank lockfile, use `file = NA` instead. #' #' @seealso \code{\link{lockfiles}}, for a description of the structure of an #' `renv` lockfile. #' #' @examples #' #' \dontrun{ #' #' lock <- lockfile("renv.lock") #' #' # set the repositories for a lockfile #' lock$repos(CRAN = "https://cran.r-project.org") #' #' # depend on digest 0.6.22 #' lock$add(digest = "digest@@0.6.22") #' #' # write to file #' lock$write("renv.lock") #' #' } #' #' @keywords internal #' @rdname lockfile-api #' @name lockfile-api #' lockfile <- function(file = NULL, project = NULL) { project <- renv_project_resolve(project) renv_scope_error_handler() lock <- if (is.null(file)) { renv_lockfile_create( project = project, libpaths = renv_libpaths_all(), type = settings$snapshot.type(project = project) ) } else if (is.na(file)) { renv_lockfile_init(project) } else { renv_lockfile_read(file = file) } renv_lockfile_api(lock) } renv/R/testthat-helpers.R0000644000176200001440000000067614731330073015062 0ustar liggesusers expect_same_elements <- function(lhs, rhs) { if (!requireNamespace("testthat", quietly = TRUE)) stop("testthat not available for testing") if (is.list(lhs) && is.list(rhs)) { lhs <- lhs[order(names(lhs))] rhs <- rhs[order(names(rhs))] return(testthat::expect_equal(!!lhs, !!rhs)) } if (packageVersion("testthat") > "2.2.0") testthat::expect_setequal(!!lhs, !!rhs) else testthat::expect_setequal(lhs, rhs) } renv/R/utils-map.R0000644000176200001440000000357014731330073013471 0ustar liggesusers bapply <- function(x, f, ..., index = "Index") { result <- lapply(x, f, ...) bind(result, index = index) } enumerate <- function(x, f, ..., FUN.VALUE = NULL) { n <- names(x) idx <- `names<-`(seq_along(x), n) callback <- function(i) f(n[[i]], x[[i]], ...) if (is.environment(x)) x <- as.list(x, all.names = TRUE) if (is.null(FUN.VALUE)) lapply(idx, callback) else vapply(idx, callback, FUN.VALUE = FUN.VALUE) } enum_chr <- function(x, f, ...) { enumerate(x, f, ..., FUN.VALUE = character(1)) } enum_int <- function(x, f, ...) { enumerate(x, f, ..., FUN.VALUE = integer(1)) } enum_dbl <- function(x, f, ...) { enumerate(x, f, ..., FUN.VALUE = double(1)) } enum_lgl <- function(x, f, ...) { enumerate(x, f, ..., FUN.VALUE = logical(1)) } uapply <- function(x, f, ...) { f <- match.fun(f) unlist(lapply(x, f, ...), recursive = FALSE) } filter <- function(x, f, ...) { f <- match.fun(f) x[map_lgl(x, f, ...)] } reject <- function(x, f, ...) { f <- match.fun(f) x[!map_lgl(x, f, ...)] } map <- function(x, f, ...) { f <- match.fun(f) lapply(x, f, ...) } map_chr <- function(x, f, ...) { f <- match.fun(f) vapply(x, f, ..., FUN.VALUE = character(1)) } map_dbl <- function(x, f, ...) { f <- match.fun(f) vapply(x, f, ..., FUN.VALUE = numeric(1)) } map_int <- function(x, f, ...) { f <- match.fun(f) vapply(x, f, ..., FUN.VALUE = integer(1)) } map_lgl <- function(x, f, ...) { f <- match.fun(f) vapply(x, f, ..., FUN.VALUE = logical(1)) } extract <- function(x, ...) { lapply(x, `[[`, ...) } extract_chr <- function(x, ...) { vapply(x, `[[`, ..., FUN.VALUE = character(1)) } extract_dbl <- function(x, ...) { vapply(x, `[[`, ..., FUN.VALUE = numeric(1)) } extract_int <- function(x, ...) { vapply(x, `[[`, ..., FUN.VALUE = integer(1)) } extract_lgl <- function(x, ...) { vapply(x, `[[`, ..., FUN.VALUE = logical(1)) } renv/R/pak.R0000644000176200001440000001322714755173025012341 0ustar liggesusers # the minimum-required version of 'pak' for renv integration the$pak_minver <- numeric_version("0.7.0") renv_pak_init <- function(stream = NULL, force = FALSE) { if (force || !renv_pak_available()) { stream <- stream %||% renv_pak_stream() renv_pak_init_impl(stream) } renv_namespace_load("pak") } renv_pak_stream <- function() { # check if stable is new enough streams <- c("stable", "rc", "devel") for (stream in streams) { repos <- renv_pak_repos(stream) latest <- renv_available_packages_latest("pak", repos = repos) version <- numeric_version(latest$Version) if (version >= the$pak_minver) return(stream) } fmt <- "internal error: pak (>= %s) is not available" stopf(fmt, format(the$pak_minver)) } renv_pak_available <- function() { tryCatch( packageVersion("pak") >= the$pak_minver, error = function(e) FALSE ) } renv_pak_repos <- function(stream) { # on macOS, we can only use pak binaries with CRAN R if (renv_platform_macos() && .Platform$pkgType == "source") return(getOption("repos")) # otherwise, use pre-built pak binaries fmt <- "https://r-lib.github.io/p/pak/%s/%s/%s/%s" sprintf(fmt, stream, .Platform$pkgType, version$os, version$arch) } renv_pak_init_impl <- function(stream) { renv_scope_options( renv.config.pak.enabled = FALSE, renv.config.ppm.enabled = FALSE, repos = c("r-lib" = renv_pak_repos(stream)) ) library <- renv_libpaths_active() install("pak", library = library) loadNamespace("pak", lib.loc = library) } renv_pak_update <- function(project, library, prompt) { pak <- renv_namespace_load("pak") # if this project contains a DESCRIPTION file, use it when # determining which packages to update if (file.exists(file.path(project, "DESCRIPTION"))) { result <- pak$local_install_dev_deps( root = project, lib = library[[1L]], ask = prompt ) return(result) } # read description files for all installed packages # TODO: do we want to also update packages in other library paths, # or just packages installed in the project library? records <- renv_snapshot_libpaths(library[[1L]], project = project) remotes <- map_chr(records, renv_record_format_remote, versioned = FALSE, pak = TRUE) if (length(remotes) == 0L) { caution("- There are no packages to update.") return(invisible(NULL)) } # update those packages pak$pkg_install( pkg = unname(remotes), lib = library[[1L]], upgrade = TRUE, ask = prompt ) } renv_pak_install <- function(packages, library, type, rebuild, prompt, project) { pak <- renv_namespace_load("pak") # transform repositories if (renv_ppm_enabled()) { repos <- getOption("repos") renv_scope_options(repos = renv_ppm_transform(repos)) } # make sure pak::pkg_install() still works even if we're # running in renv with devtools::load_all() name <- Sys.getenv("_R_CHECK_PACKAGE_NAME_", unset = NA) if (identical(name, "renv")) renv_scope_envvars("_R_CHECK_PACKAGE_NAME_" = NULL) # if we received a named list of remotes, use the names packages <- if (any(nzchar(names(packages)))) names(packages) else as.character(packages) # if no packages were specified, treat this as a request to # install / update packages used in the project if (length(packages) == 0L) return(renv_pak_update(project, library, prompt)) # pak doesn't support ':' as a sub-directory separator, so try to # repair that here # https://github.com/rstudio/renv/issues/2011 pattern <- "(?", remote) return(remote) } record$Version } renv_record_format_pair <- function(lhs, rhs, separator = "->") { placeholder <- renv_record_placeholder() # check for install / remove if (is.null(lhs) || is.null(rhs)) { lhs <- renv_record_format_short(lhs) rhs <- renv_record_format_short(rhs) return(sprintf("[%s %s %s]", lhs, separator, rhs)) } map <- list( Source = "src", Repository = "repo", Version = "ver", RemoteHost = "host", RemoteUsername = "user", RemoteRepo = "repo", RemoteRef = "ref", RemoteSha = "sha", RemoteSubdir = "subdir" ) fields <- names(map) # check to see which fields have changed between the two diff <- map_lgl(fields, function(field) { !identical(lhs[[field]], rhs[[field]]) }) changed <- names(which(diff)) if (empty(changed)) { fmt <- "[%s: unchanged]" lhsf <- renv_record_format_short(lhs) return(sprintf(fmt, lhsf)) } # check for CRAN packages; in such cases, we typically want to ignore # the Remote fields which might've been added by 'pak' or other tools isrepo <- nzchar(lhs$Version %||% "") && nzchar(rhs$Version %||% "") && nzchar(lhs$Repository %||% "") && nzchar(rhs$Repository %||% "") && identical(lhs$Repository, rhs$Repository) if (isrepo) { fmt <- "[%s %s %s]" lhsf <- renv_record_format_short(lhs) rhsf <- renv_record_format_short(rhs) return(sprintf(fmt, lhsf, separator, rhsf)) } # check for only sha changed usesha <- setequal(changed, "RemoteSha") || setequal(changed, c("RemoteSha", "Version")) if (usesha) { user <- lhs$RemoteUsername %||% placeholder repo <- lhs$RemoteRepo %||% placeholder spec <- paste(user, repo, sep = "/") ref <- lhs$RemoteRef %||% placeholder if (!ref %in% c("main", "master", "*")) spec <- paste(spec, ref, sep = "@") fmt <- "[%s: %s %s %s]" lsha <- substring(lhs$RemoteSha %||% placeholder, 1L, 8L) rsha <- substring(rhs$RemoteSha %||% placeholder, 1L, 8L) return(sprintf(fmt, spec, lsha, separator, rsha)) } # check for only source change if (setequal(changed, "Source")) { fmt <- "[%s: %s %s %s]" ver <- lhs$Version %||% placeholder lhsf <- lhs$Source %||% placeholder rhsf <- rhs$Source %||% placeholder return(sprintf(fmt, ver, lhsf, separator, rhsf)) } # check only version changed if (setequal(changed, "Version")) { fmt <- "[%s %s %s]" lhsf <- lhs$Version %||% placeholder rhsf <- rhs$Version %||% placeholder return(sprintf(fmt, lhsf, separator, rhsf)) } # if the source has changed, highlight that if ("Source" %in% changed) { fmt <- "[%s %s %s]" lhsf <- renv_record_format_short(lhs) rhsf <- renv_record_format_short(rhs) return(sprintf(fmt, lhsf, separator, rhsf)) } # otherwise, report each diff individually diffs <- map_chr(changed, function(field) { lhsf <- lhs[[field]] %||% placeholder rhsf <- rhs[[field]] %||% placeholder if (field == "RemoteSha") { lhsf <- substring(lhsf, 1L, 8L) rhsf <- substring(rhsf, 1L, 8L) } fmt <- "%s: %s %s %s" sprintf(fmt, map[[field]], lhsf, separator, rhsf) }) sprintf("[%s]", paste(diffs, collapse = "; ")) } renv_records_equal <- function(lhs, rhs) { lhs <- reject(lhs, is.null) rhs <- reject(rhs, is.null) nm <- setdiff(union(names(lhs), names(rhs)), "Hash") identical(keep(lhs, nm), keep(rhs, nm)) } renv_records_resolve <- function(records, latest = FALSE) { enumerate(records, function(package, record) { # check for already-resolved records if (is.null(record) || is.list(record)) return(record) # check for version-only specifications and # prepend the package name in such a case pattern <- "^(?:[[:digit:]]+[.-]){1,}[[:digit:]]+$" if (grepl(pattern, record)) record <- paste(package, record, sep = "@") # resolve the record renv_remotes_resolve(record, latest) }) } renv/R/dependencies.R0000644000176200001440000014672214761163114014216 0ustar liggesusers #' Find R package dependencies in a project #' #' @description #' `dependencies()` will scan files within your project, looking for \R files #' and the packages used within those \R files. This is done primarily by #' parsing the code and looking for calls of the form `library(package)`, #' `require(package)`, `requireNamespace("package")`, and `package::method()`. #' renv also supports package loading with #' [box](https://cran.r-project.org/package=box) (`box::use(...)`) and #' [pacman](https://cran.r-project.org/package=pacman) (`pacman::p_load(...)`). #' #' For \R package projects, `renv` will also detect dependencies expressed #' in the `DESCRIPTION` file. For projects using Python, \R dependencies within #' the \R code chunks of your project's `.ipynb` files will also be used. #' #' Note that the \code{\link[rmarkdown:rmarkdown-package]{rmarkdown}} package is #' required in order to scan dependencies in R Markdown files. #' #' # Missing dependencies #' #' `dependencies()` uses static analysis to determine which packages are used #' by your project. This means that it inspects, but doesn't run, the \R code #' in your project. Static analysis generally works well, but is not #' 100% reliable in detecting the packages required by your project. For #' example, `renv` is unable to detect this kind of usage: #' #' ```{r eval=FALSE} #' for (package in c("dplyr", "ggplot2")) { #' library(package, character.only = TRUE) #' } #' ``` #' #' It also can't generally tell if one of the packages you use, uses one of #' its suggested packages. For example, the `tidyr::separate_wider_delim()` #' function requires the `stringr` package, but `stringr` is only suggested, #' not required, by `tidyr`. #' #' If you find that renv's dependency discovery misses one or more packages #' that you actually use in your project, one escape hatch is to include a file #' called `_dependencies.R` that includes straightforward library calls: #' #' ``` #' library(dplyr) #' library(ggplot2) #' library(stringr) #' ``` #' #' # Ignoring files #' #' By default, renv will read your project's `.gitignore`s (if present) to #' determine whether certain files or folders should be included when traversing #' directories. If preferred, you can also create a `.renvignore` file (with #' entries of the same format as a standard `.gitignore` file) to tell renv #' which files to ignore within a directory. If both `.renvignore` and #' `.gitignore` exist within a folder, the `.renvignore` will be used in lieu of #' the `.gitignore`. #' #' See for documentation on the #' `.gitignore` format. Some simple examples here: #' #' ``` #' # ignore all R Markdown files #' *.Rmd #' #' # ignore all data folders #' data/ #' #' # ignore only data folders from the root of the project #' /data/ #' ``` #' #' Using ignore files is important if your project contains a large number #' of files; for example, if you have a `data/` directory containing many #' text files. #' # Errors #' #' renv's attempts to enumerate package dependencies in your project can fail #' -- most commonly, because of failures when attempting to parse your \R code. #' You can use the `errors` argument to suppress these problems, but a #' more robust solution is tell renv not to look at the problematic code. #' As well as using `.renvignore`, as described above, you can also suppress errors #' discovered within individual `.Rmd` chunks by including `renv.ignore=TRUE` #' in the chunk header. For example: #' #' ```{r chunk-label, renv.ignore=TRUE} #' # code in this chunk will be ignored by renv #' ``` #' #' Similarly, if you'd like renv to parse a chunk that is otherwise ignored #' (e.g. because it has `eval=FALSE` as a chunk header), you can set: #' #' ```{r chunk-label, eval=FALSE, renv.ignore=FALSE} #' # code in this chunk will _not_ be ignored #' ``` #' #' # Development dependencies #' #' renv has some support for distinguishing between development and run-time #' dependencies. For example, your Shiny app might rely on #' [ggplot2](https://ggplot2.tidyverse.org) (a run-time dependency) but while #' you use [usethis](https://usethis.r-lib.org) during development, your app #' doesn't need it to run (i.e. it's only a development dependency). #' #' You can record development dependencies by listing them in the `Suggests` #' field of your project's `DESCRIPTION` file. Development dependencies will be installed by #' [renv::install()] (when called without arguments) but will not be tracked in #' the project snapshot. If you need greater control, you can also try project #' profiles as discussed in `vignette("profiles")`. #' #' @inheritParams renv-params #' #' @param path The path to a `.R`, `.Rmd`, `.qmd`, `DESCRIPTION`, a directory #' containing such files, or an R function. The default uses all files #' found within the current working directory and its children. #' #' @param root The root directory to be used for dependency discovery. #' Defaults to the active project directory. You may need to set this #' explicitly to ensure that your project's `.renvignore`s (if any) are #' properly handled. #' #' @param quiet Boolean; be quiet while checking for dependencies? #' Setting `quiet = TRUE` is equivalent to setting `progress = FALSE` #' and `errors = "ignored"`, and overrides those options when not `NULL`. #' #' @param progress Boolean; report progress output while enumerating #' dependencies? #' #' @param errors How should errors that occur during dependency enumeration be #' handled? #' #' * `"reported"` (the default): errors are reported to the user, but #' otherwise ignored. #' * `"fatal"`: errors are fatal and stop execution. #' * `"ignored"`: errors are ignored and not reported to the user. #' #' @param dev Boolean; include development dependencies? These packages are #' typically required when developing the project, but not when running it #' (i.e. you want them installed when humans are working on the project but #' not when computers are deploying it). #' #' Development dependencies include packages listed in the `Suggests` field #' of a `DESCRIPTION` found in the project root, and roxygen2 or devtools if #' their use is implied by other project metadata. They also include packages #' used in `~/.Rprofile` if `config$user.profile()` is `TRUE`. #' #' @return An \R `data.frame` of discovered dependencies, mapping inferred #' package names to the files in which they were discovered. Note that the #' `Package` field might name a package remote, rather than just a plain #' package name. #' #' @export #' #' @examples #' \dontrun{ #' #' # find R package dependencies in the current directory #' renv::dependencies() #' #' } dependencies <- function( path = getwd(), root = NULL, ..., quiet = NULL, progress = TRUE, errors = c("reported", "fatal", "ignored"), dev = FALSE) { renv_scope_error_handler() # special case: if 'path' is a function, parse its body for dependencies if (is.function(path)) return(renv_dependencies_discover_r(expr = body(path))) renv_dependencies_impl( path = path, root = root, quiet = quiet, progress = progress, errors = errors, dev = dev, ... ) } renv_dependencies_impl <- function( path = getwd(), ..., root = NULL, field = NULL, quiet = NULL, progress = FALSE, errors = c("reported", "fatal", "ignored"), dev = FALSE) { renv_dots_check(...) path <- renv_path_normalize(path, mustWork = TRUE) root <- root %||% renv_dependencies_root(path) # handle 'quiet' parameter if (quiet %||% FALSE) { progress <- FALSE errors <- "ignored" } # ignore errors when testing, unless explicitly asked for if (renv_tests_running() && missing(errors)) errors <- "ignored" # resolve errors errors <- match.arg(errors) # the path to the user .Rprofile is used when discovering dependencies, # so resolve that eagerly now renv_scope_binding( envir = the$paths, symbol = "r_profile_user", replacement = Sys.getenv("R_PROFILE_USER", unset = path.expand("~/.Rprofile")) ) before <- Sys.time() renv_dependencies_scope(root = root) files <- renv_dependencies_find(path, root) deps <- renv_dependencies_discover(files, progress, errors) after <- Sys.time() elapsed <- difftime(after, before, units = "secs") renv_condition_signal("renv.dependencies.elapsed_time", elapsed) renv_dependencies_report(errors) deps <- if (empty(deps) || nrow(deps) == 0L) { renv_dependencies_list_empty() } else { # drop NAs, and only keep 'dev' dependencies if requested rows(deps, deps$Dev %in% c(dev, FALSE)) } take(deps, field) } renv_dependencies_root <- function(path = getwd()) { path <- renv_path_normalize(path, mustWork = TRUE) project <- renv_project_get(default = NULL) if (!is.null(project) && all(renv_path_within(path, project))) return(project) roots <- uapply(path, renv_dependencies_root_impl) if (empty(roots)) return(NULL) uniroot <- unique(roots) if (length(uniroot) > 1) return(NULL) uniroot } renv_dependencies_root_impl <- function(path) { renv_file_find(path, function(parent) { anchors <- c("DESCRIPTION", ".git", ".Rproj.user", "renv.lock", "renv") for (anchor in anchors) if (file.exists(file.path(parent, anchor))) return(parent) }) } renv_dependencies_callback <- function(path) { cbname <- list( ".lintr" = function(path) renv_dependencies_discover_lintr(path), ".Rprofile" = function(path) renv_dependencies_discover_r(path), "DESCRIPTION" = function(path) renv_dependencies_discover_description(path), "NAMESPACE" = function(path) renv_dependencies_discover_namespace(path), "_bookdown.yml" = function(path) renv_dependencies_discover_bookdown(path), "_pkgdown.yml" = function(path) renv_dependencies_discover_pkgdown(path), "_quarto.yml" = function(path) renv_dependencies_discover_quarto(path), "renv.lock" = function(path) renv_dependencies_discover_renv_lock(path), "rsconnect" = function(path) renv_dependencies_discover_rsconnect(path) ) cbext <- list( ".rproj" = function(path) renv_dependencies_discover_rproj(path), ".r" = function(path) renv_dependencies_discover_r(path), ".qmd" = function(path) renv_dependencies_discover_multimode(path, "qmd"), ".rmd" = function(path) renv_dependencies_discover_multimode(path, "rmd"), ".rmarkdown" = function(path) renv_dependencies_discover_multimode(path, "rmd"), ".rnw" = function(path) renv_dependencies_discover_multimode(path, "rnw"), ".ipynb" = function(path) renv_dependencies_discover_ipynb(path) ) name <- basename(path) ext <- tolower(fileext(path)) callback <- cbname[[name]] %||% cbext[[ext]] if (!is.null(callback)) return(callback) # for files without an extension, check if those might be executable by R if (!nzchar(ext)) { shebang <- renv_file_shebang(path) if (grepl("\\b(?:R|r|Rscript)\\b", shebang)) return(function(path) renv_dependencies_discover_r(path)) } } renv_dependencies_find_extra <- function(root) { # if we don't have a root, we don't have a project if (is.null(root)) return(NULL) # only run for root-level dependency checks project <- renv_project_resolve() if (!renv_path_same(root, project)) return(NULL) # only run if we have a custom profile profile <- renv_profile_get() if (is.null(profile)) return(NULL) # look for dependencies in the associated 'renv' folder path <- renv_paths_renv(project = project) renv_dependencies_find_impl(path, root, 0L) } renv_dependencies_find <- function(path = getwd(), root = getwd()) { files <- lapply(path, renv_dependencies_find_impl, root = root, depth = 0) extra <- renv_dependencies_find_extra(root) if (config$user.profile()) { profile <- the$paths$r_profile_user if (file.exists(profile)) { extra <- c(extra, profile) } } unlist(c(files, extra), recursive = TRUE, use.names = FALSE) } renv_dependencies_find_impl <- function(path, root, depth) { # check file type info <- renv_file_info(path) # the file might have been removed after listing -- if so, just ignore it if (is.na(info$isdir)) return(NULL) # if this is a directory, recurse if (info$isdir) return(renv_dependencies_find_dir(path, root, depth)) path } renv_dependencies_find_dir <- function(path, root, depth) { # check if this path should be ignored excluded <- renv_renvignore_exec(path, root, path) if (excluded) return(character()) # check if we've already scanned this directory # (necessary to guard against recursive symlinks) if (!renv_platform_windows()) { norm <- renv_path_normalize(path) state <- renv_dependencies_state() if (visited(norm, state$scanned)) return(character()) } # list children children <- renv_dependencies_find_dir_children(path, root, depth) # notify about number of children renv_condition_signal("renv.dependencies.count", list(path = path, count = length(children))) # find recursive dependencies depth <- depth + 1 paths <- map(children, renv_dependencies_find_impl, root = root, depth = depth) # explicitly include rsconnect folder # (so we can infer a dependency on rsconnect when appropriate) rsconnect <- file.path(path, "rsconnect") if (file.exists(rsconnect)) paths <- c(rsconnect, paths) paths } # return the set of files / subdirectories within a directory that should be # crawled for dependencies renv_dependencies_find_dir_children <- function(path, root, depth) { # list files in the folder children <- renv_file_list(path, full.names = TRUE) # skip if this contains too many files # https://github.com/rstudio/renv/issues/1186 count <- length(children) if (count >= config$dependencies.limit()) { relpath <- renv_path_relative(path, dirname(root)) renv_dependencies_find_dir_children_overload(relpath, count) } # remove files which are broken symlinks children <- children[file.exists(children)] # remove hard-coded ignores # (only keep DESCRIPTION files at the top level) ignored <- c("packrat", "renv", "revdep", "vendor", if (depth) c("DESCRIPTION", "NAMESPACE")) children <- children[!basename(children) %in% ignored] # compute exclusions excluded <- renv_renvignore_exec(path, root, children) # keep only non-excluded children children[!excluded] } renv_dependencies_find_dir_children_overload <- function(path, count) { # check for missing state (e.g. if internal method called directly) state <- renv_dependencies_state() if (is.null(state)) return() fmt <- "directory contains %s; consider ignoring this directory" msg <- sprintf(fmt, nplural("file", count)) error <- simpleError(message = msg) path <- path %||% state$path problem <- list(file = path, error = error) state$problems$push(problem) } renv_dependencies_discover <- function(paths, progress, errors) { if (!renv_dependencies_discover_preflight(paths, errors)) return(invisible(list())) # short path if we're not showing progress if (identical(progress, FALSE)) return(bapply(paths, renv_dependencies_discover_impl)) # otherwise, run with progress reporting # nocov start printf("Finding R package dependencies ... ") callback <- renv_progress_callback(renv_dependencies_discover_impl, length(paths)) deps <- lapply(paths, callback) writef("Done!") bind(deps) # nocov end } renv_dependencies_discover_impl <- function(path) { callback <- renv_dependencies_callback(path) if (is.null(callback)) { return(NULL) } status <- catch(filebacked("dependencies", path, callback)) if (inherits(status, "error")) { signalCondition(warnify(status)) NULL } status } renv_dependencies_discover_preflight <- function(paths, errors) { if (identical(errors, "ignored")) return(TRUE) if (length(paths) < config$dependencies.limit()) return(TRUE) lines <- c( "A large number of files (%i in total) have been discovered.", "It may take renv a long time to scan these files for dependencies.", "Consider using .renvignore to ignore irrelevant files.", "See `?renv::dependencies` for more information.", "Set `options(renv.config.dependencies.limit = Inf)` to disable this warning.", "" ) writef(lines, length(paths)) if (identical(errors, "reported")) return(TRUE) cancel_if(interactive() && !proceed()) TRUE } renv_dependencies_discover_renv_lock <- function(path) { renv_dependencies_list(path, "renv") } renv_dependencies_discover_description_fields <- function(path, project = NULL) { # most callers don't pass in project so we need to get it from global state project <- project %||% renv_dependencies_state(key = "root") %||% renv_restore_state(key = "root") %||% renv_project_resolve() # by default, respect fields defined in settings fields <- settings$package.dependency.fields(project = project) # if this appears to be the DESCRIPTION associated with the active project, # and an explicit set of dependencies was provided in install, then use those if (renv_path_same(file.path(project, "DESCRIPTION"), path)) { default <- the$install_dependency_fields %||% c(fields, "Suggests") profile <- sprintf("Config/renv/profiles/%s/dependencies", renv_profile_get()) fields <- c(default, profile) } fields } renv_dependencies_discover_description <- function(path, fields = NULL, subdir = NULL, project = NULL) { dcf <- catch(renv_description_read(path = path, subdir = subdir)) if (inherits(dcf, "error")) return(renv_dependencies_error(path, error = dcf)) # resolve the dependency fields to be used fields <- fields %||% renv_dependencies_discover_description_fields(path, project) # make sure dependency fields are expanded fields <- renv_description_dependency_fields_expand(fields) data <- map( fields, renv_dependencies_discover_description_impl, dcf = dcf, path = path ) # if this is a bioconductor package, add their implicit dependencies if ("biocViews" %in% names(dcf)) { data[[length(data) + 1L]] <- renv_dependencies_list( source = path, packages = c(renv_bioconductor_manager(), "BiocVersion") ) } bind(data) } renv_dependencies_discover_namespace <- function(path) { tryCatch( renv_dependencies_discover_namespace_impl(path), error = warnify ) } renv_dependencies_discover_namespace_impl <- function(path) { # parseNamespaceFile() expects to be called on an installed package, # so we have to pretend our best here library <- dirname(dirname(path)) package <- basename(dirname(path)) info <- parseNamespaceFile( package = package, package.lib = library, mustExist = TRUE ) # read package names from imports packages <- map_chr(info$imports, `[[`, 1L) renv_dependencies_list( source = path, packages = sort(unique(packages)) ) } renv_dependencies_discover_description_impl <- function(dcf, field, path) { # read field contents <- dcf[[field]] if (!is.character(contents)) return(list()) # split on commas parts <- strsplit(dcf[[field]], "\\s*,\\s*")[[1]] # drop any empty fields x <- parts[nzchar(parts)] # match to split on remote, version pattern <- paste0( "([^,\\([:space:]]+)", # remote name "(?:\\s*\\(([><=]+)\\s*([0-9.-]+)\\))?" # optional version specification ) m <- regexec(pattern, x) matches <- regmatches(x, m) if (empty(matches)) return(list()) # drop R (https://github.com/rstudio/renv/issues/1806) matches <- filter(matches, function(match) { !identical(match[[2L]], "R") }) if (empty(matches)) return(list()) # create dependency list renv_dependencies_list( path, extract_chr(matches, 2L), extract_chr(matches, 3L), extract_chr(matches, 4L), dev = field == "Suggests" ) } renv_dependencies_discover_bookdown <- function(path) { # TODO: other dependencies to parse from bookdown? renv_dependencies_list(path, "bookdown") } renv_dependencies_discover_pkgdown <- function(path) { # TODO: other dependencies to parse from pkgdown? renv_dependencies_list(path, "pkgdown") } renv_dependencies_discover_quarto <- function(path) { # TODO: other dependencies to parse from quarto? # # NOTE: we previously inferred a dependency on the R 'quarto' package here, # but quarto is normally invoked directly (rather than via the package) and # so such a dependency is not strictly necessary. # # https://github.com/rstudio/renv/issues/995 renv_dependencies_list_empty() } renv_dependencies_discover_rsconnect <- function(path) { renv_dependencies_list(path, "rsconnect") } renv_dependencies_discover_multimode <- function(path, mode) { # TODO: find in-line R code? deps <- stack() if (mode %in% c("rmd", "qmd")) deps$push(renv_dependencies_discover_rmd_yaml_header(path, mode)) deps$push(renv_dependencies_discover_chunks(path, mode)) bind(Filter(NROW, deps$data())) } renv_dependencies_discover_rmd_yaml_header <- function(path, mode) { deps <- stack(mode = "character") # R Markdown documents always depend on rmarkdown if (identical(mode, "rmd")) deps$push("rmarkdown") # try and read the document's YAML header contents <- renv_file_read(path) pattern <- "(?:^|\n)\\s*---\\s*(?:$|\n)" matches <- gregexpr(pattern, contents, perl = TRUE)[[1L]] # check that we have something that looks like a YAML header ok <- length(matches) > 1L && matches[[1L]] == 1L if (!ok) return(renv_dependencies_list(path, packages = deps$data())) # require yaml package for parsing YAML header name <- case( mode == "rmd" ~ "R Markdown", mode == "qmd" ~ "Quarto Markdown" ) # validate that we actually have the yaml package available if (!renv_dependencies_require("yaml", name)) { packages <- deps$data() return(renv_dependencies_list(path, packages)) } # extract YAML text yamltext <- substring(contents, matches[[1L]] + 4L, matches[[2L]] - 1L) yaml <- catch(renv_yaml_load(yamltext)) if (inherits(yaml, "error")) return(renv_dependencies_error(path, error = yaml, packages = "rmarkdown")) # check for Shiny runtime runtime <- yaml[["runtime"]] %||% "" if (pstring(runtime) && grepl("shiny", runtime, fixed = TRUE)) deps$push("shiny") server <- yaml[["server"]] %||% "" if (identical(server, "shiny")) deps$push("shiny") if (is.list(server) && identical(server[["type"]], "shiny")) deps$push("shiny") pattern <- renv_regexps_package_name() # check recursively for package usages of the form 'package::method' recurse(yaml, function(node) { # look for keys of the form 'package::method' values <- c(names(node), if (pstring(node)) node) for (value in values) { call <- tryCatch(parse(text = value)[[1]], error = function(err) NULL) if (renv_call_matches(call, names = c("::", ":::"), nargs = 2L)) { deps$push(as.character(call[[2L]])) } } }) # check for dependency on bslib theme <- catchall(yaml[[c("output", "html_document", "theme")]]) if (!inherits(theme, "error") && is.list(theme)) deps$push("bslib") # check for parameterized documents status <- catch(renv_dependencies_discover_rmd_yaml_header_params(yaml, deps)) if (inherits(status, "error")) renv_dependencies_error_push(path, status) # get list of dependencies packages <- deps$data() renv_dependencies_list(path, packages) } renv_dependencies_discover_rmd_yaml_header_params <- function(yaml, deps) { # check for declared params params <- yaml[["params"]] if (!is.list(params)) return() # infer dependency on shiny deps$push("shiny") # iterate through params, parsing dependencies from R code for (param in params) { # check for r types type <- attr(param, "type", exact = TRUE) if (!identical(type, "r")) next # attempt to parse dependencies rdeps <- catch(renv_dependencies_discover_r(text = param)) if (inherits(rdeps, "error")) next # add each dependency for (package in sort(unique(rdeps$Package))) deps$push(package) } } renv_dependencies_discover_chunks_ignore <- function(chunk) { # if renv.ignore is set, respect it ignore <- chunk$params[["renv.ignore"]] if (!is.null(ignore)) return(truthy(ignore)) # skip non-R chunks engine <- chunk$params[["engine"]] ok <- is.character(engine) && tolower(engine) %in% c("r", "rscript") if (!ok) return(TRUE) # skip un-evaluated chunks if (!truthy(chunk$params[["eval"]], default = TRUE)) return(TRUE) # skip learnr exercises if (truthy(chunk$params[["exercise"]], default = FALSE)) return(TRUE) # skip chunks whose labels end in '-display' label <- chunk$params[["label"]] %||% "" if (grepl("-display$", label)) return(TRUE) # ok, don't ignore this chunk FALSE } renv_dependencies_discover_chunks <- function(path, mode) { # figure out the appropriate begin, end patterns type <- tolower(file_ext(path)) if (type %in% c("rmd", "qmd", "rmarkdown")) type <- "md" allpatterns <- renv_knitr_patterns() patterns <- allpatterns[[type]] if (is.null(patterns)) { condition <- simpleCondition("not a recognized multi-mode R document") return(renv_dependencies_error(path, error = condition)) } # parse the chunks within # NOTE: we need to proceed line-by-line since the chunk end pattern might # end chunks not started by the chunk begin pattern (sad face) encoding <- if (type == "md") "UTF-8" else "unknown" contents <- readLines(path, warn = FALSE, encoding = encoding) ranges <- renv_dependencies_discover_chunks_ranges(path, contents, patterns) # extract chunk code from the used ranges chunks <- .mapply(function(lhs, rhs) { # parse params in header header <- contents[[lhs]] params <- renv_knitr_options_header(header, type) # extract chunk contents (preserve newlines for nicer error reporting) range <- seq.int(lhs + 1, length.out = rhs - lhs - 1) code <- rep.int("", length(contents)) code[range] <- contents[range] # also parse chunk options params <- overlay(params, renv_knitr_options_chunk(code)) # return list of outputs list(params = params, code = code) }, ranges, NULL) # iterate over chunks, and attempt to parse dependencies from each cdeps <- bapply(chunks, function(chunk) { # check whether this chunk should be ignored if (renv_dependencies_discover_chunks_ignore(chunk)) return(character()) # remove reused chunk placeholders pattern <- "<<[^>]+>>" code <- gsub(pattern, "", chunk$code) # okay, now we can discover deps deps <- catch(renv_dependencies_discover_r(path = path, text = code)) if (inherits(deps, "error")) return(renv_dependencies_error(path, error = deps)) deps }) # check for dependencies in inline chunks as well ideps <- renv_dependencies_discover_chunks_inline(path, contents) # if this is a .qmd, infer a dependency on rmarkdown if we have any R chunks qdeps <- NULL if (mode %in% "qmd") { for (chunk in chunks) { engine <- chunk$params[["engine"]] if (is.character(engine) && tolower(engine) %in% c("r", "rscript")) { qdeps <- renv_dependencies_list(path, "rmarkdown") break } } } # paste them all together deps <- bind(list(cdeps, ideps, qdeps)) if (is.null(deps)) return(deps) deps$Source <- path deps } renv_dependencies_discover_chunks_inline <- function(path, contents) { pasted <- paste(contents, collapse = "\n") matches <- gregexpr("`r ([^`]+)`", pasted, perl = TRUE) if (identical(c(matches[[1L]]), -1L)) return(list()) text <- unlist(regmatches(pasted, matches), use.names = FALSE, recursive = FALSE) code <- substring(text, 4L, nchar(text) - 1L) deps <- renv_dependencies_discover_r(path = path, text = code) if (inherits(deps, "error")) return(renv_dependencies_error(path, error = deps)) deps } renv_dependencies_discover_chunks_ranges <- function(path, contents, patterns) { output <- list() chunk <- FALSE start <- 1; end <- 1 for (i in seq_along(contents)) { line <- contents[[i]] if (chunk == FALSE && grepl(patterns$chunk.begin, line)) { chunk <- TRUE start <- i next } if (chunk == TRUE && grepl(patterns$chunk.begin, line)) { end <- i output[[length(output) + 1]] <- list(lhs = start, rhs = end) start <- i next } if (chunk == TRUE && grepl(patterns$chunk.end, line)) { chunk <- FALSE end <- i output[[length(output) + 1]] <- list(lhs = start, rhs = end) next } } if (chunk) { message <- sprintf("chunk starting on line %i is not closed", start) error <- simpleError(message) renv_dependencies_error(path, error = error) } bind(output) } renv_dependencies_discover_ipynb <- function(path) { json <- catch(renv_json_read(path)) if (inherits(json, "error")) { info <- renv_file_info(path) if (!is.na(info$size) && info$size > 1) renv_dependencies_error(path, error = json) } if (!identical(json$metadata$kernelspec$language, "R")) return() deps <- stack() if (identical(json$metadata$kernelspec$name, "ir")) deps$push(renv_dependencies_list(path, "IRkernel")) for (cell in json$cells) { if (cell$cell_type != "code") next code <- paste0(cell$source, collapse = "") deps$push(renv_dependencies_discover_r(path, text = code)) } bind(deps$data()) } renv_dependencies_discover_rproj <- function(path) { props <- renv_properties_read(path) deps <- stack() if (identical(props$PackageUseDevtools, "Yes")) { deps$push("devtools") deps$push("roxygen2") } renv_dependencies_list(path, deps$data(), dev = TRUE) } renv_dependencies_discover_lintr <- function(path) { renv_dependencies_list(path, "lintr", dev = TRUE) } renv_dependencies_discover_r <- function(path = NULL, text = NULL, expr = NULL, envir = NULL, dev = NULL) { expr <- case( is.function(expr) ~ body(expr), is.language(expr) ~ expr, is.character(expr) ~ catch(renv_parse_text(expr)), is.character(text) ~ catch(renv_parse_text(text)), is.character(path) ~ catch(renv_parse_file(path)), ~ stop("internal error") ) if (inherits(expr, "error")) return(renv_dependencies_error(path, error = expr)) # resolve dev dev <- dev %||% path == the$paths$r_profile_user # update current path state <- renv_dependencies_state() if (!is.null(state)) renv_scope_binding(state, "path", path) methods <- c( renv_dependencies_discover_r_methods, renv_dependencies_discover_r_xfun, renv_dependencies_discover_r_library_require, renv_dependencies_discover_r_require_namespace, renv_dependencies_discover_r_colon, renv_dependencies_discover_r_citation, renv_dependencies_discover_r_pacman, renv_dependencies_discover_r_modules, renv_dependencies_discover_r_import, renv_dependencies_discover_r_box, renv_dependencies_discover_r_targets, renv_dependencies_discover_r_glue, renv_dependencies_discover_r_ggplot2, renv_dependencies_discover_r_parsnip, renv_dependencies_discover_r_testthat, renv_dependencies_discover_r_knitr, renv_dependencies_discover_r_database ) envir <- envir %||% new.env(parent = emptyenv()) callback <- if (renv_ext_enabled()) { function(node) { node <- renv_call_normalize(node) for (method in methods) method(node, envir) invisible(node) } } else { function(node) { node <- renv_call_normalize(node) for (method in methods) method(node, envir) assign("object", node, envir = parent.frame()) invisible(node) } } renv_dependencies_recurse(expr, callback) packages <- ls(envir = envir, all.names = TRUE) # also try to detect knitr::spin() dependencies -- this needs to # happen outside of the regular dependency discovery machinery # as it will rely on checking comments in the document # # https://github.com/rstudio/renv/issues/2023 if (is.character(text) || is.character(path)) { text <- text %||% readLines(path, n = 1L, warn = FALSE) if (length(text) && grepl("^\\s*#'\\s*[-]{3}\\s*$", text[[1L]], perl = TRUE)) packages <- union(c("knitr", "rmarkdown"), packages) } renv_dependencies_list(path, packages, dev = dev) } renv_dependencies_discover_r_methods <- function(node, envir) { node <- renv_call_expect(node, "methods", c("setClass", "setGeneric")) if (is.null(node)) return(FALSE) envir[["methods"]] <- TRUE TRUE } renv_dependencies_discover_r_xfun <- function(node, envir) { node <- renv_call_expect(node, "xfun", c("pkg_attach", "pkg_attach2")) if (is.null(node)) return(FALSE) # attempt to match the call prototype <- function(..., install = FALSE, message = TRUE) {} matched <- catch(match.call(prototype, node, expand.dots = FALSE)) if (inherits(matched, "error")) return(FALSE) # extract character vectors from `...` strings <- stack() recurse(matched[["..."]], function(node) { if (is.character(node)) strings$push(node) }) # mark packages as known packages <- strings$data() if (empty(packages)) return(FALSE) for (package in packages) envir[[package]] <- TRUE TRUE } renv_dependencies_discover_r_library_require <- function(node, envir) { node <- renv_call_expect(node, "base", c("library", "require")) if (is.null(node)) return(FALSE) # attempt to match the call matched <- catch(match.call(base::library, node)) if (inherits(matched, "error")) return(FALSE) # if the 'package' argument is a character vector of length one, we're done if (is.character(matched$package) && length(matched$package) == 1) { envir[[matched$package]] <- TRUE return(TRUE) } # if it's a symbol, double check character.only argument if (is.symbol(matched$package) && identical(matched$character.only %||% FALSE, FALSE)) { envir[[as.character(matched$package)]] <- TRUE return(TRUE) } FALSE } renv_dependencies_discover_r_require_namespace <- function(node, envir) { node <- renv_call_expect(node, "base", c("requireNamespace", "loadNamespace")) if (is.null(node)) return(FALSE) f <- get(as.character(node[[1]]), envir = .BaseNamespaceEnv, inherits = FALSE) matched <- catch(match.call(f, node)) if (inherits(matched, "error")) return(FALSE) package <- matched$package if (is.character(package) && length(package == 1)) { envir[[package]] <- TRUE return(TRUE) } FALSE } renv_dependencies_discover_r_colon <- function(node, envir) { ok <- renv_call_matches(node, names = c("::", ":::"), nargs = 2L) if (!ok) return(FALSE) package <- node[[2L]] if (is.symbol(package)) package <- as.character(package) if (!is.character(package) || length(package) != 1L) return(FALSE) envir[[package]] <- TRUE TRUE } renv_dependencies_discover_r_citation <- function(node, envir) { node <- renv_call_expect(node, "utils", "citation") if (is.null(node)) return(FALSE) matched <- catch(match.call(utils::citation, node)) if (inherits(matched, "error")) return(FALSE) package <- matched[["package"]] if (!is.character(package) || length(package) != 1L) return(FALSE) envir[[package]] <- TRUE TRUE } renv_dependencies_discover_r_pacman <- function(node, envir) { node <- renv_call_expect(node, "pacman", "p_load") if (is.null(node) || length(node) < 2) return(FALSE) # check for character.only chonly <- node[["character.only"]] %||% FALSE # consider all unnamed arguments parts <- as.list(node[-1L]) # consider packages passed to 'char' parameter char <- node[["char"]] # detect vector of packages passed as vector if (renv_call_matches(char, "c")) parts <- c(parts, as.list(char[-1L])) # detect plain old package name if (is.character(char)) parts <- c(parts, as.list(char)) # ensure names names(parts) <- names(parts) %||% rep.int("", length(parts)) unnamed <- parts[!nzchar(names(parts))] # extract symbols / characters for (arg in unnamed) { # skip symbols if necessary if (chonly && is.symbol(arg)) next # check for character or symbol ok <- length(arg) == 1 && is.character(arg) || is.symbol(arg) if (!ok) next # add it envir[[as.character(arg)]] <- TRUE } TRUE } renv_dependencies_discover_r_modules <- function(node, envir) { # check for an explicit call to 'modules::import()' if (identical(node[[1L]], quote(modules::import))) { renv_dependencies_discover_r_modules_impl(node, envir) } # check for 'import' usages with a module block node <- renv_call_expect(node, "modules", "module") if (length(node) >= 2L && identical(node[[1L]], quote(module)) && is.call(node[[2L]]) && identical(node[[2L]][[1L]], as.symbol("{"))) { renv_dependencies_recurse(node[[2L]], function(node) { renv_dependencies_discover_r_modules_impl(node, envir) }) } } renv_dependencies_discover_r_modules_impl <- function(node, envir) { node <- renv_call_expect(node, "modules", c("import")) if (is.null(node)) return(FALSE) # attempt to match the call prototype <- function(from, ..., attach = TRUE, where = parent.frame()) {} matched <- catch(match.call(prototype, node, expand.dots = FALSE)) if (inherits(matched, "error")) return(FALSE) # extract character vector or symbol from `from` package <- matched[["from"]] if (empty(package)) return(FALSE) # package could be symbols or character so call as.character # to be safe then mark packages as known envir[[as.character(package)]] <- TRUE TRUE } renv_dependencies_discover_r_import <- function(node, envir) { # require that usages are colon-prefixed colon <- renv_call_matches(node[[1L]], names = c("::", ":::"), nargs = 2L) if (!colon) return(FALSE) node <- renv_call_expect(node, "import", c("from", "here", "into")) if (is.null(node)) return(FALSE) # attempt to match the call name <- as.character(node[[1L]]) matched <- if (name == "from") { catch(match.call(function(.from, ...) {}, node, expand.dots = FALSE)) } else { catch(match.call(function(..., .from) {}, node, expand.dots = FALSE)) } if (inherits(matched, "error")) return(FALSE) # the '.from' argument is the package name, either a character vector of length one or a symbol from <- matched$.from if (is.symbol(from)) { co <- node[[".character_only"]] if (!identical(co, TRUE)) from <- as.character(from) } ok <- is.character(from) && length(from) == 1L if (!ok) return(FALSE) # '.from' can also be an R script; if it appears to be a path, then ignore it # https://github.com/rstudio/renv/issues/1743 if (grepl("\\.[rR]$", from, perl = TRUE) && grepl("[/\\]", from)) return(FALSE) envir[[from]] <- TRUE TRUE } renv_dependencies_discover_r_box <- function(node, envir) { node <- renv_call_expect(node, "box", "use") if (is.null(node)) return(FALSE) for (i in seq.int(2L, length.out = length(node) - 1L)) renv_dependencies_discover_r_box_impl(node[[i]], envir) TRUE } renv_dependencies_discover_r_box_impl <- function(node, envir) { # if the call uses /, it's a path, not a package if (renv_call_matches(node, "/")) return(FALSE) # if the node is just a symbol, then it's the name of a package # otherwise, if it's a call to `[`, the first argument is the package name name <- if (is.symbol(node) && !identical(node, quote(expr = ))) { as.character(node) } else if ( renv_call_matches(node, "[") && length(node) > 1L && is.symbol(node[[2L]])) { as.character(node[[2L]]) } # the names `.` and `..` are special place holders and don't refer to packages if (is.null(name) || name == "." || name == "..") return(FALSE) envir[[name]] <- TRUE TRUE } renv_dependencies_discover_r_targets <- function(node, envir) { node <- renv_call_expect(node, "targets", "tar_option_set") if (is.null(node)) return(FALSE) envir[["targets"]] <- TRUE packages <- tryCatch( renv_dependencies_eval(node$packages), error = identity ) # TODO: evaluation can fail for a multitude of reasons; # are any of these worth signalling to the user? if (inherits(packages, "error")) return(TRUE) if (is.character(packages)) for (package in packages) envir[[package]] <- TRUE TRUE } renv_dependencies_discover_r_glue <- function(node, envir) { node <- renv_call_expect(node, "glue", "glue") if (is.null(node)) return(FALSE) # analyze all unnamed strings in the call args <- as.list(node)[-1L] nm <- names(args) %||% rep.int("", length(args)) strings <- args[!nzchar(nm) & map_lgl(args, is.character)] # start iterating through the strings, looking for code chunks for (string in strings) renv_dependencies_discover_r_glue_impl(string, node, envir) TRUE } renv_dependencies_discover_r_ggplot2 <- function(node, envir) { node <- renv_call_expect(node, "ggplot2", "ggsave") if (is.null(node)) return(FALSE) # check for attempts to save to '.svg', and assume svglite is # required in this scenario. matched <- catch(match.call(function(filename, ...) {}, node)) if (inherits(matched, "error")) return(FALSE) filename <- matched$filename if (!is.character(filename)) return(FALSE) if (!endswith(filename, ".svg")) return(FALSE) envir[["svglite"]] <- TRUE TRUE } renv_dependencies_discover_r_testthat <- function(node, envir) { # check for construction of JunitReporter if (identical(node, quote(JunitReporter$new))) { envir[["xml2"]] <- TRUE return(TRUE) } # check for an R6 class inheriting from a JunitReporter class <- renv_call_expect(node, "R6", "R6Class") if (!is.null(class) && identical(class$inherit, quote(JunitReporter))) { envir[["xml2"]] <- TRUE return(TRUE) } # check for calls to various test runners, which accept a reporter node <- renv_call_expect(node, "testthat", c("test_package", "test_dir", "test_file")) if (is.null(node)) return(FALSE) candidates <- list( "Junit", "junit", quote(JunitReporter), quote(testthat::JunitReporter) ) reporter <- node$reporter if (!is.null(reporter)) { for (candidate in candidates) { if (identical(candidate, reporter)) { envir[["xml2"]] <- TRUE return(TRUE) } } } FALSE } renv_dependencies_discover_r_knitr <- function(node, envir) { matched <- is.call(node) && ( identical(node[[1L]], quote(knitr::opts_chunk$set)) || identical(node[[1L]], quote(opts_chunk$set)) ) if (!matched) return(FALSE) args <- as.list(node) if (identical(args[["dev"]], "ragg_png")) { envir[["ragg"]] <- TRUE return(TRUE) } FALSE } renv_dependencies_discover_r_glue_impl <- function(string, node, envir) { # get open, close delimiters ropen <- charToRaw(node$.open %||% "{") rclose <- charToRaw(node$.close %||% "}") rcomment <- charToRaw(node$.comment %||% "#") # constants rcomment <- charToRaw("#") rbackslash <- charToRaw("\\") rquotes <- c( charToRaw("'"), charToRaw("\""), charToRaw("`") ) # iterate through characters in string raw <- c(charToRaw(string), as.raw(0L)) i <- 0L n <- length(raw) quote <- raw() # index for open delimiter match index <- 0L count <- 0L while (i < n) { # ensure we always advance index i <- i + 1L # handle quoted states if (length(quote)) { # skip escaped characters if (raw[[i]] == rbackslash) { i <- i + 1L next } # check for escape from quote if (raw[[i]] == quote) { quote <- raw() next } } # skip comments if (raw[[i]] == rcomment) { i <- grepRaw("(?:$|\n)", raw, i) next } # skip escaped characters if (raw[[i]] == rbackslash) { i <- i + 1L next } # check for quotes idx <- match(raw[[i]], rquotes, nomatch = 0L) if (idx > 0) { quote <- rquotes[[idx]] next } # check for open delimiter if (i %in% grepRaw(ropen, raw, i, fixed = TRUE)) { # check for duplicate (escape) j <- i + length(ropen) if (j %in% grepRaw(ropen, raw, j, fixed = TRUE)) { i <- j + length(ropen) - 1L next } # save index if we're starting a match if (count == 0L) { index <- i } # increment match count count <- count + 1L next } # check for close delimiter if (i %in% grepRaw(rclose, raw, i, fixed = TRUE)) { # check for duplicate (escape) j <- i + length(rclose) if (j %in% grepRaw(rclose, raw, j, fixed = TRUE)) { i <- j + length(rclose) - 1L next } if (count > 0L) { # decrement count if we have a match count <- count - 1L # check for match and parse dependencies within if (count == 0L) { # extract inner code lhs <- index + length(ropen) rhs <- i - 1L code <- rawToChar(raw[lhs:rhs]) # parse dependencies renv_dependencies_discover_r(text = code, envir = envir) } } } } } renv_dependencies_discover_r_parsnip <- function(node, envir) { node <- renv_call_expect(node, "parsnip", "set_engine") if (is.null(node)) return(FALSE) matched <- catch(match.call(function(object, engine, ...) {}, node)) if (inherits(matched, "error")) return(FALSE) engine <- matched$engine if (!is.character(engine) || length(engine) != 1L) return(FALSE) map <- getOption("renv.parsnip.engines", default = list( glm = "stats", glmnet = "glmnet", keras = "keras", kknn = "kknn", nnet = "nnet", rpart = "rpart", spark = "sparklyr", stan = "rstanarm" )) packages <- if (is.function(map)) tryCatch(map(engine), error = function(e) NULL) else map[[engine]] if (is.null(packages)) return(FALSE) for (package in packages) envir[[package]] <- TRUE # TODO: a number of model routines appear to depend on dials; # should we just assume it's required by default? or should # users normally be using tidymodels instead of parsnip directly? TRUE } renv_dependencies_discover_r_database <- function(node, envir) { found <- FALSE matched <- function(requirements) { for (requirement in requirements) envir[[requirement]] <<- TRUE found <<- TRUE } db <- renv_dependencies_database() enumerate(db, function(package, dependencies) { enumerate(dependencies, function(method, requirements) { if (is.call(node)) { expect <- renv_call_expect(node, package, method) if (!is.null(expect)) return(matched(requirements)) } if (is.symbol(node)) { value <- as.character(node) if (identical(value, method)) return(matched(requirements)) } }) }) found } renv_dependencies_database <- function() { the$dependencies_database <- the$dependencies_database %||% { db <- getOption("renv.dependencies.database", default = list()) db$ggplot2$geom_hex <- "hexbin" db$testthat$JunitReporter <- "xml2" db } } renv_dependencies_list <- function(source, packages, require = "", version = "", dev = FALSE) { if (empty(packages)) return(renv_dependencies_list_empty()) source <- source %||% rep.int(NA_character_, length(packages)) data_frame( Source = as.character(source), Package = as.character(packages), Require = require, Version = version, Dev = dev ) } renv_dependencies_list_empty <- function() { data_frame( Source = character(), Package = character(), Require = character(), Version = character(), Dev = logical() ) } renv_dependencies_require <- function(package, type = NULL) { if (requireNamespace(package, quietly = TRUE)) return(TRUE) if (once()) { fmt <- lines( "The '%1$s' package is required to parse dependencies within %2$s", "Consider installing it with `install.packages(\"%1$s\")`." ) within <- if (is.null(type)) "this project" else paste(type, "files") warningf(fmt, package, within) } return(FALSE) } the$dependencies_state <- NULL renv_dependencies_state <- function(key = NULL) { state <- the$dependencies_state if (is.null(key)) state else state[[key]] } renv_dependencies_scope <- function(root = NULL, scope = parent.frame()) { state <- env(root = root, scanned = env(), problems = stack()) the$dependencies_state <- state defer(the$dependencies_state <- NULL, scope = scope) } renv_dependencies_error_push <- function(path = NULL, error = NULL) { state <- renv_dependencies_state() if (is.null(state)) return() path <- path %||% state$path problem <- list(file = path, error = error) state$problems$push(problem) } renv_dependencies_error <- function(path, error = NULL, packages = NULL) { # if no error, return early if (is.null(error)) return(renv_dependencies_list(path, packages)) # push the error report renv_dependencies_error_push(path, error) # return dependency list renv_dependencies_list(path, packages) } renv_dependencies_report <- function(errors) { if (identical(errors, "ignored")) return(FALSE) state <- renv_dependencies_state() if (is.null(state)) return(FALSE) problems <- state$problems$data() if (empty(problems)) return(TRUE) # bind into list bound <- bapply(problems, function(problem) { fields <- c(renv_path_aliased(problem$file), problem$line, problem$column) header <- paste(fields, collapse = ":") message <- conditionMessage(problem$error) c(file = problem$file, header = header, message = message) }) # split based on header (group errors from same file) splat <- split(bound, bound$file) # emit messages lines <- enumerate(splat, function(file, problem) { messages <- paste("Error", problem$message, sep = ": ", collapse = "\n\n") paste(c(header(file), messages, ""), collapse = "\n") }) bulletin( "WARNING: One or more problems were discovered while enumerating dependencies.", c("", lines), "Please see `?renv::dependencies` for more information.", bullets = FALSE ) if (identical(errors, "fatal")) { fmt <- "one or more problems were encountered while enumerating dependencies" stopf(fmt) } renv_condition_signal("renv.dependencies.problems", problems) TRUE } renv_dependencies_eval <- function(expr) { # create environment with small subset of "safe" symbols, that # are commonly used for chunk expressions syms <- c( "list", "c", "T", "F", "{", "(", "[", "[[", "::", ":::", "$", "@", ":", "+", "-", "*", "/", "<", ">", "<=", ">=", "==", "!=", "!", "&", "&&", "|", "||" ) vals <- mget(syms, envir = baseenv()) envir <- list2env(vals, parent = emptyenv()) # evaluate in that environment eval(expr, envir = envir) } renv_dependencies_recurse <- function(object, callback) { if (is.call(object)) callback(object) if (is.recursive(object)) for (i in seq_along(object)) if (is.call(object[[i]])) renv_dependencies_recurse_impl(object[[i]], callback) } renv_dependencies_recurse_impl <- function(object, callback) { callback(object) for (i in seq_along(object)) if (is.call(object[[i]])) renv_dependencies_recurse_impl(object[[i]], callback) } renv/R/infrastructure.R0000644000176200001440000001453614731330073014642 0ustar liggesusers # tools for writing / removing renv-related infrastructure renv_infrastructure_write <- function(project = NULL, profile = NULL, version = NULL) { # don't do anything in embedded mode if (renv_metadata_embedded()) return() project <- renv_project_resolve(project) renv_infrastructure_write_profile(project, profile = profile) renv_infrastructure_write_rprofile(project) renv_infrastructure_write_rbuildignore(project) renv_infrastructure_write_gitignore(project) renv_infrastructure_write_activate(project, version = version) } renv_infrastructure_write_profile <- function(project, profile = NULL) { path <- renv_paths_renv("profile", profile = FALSE, project = project) profile <- renv_profile_normalize(profile) if (is.null(profile)) return(unlink(path)) ensure_parent_directory(path) writeLines(profile, con = path) } renv_infrastructure_write_rprofile <- function(project) { if (!config$autoloader.enabled()) return() # NOTE: intentionally leave project NULL to compute relative path path <- renv_paths_activate(project = NULL) add <- sprintf("source(%s)", renv_json_quote(path)) renv_infrastructure_write_entry_impl( add = add, remove = character(), file = file.path(project, ".Rprofile"), create = TRUE ) } renv_infrastructure_write_rbuildignore <- function(project) { lines <- c("^renv$", "^renv\\.lock$") if (file.exists(file.path(project, "requirements.txt"))) lines <- c(lines, "^requirements\\.txt$") if (file.exists(file.path(project, "environment.yml"))) lines <- c(lines, "^environment\\.yml$") renv_infrastructure_write_entry_impl( add = lines, remove = character(), file = file.path(project, ".Rbuildignore"), create = renv_project_type(project) == "package" ) } renv_infrastructure_write_gitignore <- function(project) { if (!settings$vcs.manage.ignores()) return() add <- stack(mode = "character") remove <- stack(mode = "character") stk <- if (settings$vcs.ignore.library()) add else remove stk$push("library/") stk <- if (settings$vcs.ignore.local()) add else remove stk$push("local/") stk <- if (settings$vcs.ignore.cellar()) add else remove stk$push("cellar/") add$push("lock/", "python/", "sandbox/", "staging/") renv_infrastructure_write_entry_impl( add = as.character(add$data()), remove = as.character(remove$data()), file = renv_paths_renv(".gitignore", project = project), create = TRUE ) } renv_infrastructure_write_activate <- function(project = NULL, version = NULL, create = TRUE) { project <- renv_project_resolve(project) version <- version %||% renv_activate_version(project) sha <- attr(version, "sha", exact = TRUE) source <- system.file("resources/activate.R", package = "renv") target <- renv_paths_activate(project = project) if (!create && !file.exists(target)) return(FALSE) template <- renv_file_read(source) new <- renv_template_replace( text = template, replacements = list( version = stringify(as.character(version)), sha = stringify(sha) ), format = "..%s.." ) if (file.exists(target)) { old <- renv_file_read(target) if (old == new) return(TRUE) } ensure_parent_directory(target) writeLines(new, con = target) } renv_infrastructure_write_entry_impl <- function(add, remove, file, create) { # check to see if file doesn't exist if (!file.exists(file)) { # if we're not forcing file creation, just bail if (!create) return(TRUE) # otherwise, write the file ensure_parent_directory(file) writeLines(add, con = file) return(TRUE) } # if the file already has the requested line, nothing to do before <- readLines(file, warn = FALSE) after <- before # add requested entries for (item in rev(add)) { # check to see if the requested line exists (either commented # or uncommented). if it exists, we'll attempt to uncomment # any commented lines cpattern <- sprintf("^\\s*#?\\s*\\Q%s\\E\\s*(?:#|\\s*$)", item) matches <- grepl(cpattern, after, perl = TRUE) if (any(matches)) after[matches] <- gsub("^(\\s*)#\\s*", "\\1", after[matches]) else after <- c(item, after) } # remove requested entries for (item in rev(remove)) { pattern <- sprintf("^\\s*\\Q%s\\E\\s*(?:#|\\s*$)", item) matches <- grepl(pattern, after, perl = TRUE) if (any(matches)) { replacement <- gsub("^(\\s*)", "\\1# ", after[matches], perl = TRUE) after[matches] <- replacement } } # write to file if we have changes if (!identical(before, after)) writeLines(after, con = file) TRUE } renv_infrastructure_remove <- function(project = NULL) { project <- renv_project_resolve(project) renv_infrastructure_remove_rprofile(project) renv_infrastructure_remove_rbuildignore(project) unlink(file.path(project, "renv"), recursive = TRUE) } renv_infrastructure_remove_rprofile <- function(project) { # NOTE: intentionally leave project NULL to compute relative path path <- renv_paths_activate(project = NULL) line <- sprintf("source(%s)", renv_json_quote(path)) renv_infrastructure_remove_entry_impl( line = line, file = file.path(project, ".Rprofile"), removable = TRUE ) } renv_infrastructure_remove_rbuildignore <- function(project) { renv_infrastructure_remove_entry_impl( line = "^renv$", file = file.path(project, ".Rbuildignore"), removable = FALSE ) } renv_infrastructure_remove_entry_impl <- function(line, file, removable) { # if the file doesn't exist, nothing to do if (!file.exists(file)) return(TRUE) # find and comment out the line contents <- readLines(file, warn = FALSE) pattern <- sprintf("^\\s*\\Q%s\\E\\s*(?:#|\\s*$)", line) matches <- grepl(pattern, contents, perl = TRUE) # if this file is removable, check to see if we matched all non-blank # lines; if so, remove the file if (removable) { rest <- contents[!matches] if (all(grepl("^\\s*$", rest))) return(unlink(file)) } # otherwise, just mutate the file replacement <- gsub("^(\\s*)", "\\1# ", contents[matches], perl = TRUE) contents[matches] <- replacement writeLines(contents, con = file) TRUE } renv/R/snapshot-auto.R0000644000176200001440000001114714731330073014362 0ustar liggesusers # information about the project library; used to detect whether # the library appears to have been modified or updated the$library_info <- NULL # are we forcing automatic snapshots? the$auto_snapshot_forced <- FALSE # did the last attempt at an automatic snapshot fail? the$auto_snapshot_failed <- FALSE # are we currently running an automatic snapshot? the$auto_snapshot_running <- FALSE # is the next automatic snapshot suppressed? the$auto_snapshot_suppressed <- FALSE # nocov start renv_snapshot_auto <- function(project) { # set some state so we know we're running the$auto_snapshot_running <- TRUE defer(the$auto_snapshot_running <- FALSE) # passed pre-flight checks; snapshot the library updated <- withCallingHandlers( tryCatch( renv_snapshot_auto_impl(project), error = function(err) FALSE ), cancel = function() FALSE ) if (updated) { lockfile <- renv_path_aliased(renv_lockfile_path(project)) writef("- Automatic snapshot has updated '%s'.", lockfile) } invisible(updated) } renv_snapshot_auto_impl <- function(project) { # validation messages can be noisy; turn off for auto snapshot renv_scope_options( renv.config.snapshot.validate = FALSE, renv.verbose = FALSE ) # file.info() can warn in some cases; silence those renv_scope_options(warn = -1L) # get current lockfile state lockfile <- renv_paths_lockfile(project) old <- file.info(lockfile, extra_cols = FALSE)$mtime # perform snapshot without prompting snapshot(project = project, prompt = FALSE) # check for change in lockfile new <- file.info(lockfile, extra_cols = FALSE)$mtime !identical(old, new) } renv_snapshot_auto_enabled <- function(project = renv_project_get()) { # respect override if (the$auto_snapshot_forced) return(TRUE) # respect config setting enabled <- config$auto.snapshot(project = project) if (!enabled) return(FALSE) # only snapshot interactively if (!interactive()) return(FALSE) # only automatically snapshot the current project if (!renv_project_loaded(project)) return(FALSE) # don't auto-snapshot if the project hasn't been initialized if (!renv_project_initialized(project = project)) return(FALSE) # don't auto-snapshot if we don't have a library library <- renv_paths_library(project = project) if (!file.exists(library)) return(FALSE) # don't auto-snapshot unless the active library is the project library if (!renv_file_same(renv_libpaths_active(), library)) return(FALSE) TRUE } renv_snapshot_auto_update <- function(project = renv_project_get() ) { # check for enabled if (!renv_snapshot_auto_enabled(project = project)) return(FALSE) # get path to project library libpath <- renv_paths_library(project = project) if (!file.exists(libpath)) return(FALSE) # list files + get file info for files in project library info <- renv_file_info(libpath) # only keep relevant fields fields <- c("size", "mtime", "ctime") new <- c(info[fields]) # update our cached info old <- the$library_info the$library_info <- new # if we've suppressed the next automatic snapshot, bail here if (the$auto_snapshot_suppressed) { the$auto_snapshot_suppressed <- FALSE return(FALSE) } # report if things have changed !is.null(old) && !identical(old, new) } renv_snapshot_task <- function() { # if the previous snapshot attempt failed, do nothing if (the$auto_snapshot_failed) return(FALSE) # silence warnings in this scope renv_scope_options(warn = -1L) # attempt automatic snapshot, but disable on failure tryCatch( renv_snapshot_task_impl(), error = function(cnd) { caution("Error generating automatic snapshot: %s", conditionMessage(cnd)) caution("Automatic snapshots will be disabled. Use `renv::snapshot()` to manually update the lockfile.") the$auto_snapshot_failed <- TRUE } ) } renv_snapshot_task_impl <- function() { # check for active renv project project <- renv_project_get() if (is.null(project)) return(invisible(FALSE)) # see if library state has updated updated <- renv_snapshot_auto_update(project = project) if (!updated) return(invisible(FALSE)) # library has updated; perform auto snapshot status <- renv_snapshot_auto(project = project) ok <- identical(status, TRUE) # return invisibly for snapshot tests invisible(ok) } renv_snapshot_auto_suppress_next <- function() { # if we're currently running an automatic snapshot, then nothing to do if (the$auto_snapshot_running) return() # otherwise, set the suppressed flag the$auto_snapshot_suppressed <- TRUE } # nocov end renv/R/addins.R0000644000176200001440000000400014731330072013004 0ustar liggesusers renv_addins_embed_ui <- function() { miniUI::miniPage( miniUI::gadgetTitleBar("Embed a Lockfile"), miniUI::miniContentPanel( shiny::verticalLayout( shiny::fileInput( inputId = "lockfile", label = "Lockfile path:", placeholder = "(Use default)" ) ) ) ) } renv_addins_embed_server <- function(input, output, session) { shiny::observeEvent(input$done, { # notify the user that we're working now progress <- shiny::Progress$new( session = shiny::getDefaultReactiveDomain(), style = "notification" ) progress$set(message = "Embedding lockfile...") # get editor context context <- rstudioapi::getSourceEditorContext() # validate we have a path path <- context$path if (!nzchar(path)) stop("cannot embed lockfile into an unsaved file", call. = FALSE) # get project path project <- rstudioapi::getActiveProject() # read lockfile lockfile <- input$lockfile if (!is.null(lockfile)) lockfile <- renv_lockfile_read(file = lockfile$datapath) # save document and run embed rstudioapi::documentSave(id = context$id) embed(path = path, lockfile = lockfile, project = project) # stop app invisible(shiny::stopApp()) }) } renv_addins_embed <- function() { # first, check that shiny and miniUI are available for (package in c("miniUI", "rstudioapi", "shiny")) { if (!requireNamespace(package, quietly = TRUE)) { fmt <- "required package '%s' is not available" stopf(fmt, package) } } # ask the user to save the document first if necessary context <- rstudioapi::getSourceEditorContext() if (!nzchar(context$path)) stop("this addin cannot be run with an unsaved document") # okay, we can run the addin shiny::runGadget( app = renv_addins_embed_ui(), server = renv_addins_embed_server, viewer = shiny::dialogViewer( dialogName = "Embed Lockfile", width = 400, height = 200 ) ) } renv/R/rtools.R0000644000176200001440000001012614731330073013073 0ustar liggesusers renv_rtools_list <- function() { drive <- Sys.getenv("SYSTEMDRIVE", unset = "C:") roots <- c( renv_rtools_registry(), Sys.getenv("RTOOLS44_HOME", unset = file.path(drive, "rtools44")), Sys.getenv("RTOOLS43_HOME", unset = file.path(drive, "rtools43")), Sys.getenv("RTOOLS42_HOME", unset = file.path(drive, "rtools42")), Sys.getenv("RTOOLS40_HOME", unset = file.path(drive, "rtools40")), file.path(drive, "Rtools"), list.files(file.path(drive, "RBuildTools"), full.names = TRUE), "~/Rtools", list.files("~/RBuildTools", full.names = TRUE) ) roots <- unique(roots[file.exists(roots)]) lapply(roots, renv_rtools_read) } renv_rtools_find <- function() { for (spec in renv_rtools_list()) if (renv_rtools_compatible(spec)) return(spec) NULL } renv_rtools_read <- function(root) { list( root = root, version = renv_rtools_version(root) ) } renv_rtools_version <- function(root) { name <- basename(root) # check for 'rtools' folder # e.g. C:/rtools42 pattern <- "^rtools(\\d)(\\d)$" if (grepl(pattern, name, perl = TRUE, ignore.case = TRUE)) return(gsub(pattern, "\\1.\\2", name, perl = TRUE, ignore.case = TRUE)) # check for versioned installation path # e.g. C:/RBuildTools/4.2 version <- catch(numeric_version(name)) if (!inherits(version, "error")) return(format(version)) # detect older Rtools installations path <- file.path(root, "VERSION.txt") if (!file.exists(path)) return(NULL) contents <- readLines(path, warn = FALSE) version <- gsub("[^[:digit:].]", "", contents) numeric_version(version) } renv_rtools_compatible <- function(spec) { if (is.null(spec$version)) return(FALSE) ranges <- list( "4.4" = c("4.4.0", "9.9.9"), "4.3" = c("4.3.0", "4.4.0"), "4.2" = c("4.2.0", "4.3.0"), "4.0" = c("4.0.0", "4.2.0"), "3.5" = c("3.3.0", "4.0.0"), "3.4" = c("3.3.0", "4.0.0"), "3.3" = c("3.2.0", "3.3.0"), "3.2" = c("3.1.0", "3.2.0"), "3.1" = c("3.0.0", "3.1.0") ) version <- numeric_version(spec$version)[1, 1:2] range <- ranges[[format(version)]] if (is.null(range)) return(FALSE) rversion <- getRversion() range[[1]] <= rversion && rversion < range[[2]] } renv_rtools_registry <- function() { status <- tryCatch( utils::readRegistry( key = "SOFTWARE\\R-Core\\Rtools", hive = "HLM" ), error = function(e) list() ) path <- status$InstallPath %||% "" if (file.exists(path)) return(renv_path_normalize(path)) } renv_rtools_envvars <- function(root) { version <- renv_rtools_version(root) if (version < "4.0") renv_rtools_envvars_default(root) else if (version < "4.2") renv_rtools_envvars_rtools40(root) else if (version < "4.3") renv_rtools_envvars_rtools42(root) else if (version < "4.4") renv_rtools_envvars_rtools43(root) else renv_rtools_envvars_default(root) } renv_rtools_envvars_default <- function(root) { # add Rtools utilities to path bin <- normalizePath(file.path(root, "usr/bin"), mustWork = FALSE) path <- paste(bin, Sys.getenv("PATH"), sep = .Platform$path.sep) # set BINPREF binpref <- "" list(PATH = path, BINPREF = binpref) } renv_rtools_envvars_rtools43 <- function(root) { # add Rtools utilities to path bin <- normalizePath(file.path(root, "usr/bin"), mustWork = FALSE) path <- paste(bin, Sys.getenv("PATH"), sep = .Platform$path.sep) # set BINPREF binpref <- "" list(PATH = path, BINPREF = binpref) } renv_rtools_envvars_rtools42 <- function(root) { # add Rtools utilities to path bin <- normalizePath(file.path(root, "usr/bin"), mustWork = FALSE) path <- paste(bin, Sys.getenv("PATH"), sep = .Platform$path.sep) # set BINPREF binpref <- "" list(PATH = path, BINPREF = binpref) } renv_rtools_envvars_rtools40 <- function(root) { # add Rtools utilities to path bin <- normalizePath(file.path(root, "usr/bin"), mustWork = FALSE) path <- paste(bin, Sys.getenv("PATH"), sep = .Platform$path.sep) # set BINPREF (note: trailing slash is required) binpref <- "/mingw$(WIN)/bin/" list(PATH = path, BINPREF = binpref) } renv/R/cellar.R0000644000176200001440000000245714731330072013022 0ustar liggesusers renv_cellar_roots <- function(project = NULL) { c( renv_paths_renv("cellar", project = project), renv_paths_renv("local", project = project), renv_paths_cellar(), renv_paths_local() ) } renv_cellar_database <- function(project = NULL) { # find cellar root directories project <- renv_project_resolve(project) roots <- renv_cellar_roots(project) # list files both at top-level + one nested level paths <- list.files(roots, full.names = TRUE) paths <- c(paths, list.files(paths, full.names = TRUE)) # grab files that look like packages extpat <- "(?:\\.tar\\.gz|\\.tgz|\\.zip)$" paths <- grep(extpat, paths, value = TRUE) # parse into data.frame base <- basename(paths) parts <- strsplit(base, "_", fixed = TRUE) package <- map_chr(parts, `[[`, 1L) rest <- map_chr(parts, `[[`, 2L) version <- sub(extpat, "", rest) data_frame( Package = package, Version = version, Path = paths ) } renv_cellar_latest <- function(package, project) { db <- renv_cellar_database(project = project) db <- rows(db, db$Package == package) db <- rows(db, order(package_version(db$Version), decreasing = TRUE)) if (nrow(db) == 0L) return(record) entry <- db[1, ] list( Package = entry$Package, Version = entry$Version, Source = "Cellar" ) } renv/R/clean.R0000644000176200001440000002155014761163114012641 0ustar liggesusers #' Clean a project #' #' Clean up a project and its associated \R libraries. #' #' # Actions #' #' The following clean actions are available: #' #' \describe{ #' #' \item{`package.locks`}{ #' #' During package installation, \R will create package locks in the #' library path, typically named `00LOCK-`. On occasion, if package #' installation fails or \R is terminated while installing a package, these #' locks can be left behind and will inhibit future attempts to reinstall #' that package. Use this action to remove such left-over package locks. #' #' } #' #' \item{`library.tempdirs`}{ #' #' During package installation, \R may create temporary directories with #' names of the form `file\w{12}`, and on occasion those files can be #' left behind even after they are no longer in use. Use this action to #' remove such left-over directories. #' } #' #' \item{`system.library`}{ #' #' In general, it is recommended that only packages distributed with \R #' are installed into the default library (the library path referred to #' by `.Library`). Use this action to remove any user-installed packages #' that have been installed to the system library. #' #' Because this action is destructive, it is by default never run -- it #' must be explicitly requested by the user. #' #' } #' #' \item{`unused.packages`}{ #' #' Remove packages that are installed in the project library, but no longer #' appear to be used in the project sources. #' #' Because this action is destructive, it is by default only run in #' interactive sessions when prompting is enabled. #' #' } #' #' } #' #' #' @inherit renv-params #' #' @param actions The set of clean actions to take. See the documentation in #' **Actions** for a list of available actions, and the default actions #' taken when no actions are supplied. #' #' @export #' #' @examples #' \dontrun{ #' #' # clean the current project #' renv::clean() #' #' } clean <- function(project = NULL, ..., actions = NULL, prompt = interactive()) { renv_scope_error_handler() renv_dots_check(...) project <- renv_project_resolve(project) renv_project_lock(project = project) renv_scope_verbose_if(prompt) renv_activate_prompt("clean", NULL, prompt, project) actions <- actions %||% renv_clean_actions(prompt) all <- list( package.locks = renv_clean_package_locks, library.tempdirs = renv_clean_library_tempdirs, system.library = renv_clean_system_library, unused.packages = renv_clean_unused_packages ) methods <- all[actions] for (method in methods) tryCatch(method(project, prompt), error = warnify) writef("- The project has been cleaned.") invisible(project) } renv_clean_actions <- function(prompt) { default <- c( "package.locks", "library.tempdirs" ) unsafe <- c( # "system.library", "unused.packages" ) c(default, if (prompt) unsafe) } renv_clean_library_tempdirs <- function(project, prompt) { ntd <- function() { writef("- No temporary directories were found in the project library.") FALSE } library <- renv_paths_library(project = project) children <- list.files(library, full.names = TRUE) bad <- grep("/file\\w{12}$", children, value = TRUE) if (empty(bad)) return(ntd()) # nocov start if (prompt || renv_verbose()) { bulletin("The following directories will be removed:", bad) if (prompt && !proceed()) cancel() } # nocov end unlink(bad, recursive = TRUE) TRUE } # remove user packages in system library renv_clean_system_library <- function(project, prompt) { ntd <- function() { writef("- No non-system packages were discovered in the system library.") FALSE } # explicitly query for packages syslib <- renv_path_normalize(renv_libpaths_system()) db <- installed_packages(lib.loc = syslib, priority = "NA") packages <- setdiff(db$Package, "translations") # also look for leftover package folders # (primarily for Windows, where .dlls from old packages can be left behind) # nocov start if (renv_platform_windows()) { folders <- list.files(syslib, full.names = TRUE) descpaths <- file.path(folders, "DESCRIPTION") missing <- !file.exists(descpaths) packages <- union(packages, basename(folders)[missing]) } # nocov end # check for any packages needing removal if (empty(packages)) return(ntd()) # nocov start if (prompt || renv_verbose()) { bulletin( "The following non-system packages are installed in the system library:", packages, c( "Normally, only packages distributed with R should be installed in the system library.", "These packages will be removed.", "If necessary, consider reinstalling these packages in your site library." ) ) if (prompt && !proceed()) cancel() } # nocov end remove(packages, library = syslib) TRUE } renv_clean_unused_packages <- function(project, prompt) { ntd <- function() { writef("- No unused packages were found in the project library.") FALSE } # find packages installed in the project library library <- renv_paths_library(project = project) installed <- list.files(library, pattern = renv_regexps_package_name()) if (empty(installed)) return(ntd()) # ignore 'pak' if we're configured to use it installed <- setdiff(installed, if (config$pak.enabled()) "pak") # find packages used in the project and their recursive dependencies packages <- renv_snapshot_dependencies(project, dev = TRUE) paths <- renv_package_dependencies(packages, project = project) packages <- names(paths) # figure out which packages aren't needed removable <- renv_vector_diff(installed, packages) if (empty(removable)) return(ntd()) # nocov start if (prompt || renv_verbose()) { bulletin( c( "The following packages are installed in the project library,", "but appear to be no longer used in your project." ), removable, "These packages will be removed." ) if (prompt && !proceed()) cancel() } # nocov end remove(removable, library = library) return(TRUE) } renv_clean_package_locks <- function(project, prompt) { ntd <- function() { writef("- No stale package locks were found.") FALSE } # find 00LOCK directories in library library <- renv_paths_library(project = project) lock <- list.files(path = library, pattern = "^00LOCK", full.names = TRUE) if (empty(lock)) return(ntd()) # check to see which are old now <- Sys.time() mtime <- file.mtime(lock) mtime[is.na(mtime)] <- now diff <- difftime(now, mtime, units = "secs") old <- lock[diff > 120] if (empty(old)) return(ntd()) # nocov start if (prompt || renv_verbose()) { bulletin( "The following stale package locks were discovered in your library:", basename(old), "These locks will be removed." ) if (prompt && !proceed()) cancel() } # nocov end unlink(old, recursive = TRUE) TRUE } # nocov start renv_clean_cache <- function(project, prompt) { ntd <- function() { writef("- No unused packages were found in the renv cache.") FALSE } # find projects monitored by renv projects <- renv_paths_root("projects") projlist <- character() if (file.exists(projects)) projlist <- readLines(projects, warn = FALSE, encoding = "UTF-8") # inform user if any projects are missing missing <- !file.exists(projlist) if (any(missing)) { bulletin( "The following projects are monitored by renv, but no longer exist:", projlist[missing], "These projects will be removed from renv's project list." ) if (prompt && !proceed()) cancel() writeLines(projlist[!missing], con = projects, useBytes = TRUE) } action <- function(project) { library <- renv_paths_library(project = project) packages <- list.files(library, full.names = TRUE) descs <- file.path(packages, "DESCRIPTION") existing <- file.exists(descs) map_chr(descs[existing], renv_cache_path, USE.NAMES = FALSE) } # for each project, find packages used in their renv private library, # and look for entries in the cache projlist <- projlist[!missing] callback <- renv_progress_callback(action, length(projlist)) used <- uapply(projlist, callback) # check what packages are actually available in the cache available <- renv_cache_list() diff <- renv_vector_diff(available, used) if (empty(diff)) return(ntd()) if (prompt || renv_verbose()) { bulletin( "The following packages are installed in the cache but no longer used:", renv_cache_format_path(diff), "These packages will be removed." ) if (prompt && !proceed()) cancel() } # remove the directories unlink(diff, recursive = TRUE) renv_cache_clean_empty() writef("- %i package(s) have been removed.", length(diff)) TRUE } # nocov end renv/R/yaml.R0000644000176200001440000000032614731330073012514 0ustar liggesusers renv_yaml_load <- function(text) { yaml::yaml.load( string = text, eval.expr = FALSE, handlers = list( r = function(yaml) { attr(yaml, "type") <- "r" yaml } ) ) } renv/R/sysreqs.R0000644000176200001440000002750014761163114013271 0ustar liggesusers the$sysreqs <- NULL #' R System Requirements #' #' Compute the system requirements (system libraries; operating system packages) #' required by a set of \R packages. #' #' This function relies on the database of package system requirements #' maintained by Posit at , #' as well as the "meta-CRAN" service at . This #' service primarily exists to map the (free-form) `SystemRequirements` field #' used by \R packages to the system packages made available by a particular #' operating system. #' #' As an example, the `curl` R package depends on the `libcurl` system library, #' and declares this with a `SystemRequirements` field of the form: #' #' - libcurl (>= 7.62): libcurl-devel (rpm) or libcurl4-openssl-dev (deb) #' #' This dependency can be satisfied with the following command line invocations #' on different systems: #' #' - Debian: `sudo apt install libcurl4-openssl-dev` #' - Redhat: `sudo dnf install libcurl-devel` #' #' and so `sysreqs("curl")` would help provide the name of the package #' whose installation would satisfy the `libcurl` dependency. #' #' #' @inheritParams renv-params #' #' @param packages A vector of \R package names. When `NULL` #' (the default), the project's package dependencies as reported via #' [renv::dependencies()] are used. #' #' @param local Boolean; should `renv` rely on locally-installed copies of #' packages when resolving system requirements? When `FALSE`, `renv` will #' use to resolve the system requirements #' for these packages. #' #' @param check Boolean; should `renv` also check whether the requires system #' packages appear to be installed on the current system? #' #' @param report Boolean; should `renv` also report the commands which could be #' used to install all of the requisite package dependencies? #' #' @param collapse Boolean; when reporting which packages need to be installed, #' should the report be collapsed into a single installation command? When #' `FALSE` (the default), a separate installation line is printed for each #' required system package. #' #' @param distro The name of the Linux distribution for which system requirements #' should be checked -- typical values are "ubuntu", "debian", and "redhat". #' These should match the distribution names used by the R system requirements #' database. #' #' @examples #' #' \dontrun{ #' #' # report the required system packages for this system #' sysreqs() #' #' # report the required system packages for a specific OS #' sysreqs(platform = "ubuntu") #' #' } #' #' @export sysreqs <- function(packages = NULL, ..., local = FALSE, check = NULL, report = TRUE, distro = NULL, collapse = FALSE, project = NULL) { # allow user to provide additional package names as part of '...' if (!missing(...)) { dots <- list(...) names(dots) <- names(dots) %||% rep.int("", length(dots)) packages <- c(packages, dots[!nzchar(names(dots))]) } # resolve packages packages <- packages %||% { project <- renv_project_resolve(project) deps <- dependencies(project, dev = TRUE) sort(unique(deps$Package)) } # remove 'base' packages base <- installed_packages(priority = "base") packages <- setdiff(packages, base$Package) names(packages) <- packages # set up distro distro <- distro %||% the$distro check <- check %||% identical(distro, the$distro) renv_scope_binding(the, "os", "linux") renv_scope_binding(the, "distro", distro) # compute package records if (local) { lockfile <- renv_lockfile_create(project, dev = TRUE) records <- renv_lockfile_records(lockfile) } else { callback <- renv_progress_callback(renv_sysreqs_crandb, length(packages)) records <- map(packages, callback) } # extract and resolve the system requirements sysreqs <- map(records, `[[`, "SystemRequirements") syspkgs <- map(sysreqs, renv_sysreqs_resolve) # check the package status if possible if (check && renv_platform_linux()) renv_sysreqs_check(sysreqs, prompt = FALSE) # report installation commands if requested if (report) { all <- sort(unique(unlist(syspkgs))) installer <- renv_sysreqs_installer(distro) body <- if (collapse) paste(all, collapse = " ") else all message <- paste("sudo", installer, "-y", body) if (interactive()) { preamble <- "The requisite system packages can be installed with:" bulletin(preamble, message) } else { writeLines(message) } } # return result invisible(syspkgs) } renv_sysreqs_crandb <- function(package) { tryCatch( renv_sysreqs_crandb_impl(package), error = warnify ) } renv_sysreqs_crandb_impl <- function(package) { memoize( key = package, value = renv_sysreqs_crandb_impl_one(package), scope = "sysreqs" ) } renv_sysreqs_crandb_impl_one <- function(package) { url <- paste("https://crandb.r-pkg.org", package, sep = "/") destfile <- tempfile("renv-crandb-", fileext = ".json") download(url, destfile = destfile, quiet = TRUE) renv_json_read(destfile) } renv_sysreqs_resolve <- function(sysreqs, rules = renv_sysreqs_rules()) { matches <- map(sysreqs, renv_sysreqs_match, rules) unlist(matches, use.names = FALSE) } renv_sysreqs_read <- function(package) { desc <- renv_description_read(package) desc[["SystemRequirements"]] %||% "" } renv_sysreqs_rules <- function() { the$sysreqs <- the$sysreqs %||% renv_sysreqs_rules_impl() } renv_sysreqs_rules_impl <- function() { rules <- system.file("sysreqs/sysreqs.json", package = "renv") renv_json_read(rules) } renv_sysreqs_match <- function(sysreq, rules = renv_sysreqs_rules()) { map(rules, renv_sysreqs_match_impl, sysreq = sysreq) } renv_sysreqs_match_impl <- function(sysreq, rule) { # check for a match in the declared system requirements pattern <- paste(rule$patterns, collapse = "|") matches <- grepl(pattern, sysreq, ignore.case = TRUE, perl = TRUE) # if we got a match, pull out the dependent packages if (matches) { for (dependency in rule$dependencies) { for (constraint in dependency$constraints) { if (constraint$os == the$os) { if (constraint$distribution == the$distro) { return(dependency$packages) } } } } } } renv_sysreqs_aliases <- function(type, syspkgs) { case( type == "deb" ~ renv_sysreqs_aliases_deb(syspkgs), type == "rpm" ~ renv_sysreqs_aliases_rpm(syspkgs) ) } renv_sysreqs_aliases_deb <- function(pkgs) { # https://www.debian.org/doc/debian-policy/ch-relationships.html#s-virtual # # > A virtual package is one which appears in the Provides control field of # > another package. The effect is as if the package(s) which provide a # > particular virtual package name had been listed by name everywhere the # > virtual package name appears. (See also Virtual packages) # # read the package database, look which packages 'provide' others, # and then reverse that map to map virtual packages to the concrete # package which provides them # command <- "dpkg-query -W -f '${Package}=${Provides}\n'" output <- system(command, intern = TRUE) result <- renv_properties_read(text = output, delimiter = "=") # keep only packages which provide other packages aliases <- result[nzchar(result)] # a package might provide multiple other packages, so split those splat <- lapply(aliases, function(alias) { parts <- strsplit(alias, ",\\s*", perl = TRUE)[[1L]] names(renv_properties_read(text = parts, delimiter = " ")) }) # reverse the map, so that we can map virtual packages to the # concrete packages which they refer to envir <- new.env(parent = emptyenv()) enumerate(splat, function(package, virtuals) { for (virtual in virtuals) { envir[[virtual]] <<- c(envir[[virtual]], package) } }) # convert to intermediate list result <- as.list(envir, all.names = TRUE) # return as named character vector convert(result, type = "character") } renv_sysreqs_aliases_rpm <- function(pkgs) { # for each package, check if there's another package that 'provides' it fmt <- "rpm --query --whatprovides %s --queryformat '%%{Name}\n'" args <- paste(renv_shell_quote(pkgs), collapse = " ") command <- sprintf(fmt, args) result <- suppressWarnings(system(command, intern = TRUE)) # return as named vector, mapping virtual packages to 'real' packages matches <- grep("no package provides", result, fixed = TRUE, invert = TRUE) aliases <- result[matches] names(aliases) <- pkgs[matches] convert(aliases, type = "character") } renv_sysreqs_check <- function(sysreqs, prompt) { type <- case( nzchar(Sys.which("dpkg")) ~ "deb", nzchar(Sys.which("rpm")) ~ "rpm", ~ stop("don't know how to check sysreqs on this system") ) # figure out which system packages are required syspkgs <- map(sysreqs, renv_sysreqs_resolve) # collect list of all packages discovered allsyspkgs <- sort(unique(unlist(syspkgs, use.names = FALSE))) # some packages might be virtual packages, and won't be reported as installed # when queried. try to resolve those to the actual underlying packages. # some examples follows: # # Fedora 41: zlib-devel => zlib-ng-compat-devel # Ubuntu 24.04: libfreetype6-dev => libfreetype-dev # aliases <- renv_sysreqs_aliases(type, allsyspkgs) resolvedpkgs <- alias(allsyspkgs, aliases) # list all currently-installed packages installedpkgs <- case( type == "deb" ~ system("dpkg-query -W -f '${Package}\n'", intern = TRUE), type == "rpm" ~ system("rpm --query --all --queryformat='%{Name}\n'", intern = TRUE) ) # check for matches misspkgs <- setdiff(resolvedpkgs, installedpkgs) if (empty(misspkgs)) return(TRUE) # notify the user preamble <- "The following required system packages are not installed:" postamble <- "The R packages depending on these system packages may fail to install." parts <- map(misspkgs, function(misspkg) { needs <- map_lgl(syspkgs, function(syspkg) misspkg %in% syspkg) list(misspkg, names(syspkgs)[needs]) }) lhs <- extract_chr(parts, 1L) rhs <- map_chr(extract(parts, 2L), paste, collapse = ", ") messages <- sprintf("%s [required by %s]", format(lhs), rhs) bulletin(preamble, messages, postamble) installer <- case( nzchar(Sys.which("apt")) ~ "apt install", nzchar(Sys.which("dnf")) ~ "dnf install", nzchar(Sys.which("pacman")) ~ "pacman -S", nzchar(Sys.which("yum")) ~ "yum install", nzchar(Sys.which("zypper")) ~ "zypper install", ) preamble <- "An administrator can install these packages with:" command <- paste("sudo", installer, paste(misspkgs, collapse = " ")) bulletin(preamble, command) cancel_if(prompt && !proceed()) } renv_sysreqs_installer <- function(distro) { case( distro == "debian" ~ "apt install", distro == "redhat" ~ "dnf install", distro == "ubuntu" ~ "apt install", ~ "" ) } renv_sysreqs_update <- function() { # save path to sysreqs folder dest <- renv_path_normalize("inst/sysreqs/sysreqs.json") # move to temporary directory renv_scope_tempdir() # clone the system requirements repository args <- c("clone", "--depth", "1", "https://github.com/rstudio/r-system-requirements") renv_system_exec("git", args, action = "cloing rstudio/r-system-requirements") # read all of the rules from the requirements repository files <- list.files( path = "r-system-requirements/rules", pattern = "[.]json$", full.names = TRUE ) contents <- map(files, renv_json_read) # give names without extensions for these files names <- basename(files) idx <- map_int(gregexpr(".", names, fixed = TRUE), tail, n = 1L) names(contents) <- substr(names, 1L, idx - 1L) # write to sysreqs.json renv_json_write(contents, file = dest) } renv/R/metadata.R0000644000176200001440000000325514731330073013336 0ustar liggesusers # NOTE: 'the$metadata' is initialized either in 'renv_metadata_init()', for # stand-alone installations of renv, or via an embedded initialize script for # vendored copies of renv. renv_metadata_create <- function(embedded, version) { list(embedded = embedded, version = version) } renv_metadata_embedded <- function() { the$metadata$embedded } renv_metadata_version <- function() { the$metadata$version } renv_metadata_version_create <- function(record) { # get package version version <- record[["Version"]] # tag with RemoteSha if renv was installed from GitHub if ("github" %in% record[["RemoteType"]]) attr(version, "sha") <- record[["RemoteSha"]] # return version version } renv_metadata_remote <- function(metadata = the$metadata) { # check for development versions sha <- attr(metadata$version, "sha") if (!is.null(sha) && nzchar(sha)) return(paste("rstudio/renv", sha, sep = "@")) # otherwise, use release version paste("renv", metadata$version, sep = "@") } renv_metadata_version_friendly <- function(metadata = the$metadata, shafmt = NULL) { renv_bootstrap_version_friendly( version = metadata$version, shafmt = shafmt ) } renv_metadata_init <- function() { # if renv was embedded, then the$metadata should already be initialized if (!is.null(the$metadata)) return() # renv doesn't appear to be embedded; initialize metadata path <- renv_namespace_path("renv") record <- renv_description_read(path = file.path(path, "DESCRIPTION")) version <- renv_metadata_version_create(record) the$metadata <- renv_metadata_create( embedded = FALSE, version = version ) } renv/R/python.R0000644000176200001440000002511214731330073013073 0ustar liggesusers renv_python_resolve <- function(python = NULL) { # if Python was explicitly supplied, use it if (!is.null(python)) { resolved <- Sys.which(renv_path_canonicalize(python)) if (nzchar(resolved)) return(resolved) stopf("'%s' does not refer to a valid python interpreter", python) } # in interactive sessions, ask user what version of python they'd like to use if (interactive()) { python <- renv_python_select() fmt <- "- Selected %s [Python %s]." writef(fmt, renv_path_pretty(python), renv_python_version(python)) return(path.expand(python)) } # check environment variables envvars <- c("RETICULATE_PYTHON", "RETICULATE_PYTHON_ENV") for (envvar in envvars) { val <- Sys.getenv(envvar, unset = NA) if (!is.na(val) && file.exists(val)) return(val) } # check on the PATH (prefer Python 3) for (binary in c("python3", "python")) { python <- Sys.which(binary) if (nzchar(python)) return(python) } stopf("could not locate Python (not available on the PATH)") } renv_python_find <- function(version, path = NULL) { renv_python_find_impl(version, path) } renv_python_find_impl <- function(version, path = NULL) { # if we've been given the name of an environment, # check to see if it's already been initialized # and use the associated copy of Python if possible if (!is.null(path) && file.exists(path)) { python <- catch(renv_python_exe(path)) if (!inherits(python, "error")) return(python) } # try to find a compatible version of python pythons <- renv_python_discover() if (length(pythons) == 0) { fmt <- lines( "project requested Python %s, but no compatible Python installation could be found.", "renv's Python integration will be disabled in this session.", "See `?renv::use_python` for more details." ) stopf(fmt, version) } # read python versions pyversions <- map_chr(pythons, function(python) { tryCatch( renv_python_version(python), error = function(e) "0.0.0" ) }) # try to find a compatible version renv_version_match(pyversions, version) } renv_python_exe <- function(path) { # if this already looks like a Python executable, use it directly info <- renv_file_info(path) if (identical(info$isdir, FALSE) && startsWith(basename(path), "python")) return(renv_path_canonicalize(path)) # otherwise, attempt to infer the Python executable type info <- renv_python_info(path) if (!is.null(info$python)) return(renv_path_canonicalize(info$python)) fmt <- "failed to find Python executable associated with path %s" stopf(fmt, renv_path_pretty(path)) } renv_python_version <- function(python) { filebacked( context = "renv_python_version", path = renv_path_normalize(python), callback = renv_python_version_impl ) } renv_python_version_impl <- function(python) { python <- renv_path_canonicalize(python) code <- "from platform import python_version; print(python_version())" args <- c("-c", shQuote(code)) action <- "reading Python version" renv_system_exec(python, args, action) } renv_python_info <- function(python) { found <- renv_file_find(python, function(path) { # check for virtual environment files virtualenv <- file.exists(file.path(path, "pyvenv.cfg")) || file.exists(file.path(path, ".Python")) || file.exists(file.path(path, "bin/activate_this.py")) if (virtualenv) { suffix <- if (renv_platform_windows()) "Scripts/python.exe" else "bin/python" python <- file.path(path, suffix) return(list(python = python, type = "virtualenv", root = path)) } # check for conda-meta condaenv <- file.exists(file.path(path, "conda-meta")) && !file.exists(file.path(path, "condabin")) if (condaenv) { suffix <- if (renv_platform_windows()) "python.exe" else "bin/python" python <- file.path(path, suffix) return(list(python = python, type = "conda", root = path)) } }) if (!is.null(found)) return(found) if (file.exists(python)) list(python = python, type = "system", root = python) } renv_python_type <- function(python) { info <- renv_python_info(python) info$type } renv_python_action <- function(action, prompt, project) { python <- Sys.getenv("RENV_PYTHON", unset = NA) if (is.na(python) || !file.exists(python)) return(NULL) type <- renv_python_type(python) if (is.null(type)) return(NULL) if (type == "conda" && !requireNamespace("reticulate", quietly = TRUE)) return(NULL) action(python, type, prompt, project) } renv_python_snapshot <- function(project, prompt) { renv_python_action( renv_python_snapshot_impl, prompt = prompt, project = project ) } renv_python_snapshot_impl <- function(python, type, prompt, project) { switch(type, virtualenv = renv_python_virtualenv_snapshot(project, prompt, python), conda = renv_python_conda_snapshot(project, prompt, python) ) } renv_python_restore <- function(project, prompt) { renv_python_action( renv_python_restore_impl, prompt = prompt, project = project ) } renv_python_restore_impl <- function(python, type, prompt, project) { case( type == "virtualenv" ~ renv_python_virtualenv_restore(project, prompt, python), type == "conda" ~ renv_python_conda_restore(project, prompt, python) ) } renv_python_envpath_virtualenv <- function(version) { sprintf("python/virtualenvs/renv-python-%s", renv_version_maj_min(version)) } renv_python_envpath_condaenv <- function(version) { "python/condaenvs/renv-python" } renv_python_envpath <- function(project, type, version = NULL) { suffix <- case( type == "virtualenv" ~ renv_python_envpath_virtualenv(version), type == "conda" ~ renv_python_envpath_condaenv(version), ~ stopf("internal error: unrecognized environment type '%s'", type) ) renv_paths_renv(suffix, project = project) } renv_python_envname <- function(project, path, type) { # check for a project-local environment if (renv_path_within(path, project)) { stem <- substring(path, nchar(project) + 2L) path <- paste(".", stem, sep = "/") return(path) } bn <- basename(path) # check for file within virtualenv ok <- type == "virtualenv" && identical(renv_python_virtualenv_path(bn), path) if (ok) return(bn) # check for named conda environment ok <- type == "conda" && bn %in% reticulate::conda_list()$name if (ok) return(bn) # doesn't match any known named environments; return full path path } renv_python_discover <- function() { all <- stack() # find python in some pre-determined root directories roots <- c( getOption("renv.python.root"), Sys.getenv("WORKON_HOME", "~/.virtualenvs"), "/opt/python", "/opt/local/python", "~/opt/python", file.path(renv_pyenv_root(), "versions") ) for (root in roots) { versions <- sort(list.files(root, full.names = TRUE), decreasing = TRUE) exts <- if (renv_platform_windows()) "Scripts/python.exe" else "bin/python" pythons <- file.path(versions, exts) all$push(pythons) } # find Homebrew python if (renv_platform_macos()) { homebrew <- renv_homebrew_root() roots <- sort(list.files( path = file.path(homebrew, "opt"), pattern = "^python@[[:digit:]]+[.][[:digit:]]+$", full.names = TRUE ), decreasing = TRUE) for (root in roots) { # homebrew python doesn't install bin/python, so we need # to be a little bit more clever here exes <- list.files( path = file.path(root, "bin"), pattern = "^python[[:digit:]]+[.][[:digit:]]+$", full.names = TRUE ) if (length(exes)) all$push(exes[[1L]]) } } # find Windows python installations if (renv_platform_windows()) { sd <- Sys.getenv("SYSTEMDRIVE", unset = "C:") roots <- file.path(sd, c("", "Program Files")) lad <- Sys.getenv("LOCALAPPDATA", unset = NA) if (!is.na(lad)) roots <- c(roots, file.path(lad, "Programs/Python")) dirs <- list.files( path = roots, pattern = "^Python", full.names = TRUE ) if (length(dirs)) { exes <- file.path(dirs, "python.exe") pythons <- renv_path_normalize(exes) all$push(pythons) } } # find Python installations on the PATH path <- Sys.getenv("PATH", unset = "") splat <- strsplit(path, .Platform$path.sep, fixed = TRUE)[[1L]] for (entry in splat) { for (exe in c("python3", "python")) { python <- Sys.which(file.path(entry, exe)) if (nzchar(python)) all$push(python) } } # collect discovered pythons as vector pythons <- unlist(all$data(), recursive = FALSE, use.names = TRUE) # don't include /usr/bin/python on macOS (too old) if (renv_platform_macos()) pythons <- setdiff(pythons, "/usr/bin/python") # get list of pythons pythons <- renv_path_canonicalize(pythons[file.exists(pythons)]) # don't include WindowsApps if (renv_platform_windows()) pythons <- grep("/WindowsApps/", pythons, invert = TRUE, value = TRUE) unique(pythons) } renv_python_select_error <- function() { lines <- c( "renv was unable to find any Python installations on your machine.", if (renv_platform_windows()) "Consider installing Python from https://www.python.org/downloads/windows/.", if (renv_platform_macos()) "Consider installing Python from https://www.python.org/downloads/mac-osx/." ) stop(paste(lines, collapse = "\n")) } renv_python_select <- function(candidates = NULL) { candidates <- renv_path_aliased(candidates %||% renv_python_discover()) if (empty(candidates)) return(renv_python_select_error()) title <- "Please select a version of Python to use with this project:" selection <- tryCatch( utils::select.list(candidates, title = title, graphics = FALSE), interrupt = identity ) if (selection %in% "" || inherits(selection, "interrupt")) stop("operation canceled by user") return(path.expand(selection)) } renv_python_module_available <- function(python, module) { python <- renv_path_canonicalize(python) command <- paste("import", module) args <- c("-c", shQuote(command)) status <- system2(python, args, stdout = FALSE, stderr = FALSE) identical(status, 0L) } renv_python_active <- function() { python <- Sys.getenv("RENV_PYTHON", unset = NA) if (is.na(python)) stop("internal error: RENV_PYTHON is not set") renv_python_validate(python) } renv_python_validate <- function(python) { if (!file.exists(python)) { fmt <- "python %s does not exist" stopf(fmt, renv_path_pretty(python)) } invisible(python) } renv/R/tests.R0000644000176200001440000000735714761163114012732 0ustar liggesusers the$tests_root <- NULL # NOTE: Prefer using 'testing()' to 'renv_tests_running()' for behavior # that should apply regardless of the package currently being tested. # # renv_tests_running() is appropriate when running renv's own tests. renv_tests_running <- function() { getOption("renv.tests.running", default = FALSE) } renv_test_code <- function(code, data = list(), fileext = ".R", scope = parent.frame()) { code <- do.call(substitute, list(substitute(code), data)) file <- renv_scope_tempfile("renv-code-", fileext = fileext, scope = scope) writeLines(deparse(code), con = file) file } renv_test_retrieve <- function(record) { renv_scope_error_handler() # avoid using cache cache <- renv_scope_tempfile() renv_scope_envvars(RENV_PATHS_CACHE = cache) # construct records package <- record$Package records <- list(record) names(records) <- package # prepare dummy library templib <- renv_scope_tempfile("renv-library-") ensure_directory(templib) renv_scope_libpaths(c(templib, .libPaths())) # attempt a restore into that library renv_scope_restore( project = getwd(), library = templib, records = records, packages = package, recursive = TRUE ) records <- renv_retrieve_impl(record$Package) renv_install_impl(records) descpath <- file.path(templib, package) if (!file.exists(descpath)) stopf("failed to retrieve package '%s'", package) desc <- renv_description_read(descpath) fields <- grep("^Remote", names(record), value = TRUE) testthat::expect_identical( as.list(desc[fields]), as.list(record[fields]) ) } renv_tests_diagnostics <- function() { # print library paths bulletin( "The following R libraries are set:", paste("-", .libPaths()) ) # print repositories repos <- getOption("repos") bulletin( "The following repositories are set:", paste(names(repos), repos, sep = ": ") ) # print renv root bulletin( "The following renv root directory is being used:", paste("-", paths$root()) ) # print cache root bulletin( "The following renv cache directory is being used:", paste("-", paths$cache()) ) writeLines("The following packages are available in the test repositories:") dbs <- available_packages(type = "source", quiet = TRUE) %>% map(function(db) { rownames(db) <- NULL db[c("Package", "Version", "File")] }) print(dbs) path <- Sys.getenv("PATH") splat <- strsplit(path, .Platform$path.sep, fixed = TRUE)[[1]] bulletin( "The following PATH is set:", paste("-", splat) ) envvars <- c( grep("^_R_", names(Sys.getenv()), value = TRUE), "HOME", "R_ARCH", "R_HOME", "R_LIBS", "R_LIBS_SITE", "R_LIBS_USER", "R_USER", "R_ZIPCMD", "TAR", "TEMP", "TMP", "TMPDIR" ) keys <- format(envvars) vals <- Sys.getenv(envvars, unset = "") vals[vals != ""] <- renv_json_quote(vals[vals != ""]) bulletin( "The following environment variables of interest are set:", paste(keys, vals, sep = " : ") ) } renv_tests_root <- function() { the$tests_root <- the$tests_root %||% { renv_path_normalize(testthat::test_path(".")) } } renv_tests_path <- function(path = NULL) { # special case for NULL path if (is.null(path)) return(renv_tests_root()) # otherwise, form path from root file.path(renv_tests_root(), path) } renv_tests_supported <- function() { # supported when running locally + on CI for (envvar in c("NOT_CRAN", "CI")) if (renv_envvar_exists(envvar)) return(TRUE) # disabled on older macOS releases (credentials fails to load) if (renv_platform_macos() && getRversion() < "4.0.0") return(FALSE) # disabled on Windows if (renv_platform_windows()) return(FALSE) # true otherwise TRUE } renv/R/aliases.R0000644000176200001440000000110514761163114013172 0ustar liggesusers # aliases used primarily for nicer / normalized text output the$aliases <- c( bioc = "Bioconductor", bioconductor = "Bioconductor", bitbucket = "Bitbucket", cellar = "Cellar", cran = "CRAN", git2r = "Git", github = "GitHub", gitlab = "GitLab", local = "Local", repository = "Repository", standard = "Repository", url = "URL", xgit = "Git" ) alias <- function(text, aliases = the$aliases) { matches <- text %in% names(aliases) text[matches] <- aliases[text[matches]] text } renv/R/pyenv.R0000644000176200001440000000037114731330073012713 0ustar liggesusers renv_pyenv_root <- function() { root <- Sys.getenv("PYENV_ROOT", unset = renv_pyenv_root_default()) path.expand(root) } renv_pyenv_root_default <- function() { if (renv_platform_windows()) "~/.pyenv/pyenv-win" else "~/.pyenv" } renv/R/rebuild.R0000644000176200001440000000476614731330073013214 0ustar liggesusers #' Rebuild the packages in your project library #' #' Rebuild and reinstall packages in your library. This can be useful as a #' diagnostic tool -- for example, if you find that one or more of your #' packages fail to load, and you want to ensure that you are starting from a #' clean slate. #' #' @inherit renv-params #' #' @param packages The package(s) to be rebuilt. When `NULL`, all packages #' in the library will be reinstalled. #' #' @param recursive Boolean; should dependencies of packages be rebuilt #' recursively? Defaults to `TRUE`. #' #' @return A named list of package records which were installed by renv. #' #' @export #' #' @examples #' \dontrun{ #' #' # rebuild the 'dplyr' package + all of its dependencies #' renv::rebuild("dplyr", recursive = TRUE) #' #' # rebuild only 'dplyr' #' renv::rebuild("dplyr", recursive = FALSE) #' #' } rebuild <- function(packages = NULL, recursive = TRUE, ..., type = NULL, prompt = interactive(), library = NULL, project = NULL) { renv_consent_check() renv_scope_error_handler() renv_dots_check(...) project <- renv_project_resolve(project) renv_project_lock(project = project) renv_scope_verbose_if(prompt) libpaths <- renv_libpaths_resolve(library) library <- nth(libpaths, 1L) # get collection of packages currently installed records <- renv_snapshot_libpaths(libpaths = libpaths, project = project) packages <- setdiff(packages %||% names(records), "renv") # add in missing packages for (package in packages) { records[[package]] <- records[[package]] %||% renv_available_packages_latest(package) } # make sure records are named names(records) <- map_chr(records, `[[`, "Package") if (empty(records)) { writef("- There are no packages currently installed -- nothing to rebuild.") return(invisible(records)) } # apply any overrides records <- renv_records_override(records) # notify the user preamble <- if (recursive) "The following package(s) and their dependencies will be reinstalled:" else "The following package(s) will be reinstalled:" renv_pretty_print_records(preamble, records[packages]) cancel_if(prompt && !proceed()) # figure out rebuild parameter rebuild <- if (recursive) NA else packages # perform the install install( packages = records[packages], library = libpaths, type = type, rebuild = rebuild, project = project ) } renv/R/job.R0000644000176200001440000000335214746262030012331 0ustar liggesusers job <- function(callback, data = list()) { # unquote things in the callback body(callback) <- renv_expr_impl(body(callback), envir = parent.frame()) # set up job directory jobdir <- tempfile("renv-job-") ensure_directory(jobdir) # set up paths paths <- list( options = file.path(jobdir, "options.rds"), workspace = file.path(jobdir, "workspace.Rdata"), script = file.path(jobdir, "script.R"), result = file.path(jobdir, "result.rds") ) # save options names <- list("download.file.method", "download.file.extra", "pkgType", "repos") saveRDS(do.call(options, names), file = paths$options) # save callback and data save(callback, data, file = paths$workspace) # find path where renv is installed library <- if (devmode() || testing()) { dirname(renv_package_find("renv")) } else { dirname(renv_namespace_path("renv")) } # create a script that will load this data and run it code <- expr({ # load renv, and make internal functions visible renv <- loadNamespace("renv", lib.loc = !!library) renv$summon() # invoke the provided callback result <- catch({ options(readRDS(!!paths$options)) base::load(!!paths$workspace) do.call(callback, data) }) # write result to file saveRDS(result, file = !!paths$result) }) # write code to script writeLines(deparse(code), con = paths$script) # run that code renv_scope_envvars(RENV_WATCHDOG_ENABLED = FALSE) args <- c("--vanilla", "-s", "-f", renv_shell_path(paths$script)) status <- r(args) if (status != 0L) stopf("error executing job [error code %i]", status) # collect the result result <- readRDS(paths$result) if (inherits(result, "error")) stop(result) result } renv/R/envvars.R0000644000176200001440000000365314731330072013243 0ustar liggesusers renv_envvars_list <- function() { c( "R_PROFILE", "R_PROFILE_USER", "R_ENVIRON", "R_ENVIRON_USER", "R_LIBS_USER", "R_LIBS_SITE", "R_LIBS" ) } renv_envvars_save <- function() { # save the common set of environment variables keys <- renv_envvars_list() vals <- Sys.getenv(keys, unset = "") # check for defaults that have already been set defkeys <- paste("RENV_DEFAULT", keys, sep = "_") defvals <- Sys.getenv(defkeys, unset = NA) if (any(!is.na(defvals))) return(FALSE) # prepare defaults env <- vals names(env) <- defkeys do.call(Sys.setenv, as.list(env)) TRUE } renv_envvars_restore <- function() { # read defaults keys <- renv_envvars_list() defkeys <- paste("RENV_DEFAULT", renv_envvars_list(), sep = "_") defvals <- Sys.getenv(defkeys, unset = "") # remove previously-unset environment variables missing <- defvals == "" Sys.unsetenv(keys[missing]) # restore old values for envvars existing <- as.list(defvals[!missing]) if (length(existing)) { names(existing) <- sub("^RENV_DEFAULT_", "", names(existing)) do.call(Sys.setenv, existing) } # remove saved RENV_DEFAULT values Sys.unsetenv(defkeys) TRUE } renv_envvars_init <- function() { renv_envvars_normalize() } renv_envvars_normalize <- function() { Sys.setenv(R_LIBS_SITE = .expand_R_libs_env_var(Sys.getenv("R_LIBS_SITE"))) Sys.setenv(R_LIBS_USER = .expand_R_libs_env_var(Sys.getenv("R_LIBS_USER"))) keys <- c( "RENV_PATHS_ROOT", "RENV_PATHS_LIBRARY", "RENV_PATHS_LIBRARY_ROOT", "RENV_PATHS_LIBRARY_STAGING", "RENV_PATHS_LOCAL", "RENV_PATHS_CELLAR", "RENV_PATHS_SOURCE", "RENV_PATHS_BINARY", "RENV_PATHS_CACHE", "RENV_PATHS_RTOOLS", "RENV_PATHS_EXTSOFT", "RENV_PATHS_MRAN" ) envvars <- as.list(keep(Sys.getenv(), keys)) if (empty(envvars)) return() args <- lapply(envvars, renv_path_normalize) do.call(Sys.setenv, args) } renv/R/upgrade.R0000644000176200001440000001452114761167213013212 0ustar liggesusers #' Upgrade renv #' #' @description #' Upgrade the version of renv associated with a project, including using #' a development version from GitHub. Automatically snapshots the updated #' renv, updates the activate script, and restarts R. #' #' If you want to update all packages (including renv) to their latest CRAN #' versions, use [renv::update()]. #' #' # Note #' #' `upgrade()` is expected to work for renv versions >= 1.0.1. #' To upgrade from prior versions of renv, users should #' #' `renv::deactivate();` #' `install.packages("renv");` #' `renv::activate();` #' `renv::record("renv")` #' #' @inherit renv-params #' #' @param version The version of renv to be installed. #' #' When `NULL` (the default), the latest version of renv will be installed as #' available from CRAN (or whatever active package repositories are active) #' Alternatively, you can install the latest development version with #' `"main"`, or a specific commit with a SHA, e.g. `"5049cef8a"`. #' #' @param prompt Boolean; prompt upgrade before proceeding? #' #' @param reload Boolean; reload renv after install? When `NULL` (the #' default), renv will be re-loaded only if updating renv for the #' active project. Since it's not possible to guarantee a clean reload #' in the current session, this will attempt to restart your R session. #' #' @return A boolean value, indicating whether the requested version of #' renv was successfully installed. Note that this function is normally #' called for its side effects. #' #' @export #' #' @examples #' \dontrun{ #' #' # upgrade to the latest version of renv #' renv::upgrade() #' #' # upgrade to the latest version of renv on GitHub (development version) #' renv::upgrade(version = "main") #' #' } upgrade <- function(project = NULL, version = NULL, reload = NULL, prompt = interactive()) { renv_scope_error_handler() renv_scope_verbose_if(prompt) invisible(renv_upgrade_impl(project, version, reload, prompt)) } renv_upgrade_impl <- function(project, version, reload, prompt) { project <- renv_project_resolve(project) renv_project_lock(project = project) reload <- reload %||% renv_project_loaded(project) lockfile <- renv_lockfile_load(project) old <- lockfile$Packages$renv new <- renv_upgrade_find_record(version) # check for some form of change if (renv_records_equal(old, new)) { fmt <- "- renv [%s] is already installed and active for this project." writef(fmt, renv_metadata_version_friendly()) return(FALSE) } if (prompt || renv_verbose()) { renv_pretty_print_records_pair( "A new version of the renv package will be installed:", list(renv = old), list(renv = new), "This project will use the newly-installed version of renv." ) } cancel_if(prompt && !proceed()) renv_scope_restore( project = project, library = renv_libpaths_active(), records = list(renv = new), packages = "renv", recursive = FALSE ) # retrieve and install renv records <- renv_retrieve_impl("renv") renv_install_impl(records) # update the lockfile lockfile <- renv_lockfile_load(project = project) records <- renv_lockfile_records(lockfile) %||% list() records$renv <- new renv_lockfile_records(lockfile) <- records renv_lockfile_save(lockfile, project = project) # now update the infrastructure to use this version of renv. # do this in a separate process to avoid issues that could arise # if the old version of renv is still loaded # # https://github.com/rstudio/renv/issues/1546 writef("- Updating activate script") record <- records[["renv"]] # make sure we forward renv.config.autoloader.enabled if set # https://github.com/rstudio/renv/issues/2027 autoload <- config$autoloader.enabled() renv_scope_envvars(RENV_CONFIG_AUTOLOADER_ENABLED = autoload) code <- expr({ renv <- asNamespace("renv"); renv$summon() renv_infrastructure_write( project = !!project, version = !!renv_metadata_version_create(record) ) }) script <- renv_scope_tempfile("renv-activate-", fileext = ".R") writeLines(deparse(code), con = script) args <- c("--vanilla", "-s", "-f", renv_shell_path(script)) r(args, stdout = FALSE, stderr = FALSE) if (reload) { renv_restart_request(project) } invisible(TRUE) } renv_upgrade_find_record <- function(version) { if (is.null(version)) renv_upgrade_find_record_default() else renv_upgrade_find_record_dev(version) } renv_upgrade_find_record_default <- function() { # check if the package is available on R repositories. # if not, prefer GitHub record <- catch(renv_available_packages_latest("renv")) if (inherits(record, "error")) return(renv_upgrade_find_record_dev()) # check the version reported by R repositories. # if it's older than current renv, then prefer GitHub version <- record$Version if (package_version(version) < renv_package_version("renv")) return(renv_upgrade_find_record_dev()) # ok -- install from repository record } renv_upgrade_find_record_dev <- function(version = NULL) { version <- version %||% renv_upgrade_find_record_dev_latest() entry <- paste("rstudio/renv", version, sep = "@") renv_remotes_resolve(entry) } renv_upgrade_find_record_dev_latest <- function() { # download tags url <- "https://api.github.com/repos/rstudio/renv/tags" destfile <- tempfile("renv-tags-", fileext = ".json") download(url, destfile = destfile, quiet = TRUE) json <- renv_json_read(destfile) # find latest version names <- extract_chr(json, "name") versions <- numeric_version(names, strict = FALSE) latest <- sort(versions, decreasing = TRUE)[[1]] names[versions %in% latest][[1L]] } renv_upgrade_reload <- function() { # we need to remove the task callbacks here, as otherwise # we'll run into trouble trying to remove task callbacks # within a task callback renv_task_unload() # now define and add a callback to reload renv; use the base namespace # to avoid carrying along any bits of the current renv environment callback <- function(...) { unloadNamespace("renv") loadNamespace("renv") invisible(FALSE) } environment(callback) <- baseenv() # add the task callback; don't name it so that the renv infrastructure # doesn't try to remove this callback (it'll resolve and remove itself) addTaskCallback(callback) invisible(TRUE) } renv/R/use-python.R0000644000176200001440000003507414731330073013675 0ustar liggesusers #' Use python #' #' Associate a version of Python with your project. #' #' When Python integration is active, renv will: #' #' - Save metadata about the requested version of Python in `renv.lock` -- in #' particular, the Python version, and the Python type ("virtualenv", "conda", #' "system"), #' #' - Capture the set of installed Python packages during `renv::snapshot()`, #' #' - Re-install the set of recorded Python packages during `renv::restore()`. #' #' In addition, when the project is loaded, the following actions will be taken: #' #' - The `RENV_PYTHON` environment variable will be set, indicating the version #' of Python currently active for this sessions, #' #' - The `RETICULATE_PYTHON` environment variable will be set, so that the #' reticulate package can automatically use the requested copy of Python #' as appropriate, #' #' - The requested version of Python will be placed on the `PATH`, so that #' attempts to invoke Python will resolve to the expected version of Python. #' #' You can override the version of Python used in a particular project by #' setting the `RENV_PYTHON` environment variable; e.g. as part of the #' project's `.Renviron` file. This can be useful if you find that renv #' is unable to automatically discover a compatible version of Python to #' be used in the project. #' #' @inherit renv-params #' #' @param ... Optional arguments; currently unused. #' #' @param python #' The path to the version of Python to be used with this project. See #' **Finding Python** for more details. #' #' @param type #' The type of Python environment to use. When `"auto"` (the default), #' virtual environments will be used. #' #' @param name #' The name or path that should be used for the associated Python environment. #' If `NULL` and `python` points to a Python executable living within a #' pre-existing virtual environment, that environment will be used. Otherwise, #' a project-local environment will be created instead, using a name #' generated from the associated version of Python. #' #' @details #' # Finding Python #' #' In interactive sessions, when `python = NULL`, renv will prompt for an #' appropriate version of Python. renv will search a pre-defined set of #' locations when attempting to find Python installations on the system: #' #' - `getOption("renv.python.root")`, #' - `/opt/python`, #' - `/opt/local/python`, #' - `~/opt/python`, #' - `/usr/local/opt` (for macOS Homebrew-installed copies of Python), #' - `/opt/homebrew/opt` (for M1 macOS Homebrew-installed copies of Python), #' - `~/.pyenv/versions`, #' - Python instances available on the `PATH`. #' #' In non-interactive sessions, renv will first check the `RETICULATE_PYTHON` #' environment variable; if that is unset, renv will look for Python on the #' `PATH`. It is recommended that the version of Python to be used is explicitly #' supplied for non-interactive usages of `use_python()`. #' #' #' # Warning #' #' We strongly recommend using Python virtual environments, for a few reasons: #' #' 1. If something goes wrong with a local virtual environment, you can safely #' delete that virtual environment, and then re-initialize it later, without #' worry that doing so might impact other software on your system. #' #' 2. If you choose to use a "system" installation of Python, then any packages #' you install or upgrade will be visible to any other application that #' wants to use that same Python installation. Using a virtual environment #' ensures that any changes made are isolated to that environment only. #' #' 3. Choosing to use Anaconda will likely invite extra frustration in the #' future, as you may be required to upgrade and manage your Anaconda #' installation as new versions of Anaconda are released. In addition, #' Anaconda installations tend to work poorly with software not specifically #' installed as part of that same Anaconda installation. #' #' In other words, we recommend selecting "system" or "conda" only if you are an #' expert Python user who is already accustomed to managing Python / Anaconda #' installations on your own. #' #' #' @return #' `TRUE`, indicating that the requested version of Python has been #' successfully activated. Note that this function is normally called for its #' side effects. #' #' #' @export #' #' @examples #' \dontrun{ #' #' # use python with a project #' renv::use_python() #' #' # use python with a project; create the environment #' # within the project directory in the '.venv' folder #' renv::use_python(name = ".venv") #' #' # use python with a pre-existing virtual environment located elsewhere #' renv::use_python(name = "~/.virtualenvs/env") #' #' # use virtualenv python with a project #' renv::use_python(type = "virtualenv") #' #' # use conda python with a project #' renv::use_python(type = "conda") #' #' } use_python <- function(python = NULL, ..., type = c("auto", "virtualenv", "conda", "system"), name = NULL, project = NULL) { renv_scope_error_handler() renv_dots_check(...) project <- renv_project_resolve(project) # deactivate python integration when FALSE if (identical(python, FALSE)) return(renv_python_deactivate(project)) # handle 'auto' type type <- match.arg(type) if (identical(type, "auto")) type <- "virtualenv" case( type == "system" ~ renv_use_python_system(python, name, project), type == "virtualenv" ~ renv_use_python_virtualenv(python, name, project), type == "conda" ~ renv_use_python_condaenv(python, name, project) ) } renv_use_python_system <- function(python, name, project) { # retrieve python information python <- renv_python_resolve(python) version <- renv_python_version(python) info <- renv_python_info(python) # if the user ended up selecting a virtualenv or conda python, then # just activate those and ignore the 'system' request if (identical(info$type, "virtualenv")) return(renv_use_python_virtualenv(info$python, name, project)) if (identical(info$type, "conda")) return(renv_use_python_condaenv(info$python, name, project)) # for 'system' python usages, we just use the path to python # (note that this may not be portable or useful for other machines) renv_use_python_fini(info, python, version, project) } renv_use_python_virtualenv <- function(python, name, project) { # if name has been set, check and see if it refers to an already-existing # virtual environment; if that exists, use it if (is.null(python) && !is.null(name)) { path <- renv_python_virtualenv_path(name) if (file.exists(path)) python <- renv_python_exe(name) } python <- renv_python_resolve(python) version <- renv_python_version(python) info <- renv_python_info(python) # if name is unset, and 'python' doesn't already refer to an existing # virtual environment, then we'll use a local virtual environment local <- is.null(name) && identical(info$type, "virtualenv") if (local) { name <- renv_path_aliased(info$root) if (renv_path_same(dirname(name), renv_python_virtualenv_home())) name <- basename(name) } else { name <- name %||% renv_python_envpath(project, "virtualenv", version) if (grepl("/", name, fixed = TRUE)) name <- renv_path_canonicalize(name) } # now, check to see if the python environment exists; # if it does not exist, we'll create it now vpython <- renv_use_python_virtualenv_impl(project, name, version, python) vinfo <- renv_python_info(vpython) # finish up now renv_use_python_fini(vinfo, name, version, project) } renv_use_python_condaenv <- function(python, name, project) { # if python is set, see if it's already the path to a python interpreter # living within a conda environment while (!is.null(python)) { if (!is.null(name)) { fmt <- "ignoring value of name %s as python was already set" warningf(fmt, renv_path_pretty(name)) } # validate that this is a conda python info <- renv_python_info(python) if (!identical(info$type, "conda")) { fmt <- "%s does not appear to refer to a Conda instance of Python; ignoring" warningf(fmt, renv_path_pretty(python)) break } # use this edition of python without further adieu version <- renv_python_version(python) return(renv_use_python_fini(info, name, version, project)) } # TODO: how do we select which version of python we want to use? name <- name %||% renv_python_envpath(project, "conda") python <- renv_use_python_condaenv_impl(project, name) info <- renv_python_info(python) version <- renv_python_version(python) renv_use_python_fini(info, name, version, project) } renv_use_python_fini <- function(info, name, version, project) { # normalize project path for later comparison project <- renv_path_normalize(project) # handle 'name' -- treat values containing slashes specially, and # check if those paths are project-relative environments if (!is.null(name) && grepl("/", name, fixed = TRUE)) { name <- renv_path_normalize(name) if (startsWith(name, project)) { base <- substring(name, nchar(project) + 2L) name <- if (grepl("^[.][^/]+$", base)) base else file.path(".", base) } } # form the lockfile fields we'll want to write fields <- as.list(c(Version = version, Type = info$type, Name = name)) # update the lockfile lockfile <- renv_lockfile_load(project) if (!identical(fields, lockfile$Python)) { lockfile$Python <- fields renv_lockfile_save(lockfile, project) } # re-initialize with these settings renv_load_python(project, fields) # notify user if (!renv_tests_running()) { if (is.null(info$type)) { fmt <- "- Activated Python %s (%s)." writef(fmt, version, renv_path_aliased(info$python)) } else { fmt <- "- Activated Python %s [%s; %s]" writef(fmt, version, info$type, renv_path_aliased(name)) } } # report to user setwd(project) activate(project = project) invisible(info$python) } # return the path to an existing python binary associated with the virtual # environment having name 'name' and version 'version', or "" if no such # python instance exists renv_use_python_virtualenv_impl_existing <- function(project, name = NULL, version = NULL) { # resolve environment path from name name <- name %||% renv_python_envpath(project, "virtualenv", version) path <- renv_python_virtualenv_path(name) if (!file.exists(path)) return("") # check that this appears to have a valid python executable info <- catch(renv_python_info(path)) if (inherits(info, "error")) { warning(info) return("") } # validate version and return renv_python_virtualenv_validate(path, version) } # Internal helper for activating a Python virtual environment # # @param project # The project directory. # # @param name # The environment name, if any. If unset, it should be constructed # based on the Python executable used (note: _not_ the version parameter) # # @param version # The _requested_ version of Python (which may not be the actual version!) # This version should be used as a hint for finding an appropriate version # of Python, if the environment needs to be re-created. # # @param python # The copy of Python to be used. When unset, an appropriate version of Python # should be discovered based on the `version` parameter. # # @return # The path to the Python binary in the associated virtual environment. # renv_use_python_virtualenv_impl <- function(project, name = NULL, version = NULL, python = NULL) { # first, look for an already-existing python installation # associated with the requested version of python exe <- renv_use_python_virtualenv_impl_existing(project, name, version) if (file.exists(exe)) return(exe) # couldn't resolve environment from requested version; try to find # a compatible version of python and re-create that environment python <- python %||% renv_python_find(version) pyversion <- renv_python_version(python) name <- name %||% renv_python_envpath(project, "virtualenv", pyversion) path <- renv_python_virtualenv_path(name) # if the environment already exists, but is associated with a different # version of Python, prompt the user to re-create that environment if (file.exists(path)) { exe <- renv_python_virtualenv_validate(path, version) if (file.exists(exe)) return(exe) } printf("- Creating virtual environment '%s' ... ", basename(name)) vpython <- renv_python_virtualenv_create(python, path) writef("Done!") printf("- Updating Python packages ... ") renv_python_virtualenv_update(vpython) writef("Done!") renv_python_virtualenv_validate(path, version) } renv_use_python_condaenv_impl <- function(project, name = NULL, version = NULL, python = NULL) { # if we can't load reticulate, try installing if there is a version # recorded in the lockfile if (!requireNamespace("reticulate", quietly = TRUE)) { # retrieve reticulate record lockfile <- renv_lockfile_load(project = project) records <- renv_lockfile_records(lockfile) reticulate <- records[["reticulate"]] # if we have a reticulate record, then attempt to restore if (!is.null(reticulate)) { restore(packages = "reticulate", prompt = FALSE, project = project) } else { install(packages = "reticulate", prompt = FALSE, project = project) } } # try once more to load reticulate if (!requireNamespace("reticulate", quietly = TRUE)) stopf("use of conda environments requires the 'reticulate' package") # TODO: how to handle things like a requested Python version here? name <- name %||% renv_python_envpath(project, "conda", version) renv_python_conda_select(name, version) } renv_python_deactivate <- function(project) { file <- renv_lockfile_path(project) if (!file.exists(file)) return(TRUE) lockfile <- renv_lockfile_read(file) if (is.null(lockfile$Python)) return(TRUE) lockfile$Python <- NULL renv_lockfile_write(lockfile, file = file) writef("- Deactived Python -- the lockfile has been updated.") TRUE } renv/R/repos.R0000644000176200001440000000445214731330073012706 0ustar liggesusers renv_repos_normalize <- function(repos = getOption("repos")) { # ensure repos are a character vector repos <- convert(repos, "character") # force a CRAN mirror when needed cran <- getOption("renv.repos.cran", "https://cloud.r-project.org") repos[repos == "@CRAN@"] <- cran # ensure all values are named names(repos) <- renv_repos_names(repos) # return normalized repository repos } renv_repos_names <- function(repos) { # if repos is length 1 but has no names, then assume it's CRAN nms <- names(repos) %||% rep.int("", length(repos)) if (identical(nms, "")) return("CRAN") # otherwise, just use the repository URLs as placeholder names nms <- names(repos) unnamed <- !nzchar(nms) nms[unnamed] <- repos[unnamed] nms } renv_repos_validate <- function(repos = getOption("repos")) { # allow empty repository explicitly if (empty(repos)) return(character()) # otherwise, ensure it's a named list or character vector ok <- is.list(repos) || is.character(repos) if (!ok) stopf("repos has unexpected type '%s'", typeof(repos)) # read repository names nm <- names(repos) %||% rep.int("", length(repos)) if (any(nm %in% "")) { # if this is a length-one repository, assume it's CRAN if (length(repos) == 1L) { repos <- c(CRAN = repos) return(renv_repos_normalize(repos)) } # otherwise, error stopf("all repository entries must be named") } # normalize the repos option renv_repos_normalize(repos) } renv_repos_info <- function(url) { memoize( key = url, value = renv_repos_info_impl(url) ) } renv_repos_info_impl <- function(url) { # make sure the repository URL includes a trailing slash url <- gsub("/*$", "/", url) # if this is a file repository, return early if (grepl("^file:", url)) return(list(nexus = FALSE)) # try to download it destfile <- renv_scope_tempfile("renv-repos-") status <- catch(download(url, destfile = destfile, quiet = TRUE)) if (inherits(status, "error")) return(status) # read the contents of the page contents <- renv_file_read(destfile) # determine if this is a Nexus repository nexus <- grepl("Nexus Repository Manager", contents, fixed = TRUE) || grepl("
", contents, fixed = TRUE) list( nexus = nexus ) } renv/R/renv-package.R0000644000176200001440000000060514731330073014115 0ustar liggesusers #' Project-local Environments for R #' #' Project-local environments for \R. #' #' You can use renv to construct isolated, project-local \R libraries. #' Each project using renv will share package installations from a global #' cache of packages, helping to avoid wasting disk space on multiple #' installations of a package that might otherwise be shared across projects. #' "_PACKAGE" renv/R/encoding.R0000644000176200001440000000013014731330072013330 0ustar liggesusers renv_encoding_mark <- function(x, encoding = "UTF-8") { Encoding(x) <- "UTF-8" x } renv/vignettes/0000755000176200001440000000000014761167256013253 5ustar liggesusersrenv/vignettes/python.Rmd0000644000176200001440000000471514731330073015231 0ustar liggesusers--- title: "Using Python with renv" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Using Python with renv} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>" ) ``` If you're using renv with an R project that also depends on some Python packages (say, through the [reticulate](https://rstudio.github.io/reticulate/) package), then you may find renv's Python integration useful. ## Activating Python integration Python integration can be activated on a project-by-project basis. Use `renv::use_python()` to tell renv to create and use a project-local Python environment with your project. If the reticulate package is installed and active, then renv will use the same version of Python that reticulate normally would when generating the virtual environment. Alternatively, you can set the `RETICULATE_PYTHON` environment variable to instruct renv to use a different version of Python. If you'd rather tell renv to use an existing Python virtual environment, you can do so by passing the path of that virtual environment instead -- use `renv::use_python(python = "/path/to/python")` and renv will record and use that Python interpreter with your project. This can also be used with pre-existing virtual environments and Conda environments. ## Understanding Python integration Once Python integration is active, renv will attempt to manage the state of your Python virtual environment when `snapshot()` / `restore()` is called. With this, projects that use renv and Python can ensure that Python dependencies are tracked in addition to R package dependencies. Note that future restores will require both `renv.lock` (for R package dependencies) and `requirements.txt` (for Python package dependencies). ### Virtual environments When using virtual environments, the following extensions are provided: - `renv::snapshot()` calls `pip freeze > requirements.txt` to save the set of installed Python packages; - `renv::restore()` calls `pip install -r requirements.txt` to install the previously-recorded set of Python packages. ### Conda environments When using Conda environments, the following extensions are provided: - `renv::snapshot()` calls `conda env export > environment.yml` to save the set of installed Python packages; - `renv::restore()` calls `conda env [create/update] --file environment.yml` to install the previously-recorded set of Python packages. renv/vignettes/ide-publish.png0000644000176200001440000003174014731330073016155 0ustar liggesusersPNG  IHDR. iCCPICC ProfileHTS !RBotH =tJ)ؕ\D+* Z V dQP*ϙ;?3s?C8bq:@H& f3p )+B"_}7bm _/% ēr3nC+X"u.'4 ƒ2_&8i*6>'q8H6MA""px#q F @!LϔORpR Dȟ/ 믨=CzlZ 2PQ;g&5SȍR؋eފX0=?=@fE(ʐ96Li! 0@H2 /2~lLqD"1Xȍ3"r&ԑx>y/!]f-r#whFT@s>ftFΕKtЀ& X;܀A D8pd /+: 6T=Q N" n!%A8 Q!MH2,!; y@~PAP $JhTC^:.C]}@aLilυ0 #p ΅-p\ U. GQQV(&G%$ըT)ՀjEunzQèOh,fn@t^^ތ@@7ϣo#o2FcqŰ1rL>sss3ybXS36MŮn6b۰]~(Yq8NǕnpJx=Kc1Jr[ V aJ4%#u2b񭒒B%Z2#J>H$RINBOj#'%&d/rT.uz:@LilZ*vIQWSwPVVT?KGMlz:}+(y,,MfݚAc_@QgMfffc-BZ.h Ϧv͝]0ڰv kڣ:::brs:út]/TӺCzT==^ u(cgkwD7h4xlH4d&7ZiTo`440`bjcѤdTÔmkZoȌli̬9֜iflh!a [:Y -wYvq#S=NJdŲʲ곦[Xn~5hnܢs8ڤ2GGS?΍]{5N+N?oюE ݋Mg/DkISK)K9K%bc&~r9I줪w'%ϋWϓݓSS <aB:50uOꇴдi1Č"5Q|nfvfR/]lDzId.hHtMn&NޗUqyc٪٢k99r}u+VV] NZݾpMޚk#K[w}6lh[]wn|^}&M图 ~|~ߒs۰Dۺ<oQRPnKJ$- )k)7*VBPqһJjSՇ]]v{nأp?ڤ[U6'Ou_@uuunC<{ao#8"?ğm?qyզk׎_w~ө󍖛.7[uym\nWwT{{~`GGU>~RoN|=xw_򞑟>{^7h7xrE/^_ CU^FbG^K^VwGFx?:>|~> KW߂=s$V 8978MIO@M+@y")Ȼ loiݔ/'Қ<kEc5_kdЖ3O.XDpl>L3}'sٮ!BeXIfMM*JR(iZASCIIScreenshot$! pHYs%%IR$siTXtXML:com.adobe.xmp Screenshot 178 438 1 2 <%IDATx`TUƿ)I&$,(*b y],ObU'"'"mTD@R@B?wrgMf2L2sw9{O HHH")A$@$@  P,U, mHHR(lNHH6@$@$`)6KU' C$@$@ac   !  XR 8oD $@$@$qv;rssrЬ(9I$@$@$PnctƗ-m;=V" HA-[\D@$@$ $nV(lq @P]-!>' 7n^(lq @qBaHH-jy% K= d [6J$@$-."z  &l-HH . [\D@$@$M(lT[+ @\HH ?Jü $Dd/[@"T%iܜm M k4%~)l)HHH {g`Pn؉:C-񏄦"?LEA$@YD`O&jF J8UZ(lM @Pݏl^_IӡQHTD8-(liHIH'4yK$@ LK/Nk&5=NJ[,2O$@$(lYYm4 @,XdxHH + Pزژi  X(l> @VȬ6fH XyoL [C$`9Vߛ1*]V# X}oL0 [C$`9Vߛ1*–i5 Xf̴ eZ0?$@$@u"#u$@O "u̦_UhMaVݛV3 3  z$T*/Ǎu?ñ8vlcf9nKa++ǽ@A00+@`6_~o{&tHQ%EakT’ TZ`"h߷! gY S G(:r,NDESEڊЉbW=3 [=gr$@)",3^?FpF%E#"\J8U}J_x?JN.ВK%ZEa52 hP;!5_Ԝco\үi]rOdx^G&({%{?y }2cq癖crNa =%'!,U(6 obTbU)X; oodL'Т5&˅`9dMlne2]6涬sbԖ ʬD9֭EBsb^z3t'FLF'`ۺmGH$@@@,ܟqV51rX)f\x={X$B|8ׯA]r΅L墈䳱wt>115(l5C #&<R8?>cs 䅽 w>Ϧ"'yuw9J;E1–6"~ f"lEht=D }Vl`gK׷s佗d~Ht}(xYκ)lYWe0 ؾ 1w;V ZiPzMt;^r}aT{_8Ga,z%"j_|cV,]zF' GBM{$3͘euf)&=("u`a3.[Up1)l16 @ "ECf~۴A-}yYjI/E ǖ哪(vJ!IZf¶b*s HLo7qx+l> .QZ6/Aӹӫ-U(2|Lh B/;+–Mɼ(Q-(XI6|:'Ktp;%nN{'!@\a뮠V#B>$H ٛu8>}/"j@ǿ]l_ `kp?:@q6nܤݏ'l^Fś$@$-U<27D-У_Ңd#eǝj!\ J0aQC7S,) z/ߘOkfZJEpVig&$ܧwUi3tL(\IJbib 䎿4=p (Vp-K[MֵW0Lw8bI{=5ڪgI ^OfabvWG"C6M"nc!kmNݛv_Pq$j;!lFD%$r'Fu9Pt[kg~m '\z.C6@$2^RTOds,;/U&'=w> r7eʢ][3 ڵDu튫o5Y5 L{ZLH2_bJYk58_s,⨢;pL&9ͤcD|xoRoݳ$raB23ؗ-ӆH[3ڥkvD 7j/_ ǷiV[,Xt9B/0n FA@͈ isf4r&?5!H,C4 &-ZZOjomӰi:bbqRԬHbYViqTB]^u+:s6{O6Q y :pRk'Zd׫;|%[s|%-_7mf)Ae ruoYTfP dc-6o5бιGLߍ+؋|ٟ~/ZNY'%XG<꜑4D 2YmYӖS |Ӑ|G]d=)+d ꑀ_-h-6>dxh}}/Sdqru)f$˭^#/W%!=ν3seP5cY\u-34+rV{.쒟vnٹk IQӦoEyҞ7_#dI '%sKٖ[_>8uDgDna~Y')l&܀+q qibSOc{W\k> _ѽkh2(w)Pξš{ ˣatPh,M`USb[.OHZxk:[(ll՟N {`())5kpõbןpặ>1uJ8*N8=L'8.ikM= |Y6e /'<|\y 6qc|tkMvs"Vi5<$@a:Z%&=M6ɵl ~=Op &8bؗ^xD|auɃѦ\<\Ǝ#!re3wXkĆ5K> t>ʔ~k_dgE-ddcW}gBeܾ}qéoqt9ǃb%j{!%;wbPBܫb<v5X[lڦNRφpP͆L`Ȼ=0Ȕ~{GIԔ}݋VV5XW"jY٣?xfyҖI[!g_y%OX=Wռyse z^|vOScq󭷆ks˲H܅]?7^v30dڹ? VւE֛ojBjJ߸쳍l8S$zaS/b 6Ol`_Skn {Y[/KMS.O̗< '-MCt3:F-"V6ꧩ ~NW'ƞ{4h.UO1: 駞b5w݉9g$tqFc\+ݓlg㠺\uQS~֭[^{O g$C/l奦eBD*m|{5kfU_,vcļT$0q"2`H|Cd83RŖP#wh̑WK{> Yy*[]3ؘ1Z|9ظa#N%o;T53uS3z#^|9Zl-f jf%]};qȤkhF{fFcL6\̌dCdysdd^8[YnGfaPrcl i@ W%ie\=d06oڌ˖j/t=#GmFsSh&H=e)Ʃ&=Ó\  z0OČv{G5|=!ݢ*?Jz:ZdU7:iP۶'O/l~ ۍ`u2'gwHnf~toZ-]6ρ E=jayS#@ak a%58-Of9»jӽT]{f|X8t %lOΙu)*W9Gt˖.d|`C1K} =sB-&lɆ Gx/ڊJE;f΄mA]2J8{b=w C169(Ngm_39_eXZs-Ft߸6U.**¬9b@+ܿT%aUЬEM537Yf>*׾}{d f~QwXN3.o̙yly))K6QlD$\5=G;(C' kDwGgGK">>" uR3&%թe[2&F5=)-ۆYڙx1?"3"]֚K& r'Ǎf.!#LĻe˖XoKF&Tͯ?``_R0Z͛ǶHl$r^U-d_*!]*4 G];V-GӔ;u(ᄅrg_ؔvȵYMֱXť"c;讼j0ޛ1Ce&GyV~Uż.庱%)S4_:lXeɁɖ[^6ZU{DYP t/]2R0&y8R|c*FwTUoΝYvuɆK4h*hz' ǼPKuW:/rc.?Twimsp{7Ŧ@׳Uɚ/VꌕfMS.}twznK:v3T,[B5t|n#Ytpڵ&hj䭲u\tf .GmאwP]ᾐ-+3;P"+/Dk͛QuqOֱح!Մ_DMRUg&>%yˍ79}LM }mh. H}~NrT 1j7Qܥ&z*^%jk?FL6\8>Uz{| Pm9¾~urz߂r<ҍ7x=xpaf:S·X0vSMTW^uC:eSi:D@q9D* 3m[Y@+2P{Eaڴm&g{[~ڴi#vʚtX'4de"^‚BiJ(xln>o_ԾCQgC%^.]l©NtJ꘹!_xGz*~^:*Dm?7nҊqPE2ߺ\58lD:D-VQkЎ;p⫯9N-;2hXN߰\h}ecm2W5/+ɳwߴX|kk2NBSV_϶:%I|I?7WD̦jG? fHʌoEKx~N̿).f+ ,yf->J [D"Nׄ-k'JaʩK2V |ٍ^ M&9 si|}ѷy%LIվSO/ .y8EM7d-w5ޟ./þhZ)*j⛹q5E5vO렴ȫP_1XBu=zz$,lzfvl=ղK.pbX%uU*Qa].ǾE@RJ–RH j-l̨nʹ_|(բ@"zL9uTÏN8Sknir4e$@'P'a3fGYr~,Y?8šI'Q/um %\VignetteIndexEntry{Installing packages} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>" ) ``` ```{r setup} library(renv) ``` Package installation is an important part of renv. This vignette gives you the details, starting with an overview of renv's cache, before going into various installation challenges around building from source and downloading with proxies or authentication. ## Cache One of renv's primary features is the global package cache, which shared across all projects. The renv package cache provides two primary benefits: 1. Installing and restoring packages is much faster, as renv can find and re-use previously installed packages from the cache. 2. Projects take up less disk space, because each project doesn't need to contain it's own copy of every package. When installing a package, renv installs into the global cache and then adds a symlink[^1] to that directory in the project library. That way each renv project remains isolated from other projects on your system, but they can still re-use the same installed packages. [^1]: Or junction points, on Windows. Junction points are unfortunately not supported on Windows network shares; see [Hard links and junctions](https://learn.microsoft.com/en-us/windows/win32/fileio/hard-links-and-junctions) for more details. The process by which packages enter the cache is roughly as follows: 1. Package installation is requested via e.g. `install.packages()`, or `renv::install()`, or as part of `renv::restore()`. 2. If renv is able to find the requested version of the package in the cache, then that package is linked into the project library, and installation is complete. 3. Otherwise, the package is downloaded and installed into the project library. 4. After installation of the package has successfully completed, the package is then copied into the global package cache, and then symlinked into the project library. In some cases, renv will be unable to directly link from the global package cache to your project library, e.g. if the package cache and your project library live on different disk volumes. In such a case, renv will instead copy the package from the cache into the project library. This is much slower, so is worth avoiding. ### Cache location You can find the location of the current cache with `renv::paths$cache()`. By default, it will be in one of the following folders: - Linux: `~/.cache/R/renv/cache` - macOS: `~/Library/Caches/org.R-project.R/R/renv/cache` - Windows: `%LOCALAPPDATA%/renv/cache` If you'd like to share the package cache across multiple users, you can do so by setting the `RENV_PATHS_CACHE` environment variable to a shared path. This variable should be set in an R startup file to make it apply to all R sessions. While you can set it in a project-local `.Renviron`, or the user-level `~/.Renviron`, we generally recommend using the R installation's site-wide `Renviron.site` if you'd like to ensure the same cache path is visible to all users of R on a system. You may also want to set `RENV_PATHS_CACHE` so that the global package cache can be stored on the same volume as the projects you normally work on. This is especially important when working projects stored on a networked filesystem. ### Multiple caches It is also possible to configure renv to use multiple cache locations. For example, you might want to make both a user-local package cache, as well as a global administrator-managed cache, visible within an renv project. To do so, you can specify the paths to the cache separated with a `;` (or `:` on Unix if preferred). For example: ``` RENV_PATHS_CACHE=/path/to/local/cache;/path/to/global/cache ``` In such a case, renv will iterate over the cache locations in order when trying to find a package, and newly-installed packages will enter the first writable cache path listed in `RENV_PATHS_CACHE`. ### Shared cache locations When the renv cache is enabled, if that cache is shared and visible to multiple users, then each of those users will have an opportunity to install packages into the renv cache. However, some care must be taken to ensure that these packages can be used by different users in your environment: 1. Packages copied into the cache may have [Access-control Lists](https://en.wikipedia.org/wiki/Access-control_list) (ACLs), which might prevent others from using packages that have been installed into the cache. If this is the case, it's important that ACLs be set (or updated) on cache entries so that the cache is accessible to each user requiring access. When deploying renv in an enterprise environment, the system administrator should take care to ensure ACLs (if any) allow users access to packages within the renv cache. 2. By default, packages copied into the cache will remain "owned" by the user that requested installation of that package. If you'd like renv to instead re-assign ownership of the cached package to a separate user account, you can set the `RENV_CACHE_USER` environment variable. When set, renv will attempt to run `chown -R ` to update cache ownership after the package has been copied into the cache. ### Caveats While we recommend enabling the cache by default, if you're having trouble with it, you can disable it by setting the project setting `renv::settings$use.cache(FALSE)`. Doing this will ensure that packages are then installed into your project library directly, without attempting to link and use packages from the renv cache. If you find a problematic package has entered the cache (for example, an installed package has become corrupted), that package can be removed with the `renv::purge()` function. See the `?purge` documentation for caveats and things to be aware of when removing packages from the cache. You can also force a package to be re-installed and re-cached with the following functions: ```{r, eval = FALSE} # restore packages from the lockfile, bypassing the cache renv::restore(rebuild = TRUE) # re-install a package renv::install("", rebuild = TRUE) # rebuild all packages in the project renv::rebuild() ``` See each function's respective documentation for more details. ## Building from source Where possible, renv will install package binaries, but sometimes a binary is not available and you have to build from source. Installation from source can be challenging for a few reasons: 1. Your system will need to have a compatible compiler toolchain available. In some cases, R packages may depend on C / C++ features that aren't available in an older system toolchain, especially in some older Linux enterprise environments. 2. Your system will need requisite system libraries, as many R packages contain compiled C / C++ code that depend on and link to these packages. ### Configure flags Many `R` packages have a `configure` script that needs to be run to prepare the package for installation. Arguments and environment variables can be passed through to those scripts in a manner similar to `install.packages()`. In particular, the `R` options `configure.args` and `configure.vars` can be used to map package names to their appropriate configuration. For example: ```{r, eval = FALSE} # installation of RNetCDF may require us to set include paths for netcdf configure.args = c(RNetCDF = "--with-netcdf-include=/usr/include/udunits2") options(configure.args = configure.args) renv::install("RNetCDF") ``` This could also be specified as, for example, ```{r, eval = FALSE} options( configure.args.RNetCDF = "--with-netcdf-include=/usr/include/udunits2" ) renv::install("RNetCDF") ``` ### Install flags Similarly, additional flags that should be passed to `R CMD INSTALL` can be set via the `install.opts` `R` option: ```{r, eval = FALSE} # installation of R packages using the Windows Subsystem for Linux # may require the `--no-lock` flag to be set during install options(install.opts = "--no-lock") renv::install("xml2") # alternatively, you can set such options for specific packages with e.g. options(install.opts = list(xml2 = "--no-lock")) renv::install("xml2") ``` ### Vignettes renv does not build vignettes when installing a package from source. This is because vignettes often require suggested packages, and installing all suggested packages (particularly from source) can be arduous. If you want to distribute vignettes for your own packages, we suggest creating your own repository of binaries, either with [R Universe](https://r-universe.dev/) (for publicly hosted packages on GitHub), with [Posit Package Manager](https://posit.co/products/enterprise/package-manager/), or with [drat](https://eddelbuettel.github.io/drat/). ## Downloads By default, renv uses [curl](https://curl.se/) for file downloads when available. This allows renv to support a number of download features across multiple versions of R, including: - Custom headers (used especially for authentication), - Connection timeouts, - Download retries on transient errors. If `curl` is not available on your machine, it is highly recommended that you install it. Newer versions of Windows 10 come with a bundled version of `curl.exe`; other users on Windows can use `renv::equip()` to download and install a recent copy of `curl`. Newer versions of macOS come with a bundled version of `curl` that is adequate for usage with renv, and most Linux package managers have a modern version of `curl` available in their package repositories. You can also configure which `curl` executable is used by setting the `RENV_CURL_EXECUTABLE` environment variable, if necessary. `curl` downloads can be configured through renv's configuration settings -- see `?renv::config` for more details. ### Alternative downloaders If you've already configured R's downloader and would like to bypass renv's attempts to use `curl`, you can use the R option `renv.download.override`. For example, executing: ``` r options(renv.download.override = utils::download.file) ``` would instruct renv to use R's own download machinery when attempting to download files from the internet (respecting the R options `download.file.method` and `download.file.extra` as appropriate). Advanced users can also provide their own download function, provided its signature matches that of `utils::download.file()`. You can also instruct renv to use a different download method by setting the `RENV_DOWNLOAD_METHOD` environment variable. For example: ``` # use Windows' internal download machinery Sys.setenv(RENV_DOWNLOAD_METHOD = "wininet") # use R's bundled libcurl implementation Sys.setenv(RENV_DOWNLOAD_METHOD = "libcurl") ``` If you find that downloads work outside of renv projects, but not within renv projects, you may need to tell renv to use the same download file method that R has been configured to use. You can check which download method R is currently configured to use with: ```{r eval=FALSE} getOption("download.file.method") ``` And the downloader currently used by renv can be queried with: ```{r eval=FALSE} renv:::renv_download_method() ``` You can force renv to use the same download method as R by setting: ```{r eval=FALSE} Sys.setenv(RENV_DOWNLOAD_METHOD = getOption("download.file.method")) ``` and, if necessary, you could also set this environment variable within e.g. your `~/.Renviron`, so that it is visible to all R sessions. See `?Startup` for more details. Note that other features (e.g. authentication) may not be supported when using an alternative download file method -- you will have to configure the downloader yourself if that is required. See `?download.file` for more details. ### Proxies If your downloads need to go through a proxy server, then there are a variety of approaches you can take to make this work: 1. Set the `http_proxy` and / or `https_proxy` environment variables. These environment variables can contain the full URL to your proxy server, including a username + password if necessary. 2. You can use a `.curlrc` (`_curlrc` on Windows) to provide information about the proxy server to be used. This file should be placed in your home folder (see `Sys.getenv("HOME")`, or `Sys.getenv("R_USER")` on Windows); alternatively, you can set the `CURL_HOME` environment variable to point to a custom 'home' folder to be used by `curl` when resolving the runtime configuration file. On Windows, you can also place your `_curlrc` in the same directory where the `curl.exe` binary is located. See the curl documentation on [proxies](https://ec.haxx.se/usingcurl/proxies/) and [config files](https://ec.haxx.se/cmdline/configfile.html) for more details. As an [example](https://github.com/rstudio/renv/issues/146), the following `_curlrc` works when using authentication with NTLM and SSPI on Windows: ``` --proxy "your.proxy.dns:port" --proxy-ntlm --proxy-user ":" --insecure ``` The [curl](https://cran.r-project.org/package=curl) R package also has a helper: ``` curl::ie_get_proxy_for_url() ``` which may be useful when attempting to discover this proxy address. ### Authentication Your project may make use of packages which are available from remote sources requiring some form of authentication to access -- for example, a GitHub enterprise server. Usually, either a personal access token (PAT) or username + password combination is required for authentication. renv is able to authenticate when downloading from such sources, using the same system as the [remotes](https://cran.r-project.org/package=remotes) package. In particular, environment variables are used to record and transfer the required authentication information. | **Remote Source** | **Authentication** | |-------------------|-----------------------------------------| | GitHub | `GITHUB_PAT` | | GitLab | `GITLAB_PAT` | | Bitbucket | `BITBUCKET_USER` + `BITBUCKET_PASSWORD` | | Git Remotes | `GIT_PAT` / `GIT_USER` + `GIT_PASSWORD` | These credentials can be stored in e.g. `.Renviron`, or can be set in your R session through other means as appropriate. If you require custom authentication for different packages (for example, your project makes use of packages available on different GitHub enterprise servers), you can use the `renv.auth` R option to provide package-specific authentication settings. `renv.auth` can either be a a named list associating package names with environment variables, or a function accepting a package name + record, and returning a list of environment variables. For example: ``` r # define a function providing authentication options(renv.auth = function(package, record) { if (package == "MyPackage") return(list(GITHUB_PAT = "")) }) # use a named list directly options(renv.auth = list( MyPackage = list(GITHUB_PAT = "") )) # alternatively, set package-specific option # as a list options(renv.auth.MyPackage = list(GITHUB_PAT = "")) # as a function options(renv.auth.MyPackage = function(record) { list(GITHUB_PAT = "") }) ``` For packages installed from Git remotes, renv will attempt to use `git` from the command line to download and restore the associated package. Hence, it is recommended that authentication is done through SSH keys when possible. Authentication may be required when resolving a package from a remote specification. If the package name cannot be inferred directly from the remote, it can be supplied with a prefix of the form `=`. For example, the igraph package on GitHub at could be installed with: ``` renv::install("igraph=igraph/rigraph") ``` #### Azure DevOps Authentication with Azure DevOps may require credentials to be set in a slightly different way. In particular, you can use: ``` GITHUB_USER = GITHUB_PASS = ``` replacing `` and `` as appropriate -- for example, your password may just be a PAT. See https://github.com/rstudio/renv/issues/1751 for more details. #### Git and Personal Access Tokens Rather than username + password, some remote Git repositories might require Personal Access Tokens (PATs) for authentication. Typically, such servers expect authentication credentials where: - Your PAT is supplied as the username, - Either an empty response, or the string `x-oauth-basic`, is provided as the password. To help facilitate this, you can set: ``` GIT_USER = GIT_PASS = x-oauth-basic ``` in an appropriate startup R file. ### Custom headers If you want to set arbitrary headers when downloading files using renv, you can do so using the `renv.download.headers` R option. It should be a function that accepts a URL, and returns a named character vector indicating the headers which should be supplied when accessing that URL. For example, suppose you have a package repository hosted at `https://my/repository`, and the credentials required to access that repository are stored in the `AUTH_HEADER` environment variable. You could define `renv.download.headers` like so: ``` r options(renv.download.headers = function(url) { if (grepl("^https://my/repository", url)) return(c(Authorization = Sys.getenv("AUTH_HEADER"))) }) ``` With the above, renv will set the `Authorization` header whenever it attempts to download files from the repository at URL `https://my/repository`. ### Debugging If having problems with downloads, you can get more debugging information (including raw requests and responses) by setting: ```R options(renv.download.trace = TRUE) ``` renv/vignettes/ci.Rmd0000644000176200001440000000700014731330073014271 0ustar liggesusers--- title: "Using renv with continuous integration" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Using renv with continuous integration} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>" ) ``` When building, deploying, or testing an renv-using project with continuous integration (CI) systems (e.g. [GitHub Actions][github-actions], [GitLab CI][gitlab-ci], and others) you need some way to tell the CI system to use renv to restore the same packages that you're using locally. The general idea is: 1. Call `renv::snapshot()` on your local machine to generate `renv.lock`. 2. Call `renv::restore()` on your CI service to restore the project library from `renv.lock`. 3. Cache the project library and global renv cache on the CI service. Note that this workflow is not generally a good fit for CRAN packages, because CRAN itself runs `R CMD check` using the latest version of all dependencies. ## GitHub actions Here, we describe two common approaches for integrating renv with a [GitHub Actions](https://github.com/features/actions) workflow: * Use the `r-lib/setup-renv` action. * Use GitHub's built-in cache action together with existing renv functionality; ### Using r-lib/actions/setup-renv The r-lib organization offers some actions for R users, and among them a [`setup-renv`][r-lib-actions-renv] action is provided for projects using renv. To use this action, you can add the following steps to your workflow: ```yaml steps: - uses: actions/checkout@v3 - uses: r-lib/actions/setup-r@v2 - uses: r-lib/actions/setup-renv@v2 ``` Using these steps will automatically perform the following actions: * renv will be installed, via `install.packages("renv")`, * renv will be configured to use the GitHub cache, * If provided via a `with: profile:` key, that renv profile will be activated, * The project will be restored via `renv::restore()`. After this, any steps using R will use the active renv project by default. ### Using the GitHub Actions Cache with renv When using renv in your own custom GitHub action workflow, there are two main requirements: 1. Cache any packages installed by renv across runs, 2. Use `renv::restore()` to restore packages using this cache to speed up installation As an example, these steps might look like: ```yaml env: RENV_PATHS_ROOT: ~/.cache/R/renv steps: - name: Cache packages uses: actions/cache@v1 with: path: ${{ env.RENV_PATHS_ROOT }} key: ${{ runner.os }}-renv-${{ hashFiles('**/renv.lock') }} restore-keys: | ${{ runner.os }}-renv- - name: Restore packages shell: Rscript {0} run: | if (!requireNamespace("renv", quietly = TRUE)) install.packages("renv") renv::restore() ``` See also the [example][github-actions-renv] on GitHub actions. ## GitLab CI The following template can be used as a base when using renv with [GitLab CI][gitlab-ci]: ``` variables: RENV_PATHS_ROOT: ${CI_PROJECT_DIR}/renv cache: key: ${CI_PROJECT_NAME} paths: - ${RENV_PATHS_ROOT} before_script: - < ... other pre-deploy steps ... > - Rscript -e "if (!requireNamespace('renv', quietly = TRUE)) install.packages('renv')" - Rscript -e "renv::restore()" ``` [gitlab-ci]: https://about.gitlab.com/solutions/continuous-integration/ [github-actions]: https://github.com/features/actions [github-actions-renv]: https://github.com/actions/cache/blob/main/examples.md#r---renv [r-lib-actions-renv]: https://github.com/r-lib/actions/tree/v2-branch/setup-renv renv/vignettes/docker.Rmd0000644000176200001440000003133314731330073015153 0ustar liggesusers--- title: "Using renv with Docker" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Using renv with Docker} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>" ) ``` While renv can help capture the state of your R library at some point in time, there are still other aspects of the system that can influence the run-time behavior of your R application. In particular, the same R code can produce different results depending on: - The operating system in use, - The compiler flags used when R and packages are built, - The LAPACK / BLAS system(s) in use, - The versions of system libraries installed and in use, And so on. [Docker](https://www.docker.com/) is a tool that helps solve this problem through the use of **containers**. Very roughly speaking, one can think of a container as a small, self-contained system within which different applications can be run. Using Docker, one can declaratively state how a container should be built (what operating system it should use, and what system software should be installed within), and use that system to run applications. (For more details, please see .) Using Docker and renv together, one can then ensure that both the underlying system, alongside the required R packages, are fixed and constant for a particular application. The main challenges in using Docker with renv are: - Ensuring that the renv cache is visible to Docker containers, and - Ensuring that required R package dependencies are available at run-time. This vignette will assume you are already familiar with Docker; if you are not yet familiar with Docker, the [Docker Documentation](https://docs.docker.com/) provides a thorough introduction. To learn more about using Docker to manage R environments, visit [environments.rstudio.com](https://environments.rstudio.com/docker.html). We'll discuss two strategies for using renv with Docker: 1. Using renv to install packages when the Docker image is generated; 2. Using renv to install packages when Docker containers are run. We'll also explore the pros and cons of each strategy. ## Creating Docker images with renv With Docker, [Dockerfiles](https://docs.docker.com/engine/reference/builder/) are used to define new images. Dockerfiles can be used to declaratively specify how a Docker image should be created. A Docker image captures the state of a machine at some point in time -- e.g., a Linux operating system after downloading and installing R `r getRversion()[1, 1:2]`. Docker containers can be created using that image as a base, allowing different independent applications to run using the same pre-defined machine state. First, you'll need to get renv installed on your Docker image. The easiest way to accomplish this is with the `remotes` package. For example, you could install the latest release of `renv` from CRAN: ``` RUN R -e "install.packages('renv', repos = c(CRAN = 'https://cloud.r-project.org'))" ``` Alternatively, if you needed to use the development version of `renv`, you could use: ``` RUN R -e "install.packages('remotes', repos = c(CRAN = 'https://cloud.r-project.org'))" RUN R -e "remotes::install_github('rstudio/renv')" ``` Next, if you'd like the `renv.lock` lockfile to be used to install R packages when the Docker image is built, you'll need to copy it to the container: ``` WORKDIR /project COPY renv.lock renv.lock ``` Next, you need to tell renv which library paths to use for package installation. You can either set the `RENV_PATHS_LIBRARY` environment variable to a writable path within your Docker container, or copy the renv auto-loader tools into the container so that a project-local library can be automatically provisioned and used when R is launched. ``` # approach one ENV RENV_PATHS_LIBRARY renv/library # approach two RUN mkdir -p renv COPY .Rprofile .Rprofile COPY renv/activate.R renv/activate.R COPY renv/settings.json renv/settings.json ``` Finally, you can run `renv::restore()` to restore packages as defined in the lockfile: ``` RUN R -e "renv::restore()" ``` With this, renv will download and install the requisite packages as appropriate when the image is created. Any new containers created from this image will hence have those R packages installed and visible at run-time. ## Speeding up package installations The aforementioned approach is useful if you have multiple applications with identical package requirements. In this case, a single image containing this identical package library could serve as the parent image for several containerized applications. However, `renv::restore()` is slow -- it needs to download and install packages, which can take some time. Thus, some care is required to efficiently make use of the `renv` cache for projects that require: 1. Building an image multiple times (e.g., to debug the production application as source code is updated), or 2. Calling `renv::restore()` each time the container is run. The former process can be sped up using multi-stage builds, the latter by dynamically provisioning R Libraries, as described below. ### Multi-stage builds For projects that require repeatedly building an image, [multi-stage builds](https://docs.docker.com/build/building/multi-stage/) can be used to speed up the build process. With multi-stage builds, multiple FROM statements are used in the Dockerfile and files can be copied across build stages. This approach can be leveraged to generate more efficient builds by dedicating a first stage build to package synchronization and a second stage build to copying files and executing code that may need to be updated often across builds (e.g., code that needs to be debugged in the container). To implement a two stage build, the following code could be used as part of a Dockerfile. ``` # STAGE 1: renv-related code FROM AS base WORKDIR /project # using approach 2 above RUN mkdir -p renv COPY renv.lock renv.lock COPY .Rprofile .Rprofile COPY renv/activate.R renv/activate.R COPY renv/settings.dcf renv/settings.dcf # change default location of cache to project folder RUN mkdir renv/.cache ENV RENV_PATHS_CACHE renv/.cache # restore RUN R -e "renv::restore()" ``` The above code uses `FROM AS ` to name the first stage of the build `base`. Here, `` should be replaced with an appropriate image name. Subsequently, the code uses approach 2 (described above) to copy the auto-loader to the project directory in the image. It additionally creates the `renv/.cache` directory that is to be used as the `renv` cache. The second stage of the build is defined by adding the following code to the same Dockerfile, below the previous code chunk. ``` FROM WORKDIR /project COPY --from=base /project . # add commands that need to be debugged below ``` Here, `` could be the same as the parent image of `base`, but does not have to be (see [documentation](https://docs.docker.com/build/building/multi-stage/) for more details). The key line is the `COPY` command, which specifies that the contents of `/project` directory from the `base` image are copied into the `/project` directory of this image. Any commands that will change frequently across builds could be included below the `COPY` command. If only this code associated with the second stage build is updated then `renv::restore()` will not be called again at build time. Instead, the layers associated with the `base` image will be loaded from Docker's cache, thereby saving significant time in build process. In fact, `renv::restore()` will only be called when the `base` image needs to be rebuilt (e.g., when changes are made to `renv.lock`). Docker's cache system is generally good at understanding the dependencies of images. However, if you find that the `base` image is not updating as expected, it is possible to manually enforce a clean build by including the `--no-cache` option in the call to `docker build`. ### Dynamically Provisioning R Libraries with renv However, on occasion, one will have multiple applications built from a single base image, but each application will have its own independent R package requirements. In this case, rather than including the package dependencies in the image itself, it would be preferable for each container to provision its own library at run-time, based on that application's `renv.lock` lockfile. In effect, this is as simple as ensuring that `renv::restore()` happens at container run-time, rather than image build time. However, on its own, `renv::restore()` is slow -- it needs to download and install packages, which could take prohibitively long if an application needs to be run repeatedly. The renv package cache can be used to help ameliorate this issue. When the cache is enabled, whenever renv attempts to install or restore an R package, it first checks to see whether that package is already available within the renv cache. If it is, that instance of the package is linked into the project library. Otherwise, the package is first installed into the renv cache, and then that newly-installed copy is linked for use in the project. In effect, if the renv cache is available, you should only need to pay the cost of package installation once -- after that, the newly-installed package will be available for re-use across different projects. At the same time, each project's library will remain independent and isolated from one another, so installing a package within one container won't affect another container. However, by default, each Docker container will have its own independent filesystem. Ideally, we'd like for *all* containers launched from a particular image to have access to the same renv cache. To accomplish this, we'll have to tell each container to use an renv cache located on a shared mount. In sum, if we'd like to allow for run-time provisioning of R package dependencies, we will need to ensure the renv cache is located on a shared volume, which is visible to any containers launched. We will accomplish this by: 1. Setting the `RENV_PATHS_CACHE` environment variable, to tell the instance of renv running in each container where the global cache lives; 2. Telling Docker to mount some filesystem location from the host filesystem, at some location (`RENV_PATHS_CACHE_HOST`), to a container-specific location (`RENV_PATHS_CACHE_CONTAINER`). For example, if you had a container running a Shiny application: ``` # the location of the renv cache on the host machine RENV_PATHS_CACHE_HOST=/opt/local/renv/cache # where the cache should be mounted in the container RENV_PATHS_CACHE_CONTAINER=/renv/cache # run the container with the host cache mounted in the container docker run --rm \ -e "RENV_PATHS_CACHE=${RENV_PATHS_CACHE_CONTAINER}" \ -v "${RENV_PATHS_CACHE_HOST}:${RENV_PATHS_CACHE_CONTAINER}" \ -p 14618:14618 \ R -s -e 'renv::restore(); shiny::runApp(host = "0.0.0.0", port = 14618)' ``` With this, any calls to renv APIs within the created docker container will have access to the mounted cache. The first time you run a container, renv will likely need to populate the cache, and so some time will be spent downloading and installing the required packages. Subsequent runs will be much faster, as renv will be able to reuse the global package cache. The primary downside with this approach compared to the image-based approach is that it requires you to modify how containers are created, and requires a bit of extra orchestration in how containers are launched. However, once the renv cache is active, newly-created containers will launch very quickly, and a single image can then be used as a base for a myriad of different containers and applications, each with their own independent package dependencies. ## Handling the renv autoloader When `R` is launched within a project folder, the renv auto-loader (if present) will attempt to download and install renv into the project library. Depending on how your Docker container is configured, this could fail. For example: ``` Error installing renv: ====================== ERROR: unable to create '/usr/local/pipe/renv/library/master/R-4.0/x86_64-pc-linux-gnu/renv' Warning messages: 1: In system2(r, args, stdout = TRUE, stderr = TRUE) : running command ''/usr/lib/R/bin/R' --vanilla CMD INSTALL -l 'renv/library/master/R-4.0/x86_64-pc-linux-gnu' '/tmp/RtmpwM7ooh/renv_0.12.2.tar.gz' 2>&1' had status 1 2: Failed to find an renv installation: the project will not be loaded. Use `renv::activate()` to re-initialize the project. ``` Bootstrapping renv into the project library might be unnecessary for you. If that is the case, then you can avoid this behavior by launching R with the `--vanilla` flag set; for example: ``` R --vanilla -s -e 'renv::restore()' ``` renv/vignettes/package-sources.Rmd0000644000176200001440000002225214731330073016760 0ustar liggesusers--- title: "Package sources" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Package sources} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>" ) ``` ```{r setup} library(renv) ``` # Package sources renv uses an installed package's `DESCRIPTION` file to infer its source. For example, packages installed from the CRAN repositories typically have the field: ``` Repository: CRAN ``` set, and renv takes this as a signal that the package was retrieved from CRAN. ## Inferring package sources The following fields are checked, in order, when inferring a package's source: 1. The `RemoteType` field; typically written for packages installed by the devtools, remotes and pak packages, 1. The `Repository` field; for example, packages retrieved from CRAN will typically have the `Repository: CRAN` field, 1. The `biocViews` field; typically present for packages installed from the Bioconductor repositories, As a fallback, if renv is unable to determine a package's source from the `DESCRIPTION` file directly, but a package of the same name is available in the active R repositories (as specified in `getOption("repos")`), then the package will be treated as though it was installed from an R package repository. If all of the above methods fail, renv will finally check for a package available from the _cellar_. The package cellar is typically used as an escape hatch, for packages which do not have a well-defined remote source, or for packages which might not be remotely accessible from your machine. ## Unknown sources If renv is unable to infer a package's source, it will inform you during `renv::snapshot()` -- for example, if we attempted to snapshot a package called `skeleton` with no known source: ``` > renv::snapshot() The following package(s) were installed from an unknown source: skeleton renv may be unable to restore these packages in the future. Consider reinstalling these packages from a known source (e.g. CRAN). Do you want to proceed? [y/N]: ``` While you can still create a lockfile with such packages, `restore()` will likely fail unless you can ensure this package is installed through some other mechanism. ## Custom R package repositories Custom and local R package repositories are supported as well. The only requirement is that these repositories are set as part of the `repos` R option, and that these repositories are named. For example, you might use: ``` repos <- c(CRAN = "https://cloud.r-project.org", WORK = "https://work.example.org") options(repos = repos) ``` to tell renv to work with both the official CRAN package repository, as well as a package repository you have hosted and set up in your work environment. ## Bioconductor renv has been designed to work together as seamlessly as possible with the [Bioconductor](https://bioconductor.org/) project. This vignette outlines some of the extra steps that may be required when using renv with packages obtained from Bioconductor. ## Initializing a project To initialize renv in a project using Bioconductor, you can pass the `bioconductor` argument to `renv::init()`: ```{r, eval = FALSE} # use the latest-available Bioconductor release renv::init(bioconductor = TRUE) # use a specific version of Bioconductor renv::init(bioconductor = "3.14") ``` This will tell renv to activate the appropriate Bioconductor repositories, and to use those repositories when attempting to restore packages. ### Bioconductor releases Bioconductor prepares different versions of its package repositories, for use with different versions of R. The version of Bioconductor used within a particular renv project is stored both as a project setting, and also within the project lockfile. This allows you to "lock" a particular project to a particular Bioconductor release, even as new Bioconductor releases are made available for newer versions of R. To set the version of Bioconductor used in a project, you can use: ```{r, eval = FALSE} renv::settings$bioconductor.version("3.14") ``` If you later choose to upgrade R, you may need to upgrade the version of Bioconductor used as well. If you want to override the Bioconductor repositories used by renv, you can also explicitly set the following option: ```{r, eval = FALSE} options(renv.bioconductor.repos = c(...)) ``` ## The package cellar In some cases, your project may depend on R packages which are not available from any external source, or that external source may not be accessible from the machine calling `renv::restore()`. To help accommodate these scenarios, renv allows you to prepare a package "cellar", to be used as an ad-hoc repository of packages during restore. This allows you to provide package tarballs that can be used to restore packages which cannot be retrieved from any other source. The environment variable `RENV_PATHS_CELLAR` can be used to customize the package cellar location. It should point to a directory containing package binaries and sources, with a structure of the form: - `${RENV_PATHS_CELLAR}/_.tar.gz`; or - `${RENV_PATHS_CELLAR}//_.tar.gz` Alternatively, you can also use a project-local cellar by placing your packages within a folder located at `/renv/cellar`. Note that this folder does not exist by default; you must create it to opt-in. - `/renv/cellar/_.tar.gz`; or - `/renv/cellar//_.tar.gz` As an example, if your project depended on a package `skeleton 1.0.0`, you could place a tarball for this package in one of the following locations: - `${RENV_PATHS_CELLAR}/skeleton_1.0.0.tar.gz` - `${RENV_PATHS_CELLAR}/skeleton/skeleton_1.0.0.tar.gz` - `/renv/cellar/skeleton_1.0.0.tar.gz`; or - `/renv/cellar/skeleton/skeleton_1.0.0.tar.gz` Once this is done, renv will consult these directories during future attempts to restore your packages. You can install a package from the cellar like any other package, i.e. `renv::install("skeleton")`. During restore, if a compatible package is located within the cellar, that copy of the package will be preferred even if that package might otherwise be accessible from its associated remote source. For example, if `skeleton 1.0.0` was also available on CRAN, `renv::restore()` would still use the tarball available in the cellar rather than the version available from CRAN. If you want to see what paths renv is using for the cellar, you can use: ```{r eval=FALSE} renv:::renv_paths_cellar() ``` See `?paths` for more details. ### Explicit sources You can also provide explicit source paths in the lockfile if desired. This is most useful if you are building an renv lockfile "by hand", or need to tweak an existing lockfile to point at a separate package for installation. For example, you could have a package record in `renv.lock` of the form: ``` { "Package": "skeleton", "Version": "1.0.1", "Source": "/mnt/r/pkg/skeleton_1.0.1.tar.gz" } ``` Packages should have the following extensions, depending on whether the archive contains a binary copy of the package or the package sources: | **Platform** | **Binary** | **Sources** | | ------------ | ---------- | ----------- | | Windows | `.zip` | `.tar.gz` | | macOS | `.tgz` | `.tar.gz` | | Linux | `.tar.gz` | `.tar.gz` | Note that on Linux, both binaries and sources should have the `.tar.gz` extension, but R and renv will handle this as appropriate during installation. ## ABI compatibility ABI compatibility issues can arise if different packages were built against different versions of a shared dependency. For example, one package may have been built against Rcpp 1.0.6, and another package might have been built against Rcpp 1.0.7. However, because only one version of the Rcpp package can be loaded at a time within an R session, mixing of these two packages might cause issues either on load or at runtime depending on the version of Rcpp available. It's worth emphasizing that this is not Rcpp's fault; a package built against Rcpp 1.0.7 would reasonably expect newer APIs made available by that version of the package would be available at runtime, and that contract would be violated if an older version of Rcpp were installed in the project library. The challenge for renv is that this build-time dependency is not clearly communicated to renv; in general, it is not possible to know what packages (and their versions) a particular package was built against. ## Build-time dependencies R packages might occasionally (and unintentionally) take a build-time dependency on another R package -- for example, a package with the code: ``` `%>%` <- magrittr::`%>%` ``` would take the version of `%>%` that was available from the version of magrittr that was available at _build_ time, not the one available at _run_ time. This could be problematic if, for example, an update to the magrittr package changed in a way that made old definitions of `%>%` incompatible with newer internal functions. In general, it is a mistake for packages to take a build-time dependency on exported objects from another package; rather, such objects should be imported at runtime (using e.g. `importFrom()` in the package `NAMESPACE` file). renv/vignettes/packages.Rmd0000644000176200001440000001104314731330073015456 0ustar liggesusers--- title: "Package development" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Package development} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>", eval = FALSE ) ``` ## Development Often, R packages will have other R packages as dependencies. For this, one must declare their R package dependencies within the package `DESCRIPTION` file. If you want to prepare your environment for package development, you can use: ``` renv::install() ``` to install the packages as declared in the package's `DESCRIPTION` file. This action is roughly analogous to `remotes::install_deps()`. If you're developing a package that you intend to release to CRAN, then you likely want to build and test your package against the latest versions of your dependencies as available on CRAN. For this, you should consider using: ``` renv::update() ``` to ensure your package dependencies are up-to-date, as appropriate. ## Isolation Normally, a package under development should be tested against the latest-available versions of its dependencies on CRAN. However, in some cases, you may need to ensure your package is compatible with other packages also currently under development. In these cases, the renv project library can be useful -- you can install the development version(s) of your dependencies into the project library, without worrying about clobbering any packages already installed in your system library. In these cases, you can declare your development dependencies using the `Remotes` field of the `DESCRIPTION` file; e.g. ``` Remotes: r-lib/ggplot2 ``` and `renv::install()` will parse that remotes declaration and retrieve the requested package. See the remotes vignette, [Dependency resolution for R package development][dependencies], for more details. ## Library Paths For package projects using renv, a library path outside of the project directory will be used instead. As an example, on macOS, this might look like: ``` > .libPaths() [1] "/Users/kevin/Library/Caches/org.R-project.R/R/renv/library/example-552f6e80/R-4.3/aarch64-apple-darwin20" [2] "/Users/kevin/Library/Caches/org.R-project.R/R/renv/sandbox/R-4.3/aarch64-apple-darwin20/ac5c2659" ``` This is done to avoid issues with `R CMD build`, which can become very slow if your project contains a large number of files -- as can happen if the project library is located in `renv/library` (the typical location). Note that even though the library is located outside of the project, the library path generated will still be unique to that project, and so the project is still effectively isolated in the same way as other renv projects normally are. If you want to customize the location where `renv` places project libraries in this scenario, you can use the `RENV_PATHS_LIBRARY_ROOT` environment variable. For example: ``` RENV_PATHS_LIBRARY_ROOT = ~/.renv/library ``` If you'd still prefer to keep your project library within the project directory, you can set: ``` RENV_PATHS_LIBRARY = renv/library ``` within an appropriate `.Renviron` start-up profile -- but please be aware of the caveats to doing this, as the performance of `R CMD build` will be affected. ## Testing While developing your package, you may want to use a continuous integration service (such as [Travis CI](https://www.travis-ci.com)) to build and test your package remotely. You can use renv to help facilitate this testing -- see the [Continuous Integration](ci.html) vignette for more information. In particular, clever use of the renv cache can help save time that might normally be spent on package installation. See for an example of how renv uses itself for package management in its own CI tests. ## Submitting to CRAN Note that packages submitted to CRAN should be designed to work with the other R packages currently available on CRAN. For that reason, when preparing your package for submission, you'll need to ensure your source package tarball does not include any `renv` infrastructure. `renv` makes this easy by automatically including ``` ^renv$ ^renv\.lock$ ``` in your package's `.Rbuildignore` file. This instructs `R CMD build` to not include these files and folders in the generated package tarball. Through this, even if `renv` is used during package development, it's still easy to build and publish your package to CRAN as you would when developing packages without `renv`. [dependencies]: https://remotes.r-lib.org/articles/dependencies.html renv/vignettes/faq.Rmd0000644000176200001440000001423614731330073014456 0ustar liggesusers--- title: "Frequently asked questions" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Frequently asked questions} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r setup, include=FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>", eval = FALSE ) ``` ## Why isn't my package being snapshotted into the lockfile? For a package to be recorded in the lockfile, it must be both: 1. Installed your project library, *and* 2. Used by the project, as determined by `renv::dependencies()`. This ensures that only the packages you truly require for your project will enter the lockfile; development dependencies (e.g. `devtools`) normally should not. So if you find a package is not entering the lockfile, check the output of `renv::dependencies()`. If an expected package is not listed, it's likely because `dependencies()` uses static analysis and does not understand all of the different ways in which a package might be used in a project. See the docs for more details. ### Capturing all dependencies If you'd instead prefer to capture *all* packages installed into your project library (and eschew dependency discovery altogether), you can do so with: ```{r eval=FALSE} renv::settings$snapshot.type("all") ``` Packages can also be explicitly ignored through a project setting, e.g. with: ```{r eval=FALSE} renv::settings$ignored.packages("") ``` You might also want to double-check the set of ignored packages (`renv::settings$ignored.packages()`) and confirm that you aren't unintentionally ignoring a package you actually require. See the documentation in `?snapshot` for more details. ### Capturing explicit dependencies If you'd like to explicitly declare which packages your project depends on, you can do so by telling renv to form "explicit" snapshots: ```{r eval=FALSE} renv::settings$snapshot.type("explicit") ``` In this mode, renv will only include packages which are explicitly listed in the project's `DESCRIPTION` file as dependencies. ## How do I update the lockfile? The most important thing to remember is that `renv::snapshot()` captures the state of your project at the point in time when `renv::snapshot()` was called. In that sense, the "right" way to update the lockfile is to: 1. Load the renv project, 2. Make the changes you want; e.g. install packages; call `options(repos = <...>)`; ... 3. Call `renv::snapshot()` to update the lockfile. That said, you are also free to modify the `renv.lock` lockfile by hand if necessary; e.g. if you want to manually add / change repositories, change the version of a package used, and so on. The `renv.lock` lockfile is a [JSON](https://www.json.org/json-en.html) file. A [JSON schema](https://json-schema.org/) is provided in the [renv repository](https://github.com/rstudio/renv/tree/main/inst/schema). The main downside to editing a package record in the lockfile directly is that you won't be able to provide a `Hash` for that package, and so renv won't be able to use its global package cache when installing that package. ## How should I handle development dependencies? This is related to the above question: by design, `renv.lock` normally only captures build-time or deploy-time dependencies; it may not capture the packages that you use in iterative workflows (e.g. `devtools`). However, you may want some way of still ensuring these development dependencies get installed when trying to restore a project library. For cases like these, we recommend tracking these packages in a project DESCRIPTION file; typically, within the `Suggests:` field. Then, you can execute: ```{r eval=FALSE} renv::install() ``` to request that renv install the packages as described in the DESCRIPTION file. In addition, the `Remotes:` fields will be parsed and used, to ensure packages are installed from their declared remote source as appropriate. ## I'm returning to an older renv project. What do I do? Suppose you were using renv to manage an older project's dependencies. You have an older lockfile, capturing the dependencies in use when you were last working with that project. You now need to resume work on this project -- what do you do? The answer depends on how exactly you want to use the project. Do you want to treat it as a "time capsule", with dependencies frozen in time? Or are the dependencies in this project fluid, and you are primarily using renv just for isolation of project dependencies? For time capsules, use `renv::restore()` to reinstall the exact packages as declared in the project lockfile `renv.lock`. You may also need to find and install the older version of R used previously with that project, unless your intention is to upgrade R. For projects with fluid dependencies, call `renv::update()` to get the latest versions of the dependencies. Once you've verified that the code still works (or made the changes needed to get it working), call `renv::snapshot()` to record the latest versions. You can also take a more managed approach, that's somewhat in between the two extremes: 1. Use `renv::restore()` to restore the project state as defined in the lockfile. 2. Install and update packages deliberately with `renv::install()` and friends. 3. Verify your code works, then call `renv::snapshot()` to update the new lockfile. ## Why are package downloads failing? Some issues ultimately boil down to a lack of connectivity between your machine and the R package repositories and remote sources you are trying to use. If you are working in a corporate environment, it may be worth confirming whether you have a corporate proxy in place inhibiting internet access, or whether R and renv need to be configured in a way compatible with your working environment. This is often true on Windows machines in enterprise environments, where the default "wininet" download method may work more reliably than others. Learn more in `vignette("package-install")`. In addition, note that renv places shims on the R search path that re-routes calls from `install.packages()` to `renv::install()`. If you need to bypass these shims, you can use `utils::install.packages(<...>)`; that is, with the call to `install.packages()` explicitly qualified with the package `utils::`. See `?renv::load` more details. renv/vignettes/renv.Rmd0000644000176200001440000003515214731330073014661 0ustar liggesusers--- title: "Introduction to renv" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Introduction to renv} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r setup, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>" ) ``` The renv package helps you create reproducible environments for your R projects. This vignette introduces you to the basic nouns and verbs of renv, like the user and project libraries, and key functions like `renv::init()`, `renv::snapshot()` and `renv::restore()`. You'll also learn about some of the infrastructure that makes renv tick, some problems that renv doesn't help with, and how to uninstall it if you no longer want to use it. ```{r} #| echo: false #| label: fig-renv-verbs #| fig.align: center #| out.width: null #| fig.alt: > #| A diagram showing the most important verbs and nouns of renv. #| Projects start with init(), which creates a project library using #| packages from the system library. snapshot() updates the lockfile #| using the packages installed in the project library, where restore() #| installs packages into the project library using the metadata from #| the lockfile, and status() compares the lockfile to the project library. #| You install and update packages from CRAN and GitHub using install() #| and update(), but because you'll need to do this for multiple projects, #| renv uses cache to make this fast. #| fig.cap: > #| The most important verbs and nouns of renv, which you'll learn more #| about below. #| knitr::include_graphics("renv.png", dpi = 144) ``` We assume you're already living a project-centric lifestyle and are familiar with a version control system, like Git and GitHub: we believe these are table stakes for reproducible data science. If you're not already using projects, we recommend [Workflow: Projects](https://r4ds.had.co.nz/workflow-projects.html) from *R for Data Science*; if you're unfamiliar with [Git](https://git-scm.com/) and [GitHub](https://github.com/), we recommend [Happy Git and GitHub for the useR](https://happygitwithr.com). ## Libraries and repositories Before we get into how renv works, you'll learn to fully understand two important pieces of R jargon: libraries and repositories. A **library** is a directory containing installed packages. This term is confusing because you write (e.g.) `library(dplyr)`, making it easy to think that you're loading the dplyr library, not the dplyr package. That confusion doesn't usually matter because you don't have to think about libraries, simply installing all packages into a **system library**[^1] that's shared across all projects. With renv, you'll start using **project libraries,** giving each project its own independent collection of packages. [^1]: More precisely, there can be up to three system libraries: an (optional) **user** library, an (optional) **site** library, and a **default** library (where base R packages are installed). You can see your current libraries with `.libPaths()` and see which packages are available in each library with `lapply(.libPaths(), list.files)`. A **repository** is a source of packages; `install.packages()` gets a package from a repository (usually somewhere on the Internet) and puts it in a library (a directory on your computer). The most important repository is CRAN; you can install packages from CRAN in just about every R session. Other freely available repositories include [Bioconductor](https://bioconductor.org), the [Posit Public Package Manager](https://packagemanager.posit.co), and [R Universe](https://r-universe.dev/) (which turns GitHub organisations into repositories). You can see which repositories are currently set up in your session with `getOption("repos")`; when you call `install.packages("{pkgname}")`, R will look for `pkgname` in each repository in turn. ## Getting started To convert a project to use renv, call `renv::init()`. It adds three new files and directories to your project: - The project library, `renv/library`, is a library that contains all packages currently used by your project[^2]. This is the key magic that makes renv work: instead of having one library containing the packages used in every project, renv gives you a separate library for each project. This gives you the benefits of **isolation**: different projects can use different versions of packages, and installing, updating, or removing packages in one project doesn't affect any other project. - The **lockfile**, `renv.lock`, records enough metadata about every package that it can be re-installed on a new machine. We'll come back to the lockfile shortly when we talk about `renv::snapshot()` and `renv::restore()`. - A project R profile, `.Rprofile`. This file is run automatically every time you start R (in that project), and renv uses it to configure your R session to use the project library. This ensures that once you turn on renv for a project, it stays on, until you deliberately turn it off. [^2]: If you'd like to skip dependency discovery, you can call `renv::init(bare = TRUE)` to initialize a project with an empty project library. The next important pair of tools is `renv::snapshot()` and `renv::restore()`. `snapshot()` updates the lockfile with metadata about the currently-used packages in the project library. This is useful because you can then share the lockfile and other people or other computers can easily reproduce your current environment by running `restore()`, which uses the metadata from the lockfile to install exactly the same version of every package. This pair of functions gives you the benefits of **reproducibility** and **portability**: you are now tracking exactly which package versions you have installed so you can recreate them on other machines. Now that you've got the high-level lay of the land, we'll show a couple of specific workflows before discussing some of the reproducibility challenges that renv doesn't currently help with. ### Collaboration One of the reasons to use renv is to make it easier to share your code in such a way that everyone gets exactly the same package versions as you. As above, you'll start by calling `renv::init()`. You'll then need to commit `renv.lock`, `.Rprofile`, `renv/settings.json` and `renv/activate.R` to version control, ensuring that others can recreate your project environment. If you're using git, this is particularly simple because renv will create a `.gitignore` for you, and you can just commit all suggested files[^3]. [^3]: If you're using another version control system, you'll need to manually ignore `renv/library` and any other directories in `renv/`. Now when one of your collaborators opens this project, renv will automatically bootstrap itself, downloading and installing the appropriate version of renv. It will also ask them if they want to download and install all the packages it needs by running `renv::restore()`. ### Installing packages Over time, your project will need more packages. One of the philosophies of renv is that your existing package management workflows should continue to work, so you can continue to use familiar tools like `install.packages()`[^4]. But you can also use `renv::install()`: it's a little less typing and can install packages from GitHub, Bioconductor, and more, not just CRAN. [^4]: Behind the scene, renv shims `install.packages()`, `update.packages(),` and `remove.packages()` to call the renv equivalents. Learn more in `?renv::load.` If you use renv for multiple projects, you'll have multiple libraries, meaning that you'll often need to install the same package in multiple places. It would be annoying if you had to download (or worse, compile) the package repeatedly, so renv uses a package cache. That means you only ever have to download and install a package once, and for each subsequent install, renv will just add a link from the project library to the global cache. You can learn more about the cache in `vignette("package-install")`. After installing the package and checking that your code works, you should call `renv::snapshot()` to record the latest package versions in your lockfile. If you're collaborating with others, you'll need to commit those changes to git, and let them know that you've updated the lockfile and they should call `renv::restore()` when they're next working on a project. ### Updating packages It's worth noting that there's a small risk associated with isolation: while your code will never break due to a change in another package, it will also never benefit from bug fixes. So for packages under active development, we recommend that you regularly (at least once a year) use `renv::update()`[^5] to get the latest versions of all dependencies. Similarly, if you're making major changes to a project that you haven't worked on for a while, it's often a good idea to start with an `renv::update()` before making any changes to the code. [^5]: You can also use `update.packages()`, but `renv::update()` works with the same sources that `renv::install()` supports. After calling `renv::update()`, you should run the code in your project and verify that it still works (or make any changes needed to get it working). Then call `renv::snapshot()` to record the new versions in the lockfile. If you get stuck, and can't get the project to work with the new versions, you can call `renv::restore()` to roll back changes to the project library and revert to the known good state recorded in your lockfile. If you need to roll back to an even older version, take a look at `renv::history()` and `renv::revert()`. `renv::update()` will also update renv itself, ensuring that you get all the latest features. See `renv::upgrade()` if you ever want to upgrade just renv, or you need to install a development version from GitHub. ## Infrastructure Now that you've got the basic usage of renv under your belt, it's time to learn a bit more about how the lockfile works. You won't typically edit this file directly, but you'll see it changing in your git commits, so it's good to have a sense for what it looks like. The lockfile is always called `renv.lock` and is a json file that records all the information needed to recreate your project in the future. Here's an example lockfile, with the markdown package installed from CRAN and the mime package installed from GitHub: ``` json { "R": { "Version": "`r getRversion()`", "Repositories": [ { "Name": "CRAN", "URL": "https://cloud.r-project.org" } ] }, "Packages": { "markdown": { "Package": "markdown", "Version": "1.0", "Source": "Repository", "Repository": "CRAN", "Hash": "4584a57f565dd7987d59dda3a02cfb41" }, "mime": { "Package": "mime", "Version": "0.12.1", "Source": "GitHub", "RemoteType": "github", "RemoteHost": "api.github.com", "RemoteUsername": "yihui", "RemoteRepo": "mime", "RemoteRef": "main", "RemoteSha": "1763e0dcb72fb58d97bab97bb834fc71f1e012bc", "Requirements": [ "tools" ], "Hash": "c2772b6269924dad6784aaa1d99dbb86" } } } ``` As you can see the json file has two main components: `R` and `Packages`. The `R` component contains the version of R used, and a list of repositories where packages were installed from. The `Packages` contains one record for each package used by the project, including all the details needed to re-install that exact version. The fields written into each package record are derived from the installed package's `DESCRIPTION` file, and include the data required to recreate installation, regardless of whether the package was installed from [CRAN](https://cran.r-project.org/), [Bioconductor](https://www.bioconductor.org/), [GitHub](https://github.com/), [Gitlab](https://about.gitlab.com/), [Bitbucket](https://bitbucket.org/), or elsewhere. You can learn more about the sources renv supports in `vignette("package-sources")`. ## Caveats It is important to emphasize that renv is not a panacea for reproducibility. Rather, it is a tool that can help make projects reproducible by helping with one part of the overall problem: R packages. There are a number of other pieces that renv doesn't currently provide much help with: - **R version**: renv tracks, but doesn't help with, the version of R used with the project. renv can't easily help with this because it's run inside of R, but you might find tools like [rig](https://github.com/r-lib/rig) helpful, as they make it easier to switch between multiple version of R on one computer. - **Pandoc**: The rmarkdown package relies heavily on [pandoc](https://pandoc.org/), but pandoc is not bundled with the rmarkdown package. That means restoring rmarkdown from the lockfile is insufficient to guarantee exactly the same rendering of RMarkdown documents. If this causes problems for you, you might find the tools provided by the [pandoc package](https://cderv.github.io/pandoc/) to be useful. - **Operating system, versions of system libraries, compiler versions**: Keeping a 'stable' machine image is a separate challenge, but [Docker](https://www.docker.com/) is one popular solution. See `vignette("docker", package = "renv")` for recommendations on how Docker can be used together with renv. You also need to be aware that package installation may fail if a package was originally installed through a binary, but that binary is no longer available. renv will attempt to install the package from source, but this can (and often will) fail due to missing system prerequisites. Ultimately, making a project reproducible will always require thought, not just mechanical usage of a tool: what does it mean for a particular project to be reproducible, and how can you use tools to meet that particular goal of reproducibility? ## Uninstalling renv If you find renv isn't the right fit for your project, deactivating and uninstalling it is easy. - To deactivate renv in a project, use `renv::deactivate()`. This removes the renv auto-loader from the project `.Rprofile`, but doesn't touch any other renv files used in the project. If you'd like to later re-activate renv, you can do so with `renv::activate()`. - To completely remove renv from a project, call `renv::deactivate(clean = TRUE)`. If you later want to use renv for this project, you'll need to start from scratch with `renv::init().` If you want to stop using renv for all your projects, you'll also want to remove `renv'`s global infrastructure with the following R code[^6]: [^6]: If you've customized any of renv's infrastructure paths as described in `?renv::paths`, then you'll need to find and remove those customized folders as well. ```{r, eval = FALSE} root <- renv::paths$root() unlink(root, recursive = TRUE) ``` You can then uninstall the renv package with `utils::remove.packages("renv")`. renv/vignettes/packrat.Rmd0000644000176200001440000000437014731330073015332 0ustar liggesusers--- title: "packrat vs. renv" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{packrat vs. renv} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>" ) ``` ```{r setup} library(renv) ``` Our first attempt at solving the problem of reproducible environments was [packrat](https://rstudio.github.io/packrat/). While we learned a lot from packrat, it ultimately proved challenging to use in ways that were difficult to fix. This led us to create renv, a system with fewer surprises and better defaults. If you're an existing packrat user, you can call `renv::migrate()` to switch renv, then read on to learn about the main differences: 1. The renv lockfile `renv.lock` is formatted as [JSON](https://www.json.org/). This should make the lockfile easier to use and consume with other tools. 2. renv no longer attempts to explicitly download and track R package source tarballs within your project. This was a frustrating default that operated under the assumption that you might later want to be able to restore a project's private library without access to a CRAN repository. In practice, this is almost never the case, and the time spent downloading + storing the package sources seemed to outweigh the potential reproducibility benefits. 3. Packrat tried to maintain the distinction between so-called *stale* packages; that is, R packages which were installed by Packrat but were not recorded in the lockfile for some reason. This distinction was (1) overall not useful, and (2) confusing. renv no longer makes this distinction: `snapshot()` saves the state of your project library to `renv.lock`, `restore()` loads the state of your project library from `renv.lock`, and that's all. 4. In renv, the global package cache is enabled by default. This should reduce overall disk-space usage as packages can effectively be shared across each project using renv. 5. renv's dependency discovery machinery is more configurable. The function `renv::dependencies()` is exported, and users can create `.renvignore` files to instruct renv to ignore specific files and folders in their projects. (See `?renv::dependencies` for more information.) renv/vignettes/rsconnect.Rmd0000644000176200001440000000544214731330073015704 0ustar liggesusers--- title: "Using renv with Posit Connect" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Using renv with Posit Connect} %\VignetteEncoding{UTF-8} %\VignetteEngine{knitr::rmarkdown} editor_options: markdown: wrap: 72 --- ```{r, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>" ) ``` [RStudio Connect](https://posit.co/products/enterprise/connect/) is a publication platform for deploying content built in R and Python to share with a broad audience. R users may want to develop content (like [Shiny applications](https://shiny.posit.co/) or [RMarkdown documents](https://rmarkdown.rstudio.com/index.html)) using renv and then publish that content to RStudio Connect. This is a supported pattern where renv is used to manage the local project environment and then RStudio Connect recreates and manages the deployment environment. ## Publishing from the RStudio IDE The RStudio IDE includes a button for push-button deployment to RStudio Connect: ![RStudio IDE Publish Button](ide-publish.png) When this option is used to deploy content to RStudio Connect, a manifest file is automatically generated and sent to RStudio Connect describing the project environment. This manifest file will reflect the project environment create and managed by renv. The renv generated `.Rprofile` file should **not** be included in deployments to RStudio Connect. ## Publishing programatically When publishing content to RStudio Connect programatically, it is necessary to generate a manifest file describing the project environment. This can be done with the `writeManifest()` function from the [rsconnect](https://github.com/rstudio/rsconnect) package. When using renv, the only thing that needs to be considered is that rsconnect should be installed and executed from within the renv environment so that it recognizes the local project library when generating the manifest file. As long as rsconnect is run from within the renv created environment, it will capture project dependencies from the local renv library. This can be accomplished by opening the project in RStudio or by starting the R session from the project root directory. The renv generated `.Rprofile` file should **not** be included in deployments to RStudio Connect. ## A word about packrat RStudio Connect uses [packrat](https://rstudio.github.io/packrat/) to restore project environments on the RStudio Connect server. This should have no impact on how the user develops content for RStudio Connect. It is not necessary for the user to use packrat instead of renv when developing content, as the environment management tool used locally has no impact on the tools RStudio Connect uses for environment management. Therefore, there should be no concerns with using renv to develop content that will be deployed to RStudio Connect. renv/vignettes/profiles.Rmd0000644000176200001440000000710114731330073015523 0ustar liggesusers--- title: "Project profiles" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Project profiles} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set( eval = FALSE, collapse = TRUE, comment = "#>" ) ``` ## Introduction Starting with `renv 0.13.0`, it is possible to activate and switch between different profiles associated with a project. A profile can be thought of as a different mode in which a project is used. For example: - A "development" profile might be used when developing and testing a project, - A "production" profile might be used for production deployments, - A "shiny" profile might be used when running the Shiny application. At its heart, activating or using a particular profile implies using a different set of paths for the project library and lockfile. With this, it is possible to associate different packages, and different dependencies, with different workflows in a single project using renv. ## Usage By default, renv projects use the "default" profile, which implies that library and lockfile paths are set in the typical way. To activate a particular profile, use: ``` renv::activate(profile = "dev") ``` This creates a profile called `"dev"`, and sets it as the default for the project, so that newly-launched R sessions will operate using the `"dev"` profile. After setting this and re-launching R, you should see that the project library and lockfile paths are resolved within the `renv/profiles/dev` folder from the project root. Alternatively, if you want to activate a particular profile for an R session without setting it as the default for new R sessions, you can use: ``` Sys.setenv(RENV_PROFILE = "dev") ``` and renv will automatically use that profile as appropriate when computing library and lockfile paths. Similarly, from the command line, you might enforce the use of a particular profile in an renv project with: ``` export RENV_PROFILE=dev ``` With that set, renv would default to using the `"dev"` profile for any newly-launched R sessions within renv projects. To activate the "default" profile used by a project, use: ``` renv::activate(profile = "default") ``` ## Managing profile-specific dependencies Profile-specific package dependencies can be declared within the project's top-level `DESCRIPTION` file. For example, to declare that the shiny profile depends on the [shiny] and [tidyverse] packages: ``` Config/renv/profiles/shiny/dependencies: shiny, tidyverse ``` If you'd like to also declare that these packages should be installed from a custom remote (analogous to the `Remotes` field for the default profile), you can define those remotes with a separate field: ``` Config/renv/profiles/shiny/remotes: rstudio/shiny, tidyverse/tidyverse ``` These remotes will then be respected in calls to `renv::install()`. The packages and remotes must be specified separately, as renv cannot determine the package name associated with a particular remote without explicitly resolving that remote. Remote resolution normally requires a web request, which renv tries to avoid in "regular" dependency discovery. If you'd prefer that only the packages enumerated in this field are used, you can opt-in to using `"explicit"` snapshots, and leave the `Imports`, `Depends` and `Suggests` fields blank: ```{r} renv::settings$snapshot.type("explicit") ``` When set, only the dependencies listed in the project `DESCRIPTION` file will be used when the lockfile is generated. See `?renv::snapshot` for more details. [shiny]: https://shiny.posit.co/ [tidyverse]: https://www.tidyverse.org/ renv/vignettes/renv.png0000644000176200001440000007667414731330073014741 0ustar liggesusersPNG  IHDRdHsRGBeXIfMM*V^(ifd pHYs%%IR$ iTXtXML:com.adobe.xmp 2 1 5 2 (@IDATxwEϬ#*ARg];;޾;󦺪K! ȎًT"B@! 9B@HXB@! X! ! 9B@HXB@! X! ! 9B@HXB@! X! ! 9B@HXB@! X! ! 9C3dBP2se,ee58 /׿bc/GqoO"~jz믿J *@`[ϽJ?Ǖ=+Mr-Uvf16f̘x{~!,RZT:_~9w֭K<Ǖ|fi=XD8ꞈ3eʔk6Er)|y晧niX=Sȹ{)-@B4*/DM9rK,b=c4vx ׅZ(^뫯:w'W( ,WXNRNUrϿ_~ + we~;B֤Io6REB(\j_M!o뮻>sgUV;w[K|~!k7v|y&LoP ?xW_}m{?^|ůzό'oAm>2dH4 oRK-nwm7'7ʉ'X_ϲ{:0"gԨQ'|駟2k׮ݎ;ț4Hhh;{N;L5kig֬Y=Yp Y?.WEަVckvr!ql^r%sAEnݺyS$6l3;03[zlʯqnyI,K>o8ħz/vV]uUDYz饱ʄ<"d II?Ρ܄4 /gN1cM<9W?x}BṬ: .J|y睙k3=mriӦwͬWn>`LHE;z)S(UT #| [c5xO0|4p7|9s欷zP_^yoi7o[Cs=㦛nB5d^6+24֡ 3gM?|ڴiHhڡY4cFAא! { ʕʹMB4Б݄oᣏ>$̹f툖l42^z饌ʬelK&p&G}"8;,3P-9n{4 kJ+I3IBq!Dx*&M6ӧmh{AlW\2^5Xcs̤I{>i}.[-[CI,uSNXeB/װaݻ_Nߥp*eyp/b&,/(bٳI,2rA6*1zbXApA 9*H#̞ . {ύ;v <k}duǬ\ZW[4Hq!iKKXOF _S &&}_? WtЁ7-ZK !hYDdʢVJhp׍s. <8zf΄rBTF 2k3Us1sq޹91HװUVY[fı X4tFDQxWNV9hH ]w?}Gڊ;~PTdHW7nc\x b,޼>;2vjbq%3nhl̴!2&r%ʬqء:%/0h3tl>o& {q:s'5"`^EX_tp,M&E t+^ ;&&~2Lh+{Yqa6ZlB.o] 8;{/_{JG080C^ϽkPʄ̂1E{LJWwe, +,4 n;L?H|Dr30#c s8 +Z`諯 ?%ux>BX27gsp CQjyO_!VgyyǗR GilƖ$॥)f4HkrʮJw | }Fz)"?_u uHwv=`Z.l772[D $"EeA 9,ԭS #*isGmG G5|pg"@tar9 XHF5\ù }5$ƽ7 eDEV+YD,#ZqAjͲUB@!P=zF" efBzg! @! a@Y! $,Yj$B@!@HX6jV!  yB@4 B@A@|Yj$B@=~!8DãקkM?FP 1 5$Y_X:8c.!2Zl8z<ѤIN jڴ$k2WpwİBz-#`3gPDxCDVA!-͑ i4QN[uUzQJ/ UZ$,ShĘQP8E HDcn=2W^ye4__TCWI؉L,ꫯPU>SOp4YfHVZq5k-yz$,,ĉB?'NhB_DQ\k֚kB:lܸ1O-c}w^N/aV@C.믿 N~a)]v$,ĀE믿0a /0~x~~4,"M./J08a(Fp pK.l`s 7daeD@²k!P̙3CRMl;eʔPLvڵm"£gfϞmG&D-{qp좋.)]b$,K 5>2H?&!&MzͰjzMru)\i֬Y =8Yp7l3ZGtKث~%,kkB,kO?#F_zmڴq鈤5`u?cL/|]v]b%j 2V²k!P`SE:t ㌳馛ʊ{N5PmO"5x Uls/6z+5 ̸(W: |- uܙ7.ҞD+HM4qx0x;H6E$! ".CӦMs:8D|%=ynmذaRLfǎՋ1$,&!P>Ccǎu'oA.֩nAwy筷ʎk̽T&U)e ! |Ly$Fo-z#_x4+A믿ȼ;!Dwl?ԩӡOT* ˊ~|b^B >9tP%u]vم /x)P!橧BjҊ+ثW>;m~m*e !`֊&,]wݕmU;!+h'pYKX4UՌWxg}X遀DL<*%\-#{qI'6Ȱ$,V5**3X9VpAF΄&hW SmíȺV²Ā;!.Xd%([aU|@kmݖ31Ů7haczDc=>`ՓLG `GfLs!Cp`ҥa|OD=yM7]veFyn%VY9S3<egZYBF PM!{=9Oo߾oqM`%,*Ed(pl–/__K(`OTw%AL,6á_E /#X`E/bԵ/d#0ZlKuRJ sz-l9Is}:8Y ǯR4L#Ak 2NPlVZ)"|F 6;\[z X+>*پ}^*&٨^z) GqNҡ(7}k[l1):"BDx{qƺ%P#%JA-*)IvqY~[oժZb# Se4_nXU$Td 6IQ:@c@FgGAvJ*#4ӧQ7q\]~^ l]#CHX2R K31cƜ]2-JNLĉml6!{mʔ)O_ުGI\8hD}{ӅzvZvg{ v?^&TP=a67첗7=|aBD=P!b=+V;yq,ꫳU4e4& 7>x/0o|B@#&hWc=6[]t+\'Tl<s1XVhʷ &&AƯ.*4\_j9A dEG=s8C8 0[385~O dh$_裞O;42jԨM"޼z]a:uZo<3g#cP/OlРAm۶y73ʘÒ7 Dywf,*jMT| Yp7W !tAt" {8٩)ێ v8FHXD/3sla9 4*"z-KlohDx$۰[1l#R;h!N-eg j<\u]rITЦGh&M; 7&[{QѮJBgvimMPIFg;91ɇg!>$a_r"밨i\{E3g9ڳ7HN&IuE/ BsyrAu3"&| iKS8.'0GJD s6%2^tBMc,''uklF!~C` 4HŢ43KTaϜ 8X& 9V+b>g'|͞=o+: ’v%M%ME:th2YKycO3yV|͒ cfgn>\sM{}%0G[r9 r")܊$&.[> 6|δOXbBd4NM4޻ S1.>+- pnQ_84 yxSQ?4bvmjCꂙ,.҅n -N L<(7ӦMC'Dr;ǐgۀzoߞRZX ̈fZQ ;32oy#bb3+].Yڢ4s[0b%mIs׍Ӭ,ED8/4Ki &^r{0UIį]:s2ґq5yi倘v> ƍe]6RB;Po?Ֆ/L#2y&+̽o e.` 19(/{&t٘bg ɘ3M3 <\Z`-zv=ް}8*Ց/礞~m)V_ T>Nzͨ$ri&m؎9@p᱌LLH%8"V{PFÍ⢈x4DhDZKnJ4;ZۑpZ^-4?4#6 |2gr%V*m CNs([ ƚ<[<-2ҖAew6rx̙3}ss, |8Mr5*F?^3Ln]2_r=[q]e]JU`AAtbK$iϼ01;XB.à|ĤJn.t[#0kD n6v,dA̩AYt"CBE)퀺qkw8@y c7]鿡7lIOF#1{pMj%igq'۰Y~ֲ~H?7 "Cbmt;ߔpAv 1;0Ħ:!w`׬M<8?Ǿ(n=34~^6#ʄ*뚼]K.Kuz7 ى PҲ7f]nŢw@"_oҢCNh|X]7>&Ou+Y?6Ṵ2:^mcK~Әf{EY}(+Sl9H<"HY&q d3،E93#M٭[z^%*&j_QZᰮϬ7ZX]v_x%@a) Ka݆a&vF;8J(x_&H{<; l0qzۯ/Y2"s]EORZڛ 2fBP2Ly睇x cO}?!B,zlmYˁ4T%",}5P&H܄l˜|k6Lg,F 9@a,Gqb&\(ѣGDӥ齤’8o٬UzhA"|yb7>abuQvH.80H2qd%hpVsG*BF\x21fQbֳ$ R3ҋXq[c_5T`{ȑ} 3[b1PRaV\ۍ[,N" }[06PtKoeBiiඔN’߸u4;7I Xiz!\,!2&_cI3R-+""E̴,1E"dny^uVK*, @B@…afrJ'#WxA4Θil(cx\s:,R3 eeEbn,ɞ ݄{.bֳH o]t=`+-d<7#l F 8>e8B,!PaK{G(e+s6Y"vEȳ)7lo0e#{^q"fH2 Xpc)ۥx-l6m"l KB!!GtX$,rP9!B^{1@I${ԚlքH|P 2oHUh9.&/f ou۶m1nX\Ir990o?R9J{>}XόqL#*>+hDe RB6< 8;Ϭpߌ&V7cQL!Bl+Nf]_ 79p;u@IGnVL_AlJU0g$ۓf)1<5Z׿ez$ dǎLp]E&P\.Sb aWoqߣGlx6EBQu#͡, R?[!A@Yę!D&bճuoX y&H܄¸;IpLQr2fbL`yHd4PD,yBI K$; 9Zk[07|u2? , cD P`5cbL~s?ɽ^,AȂ MTAްL]<{d3~~Ei-ԇ9C 0v 9WhkH;ቄIh–@\^oľ9<6JPX[B@ ևld=VzGH `26]b"KCv'p>RfRaIzγC3 J;K S$'L$4-ׇk'igg[l'E2^@^__OLnζ P 54y2Y!`ҁi <#(׬$[ -+:kVQhhN>d=lܸq;zXh᧞Bz4ѝZ32s^TÝ2'dz8 9ȝy)n.ƇJ{PJ1['owfs(9}"29(S1c-&Pt!_甆%+fvZ\I |Km'l4#-Z\v˞`6XPv[qpsqʂkÒoY,#h}.i{pz胄!@J{plX!'>]w 7L$hff  h$wAS|b30?0r!lPa'e5lhDVP!!gm4TD>r[вSO,m<Q $bqTѢkZ6*a-<}=Yf2Up҆~W1`HcCA$mxts7D>(ٔrw[_XBHpN/r,l^T⍈w4gm@loR2'bUC׬j"J@jb+j$JGcƲzG2Q4wdB*iXK @B^zpVT󠟕c"qC@v/Of)@zBx<+BƆǓ8{EQV.z*ى@1ؘG|j5ו _FKÎ:(8G$%MuT׌xF>s_09(Ki!`7ߏ?X²e^6ڈhf=G3?%PU|͘lAg|z((Ԑfyb".gguQ~vz8'D6׹2 d /$:iwy q[TĪ(#Fa$H F##e[njQlV\pPєļMT;8V3{y+B?kP(Yl=xy5QD 5!|vqG # 鎲l/X[/B[!A`ƌ]vNde?j4l%_B@DoQ ݛ%B6mۖ_uԨQH!C&5kM1Q"O<b򩧞XI'$;eFBSOe W"P ަM"p2^5hg]o#Qx8ƈHD Zۮ$,)j EGNСC[.ehtE]6[!adxldkP< wa~Wa/ eB`$uUWU:"GƸODIi<~ӧ ѹs]vمf0*eAxuaK`7aF\0B[ KB -W\qErq.ͯ \?<˜\۷6T=",$kXa;|0q-U9(5F\Q{'| |n8q±oaXfxEruڵ#5os+ JvmDPH~]%,+I!Ѷ^nMt'06NG(kI$ɂ< "#wy5KÖ,3ըj06ilLY1lU >5NViMpvԉf kTEE=qyD<*sPۘeHFK|eVV7nB m&[n%Y⋇n8##qSXGZBFrIFy U{ 'NӹEr]cƌApro݂8t7Fp6PmEv XF$ZlYh# aD9z҆n5%`|~M8ٵ¡x|.vzCdEFy![o1/A@Z9m7GF⹣>ᙓX2'D"!^IȀ{Q.#*-g/"R;-vlΗO&"#EԘ1VqCBCrرE̚ܬ&$,Z`l{G,5A(Vw1GW^ 8C P:?C& pX@0~&#aj ^6\1Μ9si_}U'KŨa-غ뮋D\kV_}u9oL&O Ξ=ȼl֭[L‘)MB²48 @WyՆ8&E7DH؅eN@Zc5KN%Zb/ה)SQlǦʆ;*LD[}p{eKmcRCAp"5 7uYOvx:hf͚).b+S>* .I`F:a/"@. =!aYG R@d{e'mcRMFoDΘ1#\pŦOD& QrJ…nF 'rC1x ; d|T*j ykmB@B,1 ! @! aYs\B@B,1 ! @! aYs\B@B,1 ! @! aYs\B@B,1 ! @! aYs\B@B,1 ! @! aYs\B@B,1 ! @! aYs\B@B,1 ! @! aYs\B@B,1 ! @! aYs\B@B,1 ! @! aYs\B,|w~3fɔSv$,ĀU/rGl$¡O[mUϞ=0_"0 yKB@Ag}|AFsm>G}ٳgz .)6Y퉈! )Sk6ڨ:%ry߿()m]w=SH}=zhjq~+NKjET|;*6lm]hPR\sV\qm٦s>rEx;| 4m۶|!Cw* :IDAT04B F2u[(g}v/ uԘ1cXc ѧO' q7;obw:(l!LwqjHJ2Dx6ی;찰l⭷:ˋ4Gi! $!hx͚5va 'O?tUV9[noBXv$d"\\w HV˗_~9\y睱aN8k'SOai(7o~衇8p{G&,FVi My>ZNEy睌Tdbj.҉+ W^ vM#Dz!ZkQkw1cFz'Hヤ}̜9sV""Ri 0-#L'$ 3^J SQZ6xK844O?4]`\fe/sOү9I&Yf~qV޽Y꬞rKڊl3iU~ϟ2y2#! aY2Q%!pܫ8@^7bquE[쫦u{-](T´[4gyL;ȯOr Vh~C,# aYb՝ESǂDeo e;tq9-Z;֫L,\b%Bbߧ_ljbHM71銯+$,ăEaI:BFh{+'x",. ,'j}u]aw-Eye͟gDbnGaFKHr! aY.կ9A<ˁZyv;mڴ/ܴ.ARBXj bYbΜ9l24[SHu]G}Gڊ,o>4Zl]^$"6hoV={KiÅ |^{m޽;{P}aYQ7k 22dA_r&wm73WTB @ % xGתUe8Є+5;f̘-,*bm,_|q嗏x 7{("4ܞs9#axl=sEB H^G=QL96(ϝ|'i:u k=G Dً 1,in椓Nj߾=اOxL2O8WdYwu5-",AӞk)!@#]<9[l1B_6̈́H wDK錚7^1@hOA%TLlq#(g"?D2? NB͒3GDC@Q%r<0u+@ ҿENl~lvEn:qpQG첋˕8]tQǎGy8Ys_}[BT&=[5r-IkM6)_@ﲓ\d>ӑ"կՁ1Te ,G$eG=8l!B G*hH`6ėX0a᧹ 8`ٳnN8eBZk`i=$'.n9 c2|8˴mƆ MqLyf m3gIZ l!܎y>FA /g4iZ`同K.9tp87xc<\$bNcH;vۭ[7';S#4|/2'D>&֜4mvalBbx뭷N[ak$AӅ JL?JZ7VUK# ͒O\/>3f̠Q%Mh cR>Ue^0l;L(('QqAQo:吭A|5oiy{͛7֊+MqD_> "zN6iOР[u)MN}&MHcef=ަg*HJD@²O#FKoL+1;uU/i2Qk=!,qK#_GaÆ!,Q9ϋg`LZv-(AfHn[Y&3s ڵI_On-p!GXS# "9o].21bVd42\gشiS>}V;'MTFI쎰O4n <Յ~q)"KaSk谈Pkiv0lERe K|h'쫽z"裏⌃4%A"B+Gv Ǝ M|[abngϞM M-raFt38M,,*viPwB LC w.ǍɹƼe*[[,P.kb2 OBiPQ(mULD?K/tᑘ|9[]#3gu!PAHX62#*kc<WN,Hl6UrSt~[G >GQFHLHp8k:vX>lajLkQ!}mfؑB@T  (I̒G~Lj8֬uT6H_s'!,7.¦-]6w݃#Dn-+HQ=o:sFs?̤qlV_7.B40-ʜM=Vu! Re>W^nE/$)?혦uY-驄 ۱{_-[$ȮSN keL;4Nڵ+yaqD],X)%J:O(:H;ac'Bl[/ual5b3er\ :%n)|AV$ XA`}!1͚&آ!PM$-!q0Ztg} )H,M4/>,& ؍Ƕ7~۰{$XNp@"V$>Du 9DE^Kt" np")B@Qa#1Y슄g29iuqeg-C#pVeD^B@y*9IPXq!s, ZXJ` 6nF vE@RB"K/ˡfml8묳{.(@Qm! @U# oت~B@ b6`K~4+!@e!}y^'N$z*aW';9'9LO?I&ڵc/(ɓ'?c&L# hPs{U_ԪU+;-|H7xE#J8ٹ"l:a-^N!)%}^j ElI(ƍGJ[bmۖn I!!߆u>쳜[3!P0x]rHvmY'k2XWB@Ti8y o _pE&B2-jeѣG8U&B1*Txey;!nݺ9٘1cМ ,9'va[ou҂2qu8 ŴXz42~Fr[laY HJ;fLYh;DZ[ZO:$@0*[(D,@ 7RlHq~Qn&>Av39^,ΰD _t$Ɂ&~I8ַ~N,- #GYG%A WL Lsq9;S'疰*IWe.]P J,'9.acL&N( k#w}qS<9Lоv~F̱e`P;D#S^xDr;rH#c,?|WDL2CCl{#J!PPKkј[pf9q kq|xr],1Z#* 2o8 629q3BB3+ ѲeKT`=GF-~z%Rʎ8‰uDreFM>bbMn=$_*a4% C *f#˙l4,bH\d 矄FW(󩛱ADݷ;Dv93gL"! *a+1I["JX7|CQdJ!g>>K% uB& y01`v _IOߐ-gNWN;&>c\`J1t:X% 9NQ+$VZJD@²O-a^6\rI+bU̫&=XE-gXB 9.y)ٞ-{0DljbH&lf|wN7&)?㲴j{^\ɲ%\<+l5 oB,S3fJXAX8q!},nAHJ,{$f")>$BL94æȡ8C~H0;=^0rkF_hgUۈP@]1lPX&͇ n1.4(! ҆,D̗?3c$㹊c<`|5[=&uаiH|{#E[cO-ˮrm1BE^8;΄ "O$uM7rBJ,CE<,RZ B@²<H,*ٿQs[$ [5 D3-?ߍ[e%p "OG"R5N&qj_~ҶTy=de"bvk׮yhȅ&g+U9}e+=wm7֢e 뮻 P!e4;s|cgogA0?wlXWB@T|u?>-aP\`eYfH7^5tMtڭ3ϸc*!"IJ!d[ul M6 B (hT{ZvAyEІ4Lw_n3_{"KG u}jm٠4zmiCw}EWY@iCCD0~\X, 4H KiԩP3J! xaS P 4KryliH04[#]BZPpFP4Kq-sڴi=0bk^JEMkLǦ5N@#X,;< jY :Q~Z5v #4 Wǿ)fE)@9(pVw#`e'i xa7kV!6SΙJuKGojCf >890ՍF'@ }},ZD2p}kLHx95鬳 j()H)ZL[B@!dMϳ'B@!R$,S`ĖB@ < q")E@2Fl ! @zLϳ'B@!R$,S`ĖB@ < q")E@2Fls.@zP< q"͎;'"@Paӊp }Gn0/!P [ML7(\$#5.@ aY)OJ|ҟ'J׷zB  "ʇhsd4ggVst&MǎzB HLcAn3I K$MoDr! aY.oJ^ RFŖm rǿ[A²48@SL y}'"ҏ_|1ϿN;E2u+ʎ>:i$\pAcfܸq&L?xԨQ#G|w!Xfe~aDVeر/UoCΞ={y6l؇~آE _$ӈ#y^{o]y啡dbB# 'N|g\rW^y駟^dBoRK8",N~/_X7jԈimƌ42fhdE.Zj)͛] JP\"f^;>B2/rm#"o+o 'ߎ; w!Kg>~6 ,^")rKc$X" 44Ү]]_zΜ9i֬p~9|O>=LkVOꪫrF s1_&?Etӎ.N;4DuECܹsg]y%GWi.gxr@p &,O׾}{oGa)+{Hڗ?EREdVC!M:S1O?ّ#2! H Xb@6!8Ƹ> SJ<_Prf5}Æf5, b$Z,;U#3Pcٹ8sL6N6 q 'X);(XD dG#4}$uASv(aɌ]Fuf5:~ rJĉ'o B|s 4XB(ê79'AF,=-]37)5HBͩ! (%?<7-#P2:@^X&i0cŢ%^DfP|%! RrX#Վ;:! z5sJZTiJG1J]{њeSjF@kt56!P8-3B@C@²+# aY8f!5e=p W!  G@²pTC! j  {B@eᘩB@55\! ( 1S ! 1\~pB pR;aQe ! "P62ʈ@Pl4= "ʏ4?q )G@2H ! @,3B@!r$,SĞB@ ?q )G@2H ! @,3B@!r$,SĞB@ ?q )G@2H ! @,3B@!r$,SĞB@ ?q H>`Ϟ='LP7f⋷~믿[גL{B@!#|9眗_~D얶 ބA`Ȑ![oUYfW_}Ono=LVҢ8RSB@!PA|WyUH{E2tg-)J tԬB 1b8vX__}iӦqlryk1c+gnܸox'O2eʆn+O8qԩm]p&Mڳf5j-K,6l???|GX`PիW C\]y]p4jNk޼p?f}4 x7|W\q /~ᢋ.9ZSNGq.$`]v"O?4ޣ3b>}O-J!P# ;찃}#jdf!ڇ ѣGW[m5TCTL9昽 q҆(B\]r%(ȃ>a7_7sA"AHST4N;oR&ҧs -A< ^x7:t(}ϸ{"P36;y?!e !pܫ"^lŰp5[M6@z'|/NZkv^:i$xS%X*TSK7!<`E^roX{up]z-',8أSЯ, KB@ZACe4>Ld_~R։㉰x)cjc7N M0RnuM5W89s#,*42dza-9 欛e>(F!P׾}֭[@kuHb)w}M4 os8rVI a"! j[;еkbY` &8,O2*B@T-2| tO||*:'ú-Z w]H0cB;يYfCFB@*GO?4#k&Xg?3$=??V"<`ٚŖAz!*>kf8 ۉd&p1R7[i$! @"4jժO6c>#tBE>xZ1nB:"ӄYf~:#<ϳӅwOr FztO(OXR!P[ h;yȮx;3OdDDf(bh 33VOa K%,C4B@!a3F:b@cSJU@$\DhxżM$,kW1 E@K~#\=hY."XF/1rwGe*nD [馛tȑVkuLK̃s$!APsiN>>\XguB HXp,KJKsZ;Ij#DGtM_ K*G# ag$<#[9l/evy3W+;vHO,y0['\XnR!~$,aRزe$?%\օ%a9}Y6PsO˼]fG0K.dv"B"$&" W.Lv#:;wj^^{enDl\;"O=8wqIM%(1%\P'K/[S눽{6Ti މljJŠ2Zb9Qꪫ2:[G@9,x-CgHDNZ%A%*qg}+/BQFY{(GT gf7o%ӯ_?}CW5ߞ{ś]o8[o5B(蠜2)խe<)PX 3Q˶O<!)="d_|E{ k(Kp e<)̛ݺu2DN;K'EA=X{w/XvЁci $8.xi}mK aQZTps=RgM[f$beU2Za={PK/<;Fmݬƍj!*!@! aYAKV?|؇mB@s/SLiڴ)mVh(7,ԿB@9A! (7~_! ReB@r# aY'B HXA! (7~_! Re9sL>#um裏 ˕VZв\>lΎ.#>cr@t" a"}lvڼbhт aI D]w'~pub{W(䳒+"V/CAy>s'vE >܈-JmTB@TSFDn#sȌqh3B>૯-ꦜ|Җ(..jM ˲NB_}HڤI$'|ⓓ1BiR=z4iúeI}ԩt͹%묳NYxPB@ c$9cM3zr=SV^7ߴ~I :tMyŵyI$@L 0|RJ?@!oM0P"c@JFL <>GE>L@ۑlڶ0N:@Y 'd` A @!@,CA@ ܀ 22d` A @!@,CA@ ܀ 22d` A @!@,CA@ ܀ 22d` A @!X<.7. IENDB`renv/NAMESPACE0000644000176200001440000000355114761163114012453 0ustar liggesusers# Generated by roxygen2: do not edit by hand export(activate) export(autoload) export(checkout) export(clean) export(config) export(consent) export(deactivate) export(dependencies) export(diagnostics) export(embed) export(equip) export(history) export(hydrate) export(init) export(install) export(isolate) export(load) export(lockfile_create) export(lockfile_modify) export(lockfile_read) export(lockfile_validate) export(lockfile_write) export(migrate) export(modify) export(paths) export(project) export(purge) export(rebuild) export(record) export(refresh) export(rehash) export(remove) export(repair) export(restore) export(retrieve) export(revert) export(run) export(sandbox) export(scaffold) export(settings) export(snapshot) export(status) export(sysreqs) export(update) export(upgrade) export(use) export(use_python) importFrom(tools,file_ext) importFrom(tools,md5sum) importFrom(tools,package_dependencies) importFrom(tools,pskill) importFrom(tools,psnice) importFrom(tools,write_PACKAGES) importFrom(utils,Rprof) importFrom(utils,URLencode) importFrom(utils,adist) importFrom(utils,available.packages) importFrom(utils,browseURL) importFrom(utils,citation) importFrom(utils,contrib.url) importFrom(utils,download.file) importFrom(utils,download.packages) importFrom(utils,file.edit) importFrom(utils,getCRANmirrors) importFrom(utils,head) importFrom(utils,help) importFrom(utils,install.packages) importFrom(utils,installed.packages) importFrom(utils,modifyList) importFrom(utils,old.packages) importFrom(utils,packageDescription) importFrom(utils,packageVersion) importFrom(utils,read.table) importFrom(utils,remove.packages) importFrom(utils,sessionInfo) importFrom(utils,str) importFrom(utils,summaryRprof) importFrom(utils,tail) importFrom(utils,tar) importFrom(utils,toBibtex) importFrom(utils,untar) importFrom(utils,unzip) importFrom(utils,update.packages) importFrom(utils,zip) renv/LICENSE0000644000176200001440000000006114761167245012243 0ustar liggesusersYEAR: 2025 COPYRIGHT HOLDER: Posit Software, PBC renv/NEWS.md0000644000176200001440000025006514761163217012342 0ustar liggesusers # renv 1.1.2 * `renv` gains the `sysreqs()` function, which can be used to query the system packages required by a set of R packages. Functionality is currently available for Debian-based distributions, as well as Red Hat distributions. * On Linux, `renv` now uses the database from when determining if an R package's required system libraries are installed, and notifies the user which packages (if any) are missing during install / restore. * Fixed an issue where `renv` could fail to retrieve credentials registered for 'github.com' when querying URLs at 'api.github.com'. * Fixed an issue where `renv` could stall when loading a project and validating that the configured version of Bioconductor is compatible with the version of R currently in use. * `renv::snapshot()` no longer fails to generate a new lockfile if the project contains a lockfile which could not be read or parsed. * Fixed an issue where lockfiles containing unicode characters were not correctly written or read with versions of R (< 4.2.0) on Windows. (#2091) # renv 1.1.1 * Fixed an issue where very long lockfiles could be truncated on write. (#2089) * Fixed an issue where `renv` inadvertently made web requests in tests during `R CMD check`. # renv 1.1.0 ## Major Changes * `renv` now includes additional package DESCRIPTION fields in the lockfile. This can be useful for validating the provenance of packages recorded in the lockfile, and also for applications using `renv` which would like to recompute the hash used for package caching from the lockfile itself. If needed, old-style lockfiles can be generated by setting the option `options(renv.lockfile.version = 1)`. Note that this version of `renv` remains compatible both with the older (minimal) lockfiles, as well as with the newer lockfiles in this release. (#2057) ## Other Fixes * The `renv` sandbox is now unlocked on exit after a call to `renv::use()`. This should alleviate issues seen where R's attempts to clean up the temporary directory could fail due to inadequate permissions on the sandbox directory. (#2076) * Fixed an issue where `renv::restore()` did not respect the named repository within the lockfile when installing packages from the archives of the configured package repositories. (#2081) * Fixed an issue where `renv::snapshot()` could fail if invoked within a project containing empty or invalid `.ipynb` files. (#2073) * Fixed an issue where R package installation could fail if the project depended on a package whose current version available from the configured package repositories required on a newer version of R than what was currently installed, even if that package need not be updated. (#2071) * Fixed an issue where `RENV_CONFIG_EXTERNAL_LIBRARIES` was incorrectly split when using Windows paths. (#2069) * Fixed an issue where `renv` failed to restore packages installed from r-universe when the associated lockfile record had no `RemoteRef` field. (#2068) * `renv` now detects dependencies from usages of `utils::citation()`. (#2047) * Fixed an issue where packages installed from r-universe via an explicit URL remote could not be restored. (#2060) * Fixed an issue where the repositories provided to `renv::init()` were ignored when also initializing `renv` with a specific version of Bioconductor. * `renv::install()` gains the `include` parameter, which can be useful when you'd like to install a subset of dependencies within a project while also respecting any declared remotes in that project's `DESCRIPTION` file. (#2055) * Fixed an issue where `renv` could fail to check for updates for packages installed using `pak` without an explicit branch specified. (#2040) * `renv::use()` no longer re-installs packages which are already installed and compatible with the requested packages. (#2044) * Fixed an issue where `renv::init()` could fail when using named remotes in a DESCRIPTION file's `Remotes:` field. (#2055) * Fixed an issue where ignore rules of the form `!*.*` were not parsed and handled correctly during dependency discovery. (#2052) * Fixed an issue where `renv` erroneously reported that installed packages were cached when the cache was not writable. (#2041). * `renv` now supports updating of currently-installed packages via `renv::install()` when configured to use `pak`. (#2037) * Fixed an issue where `renv` library paths were not properly reset following a suspend / resume in RStudio Server. (#2036) * `renv::run()` gains the `args` parameter, which can be used to pass command-line arguments to a script. (#2015) * `renv` now infers a dependency on `rmarkdown` and `knitr` for R scripts which include YAML front-matter. (#2023) * The performance of `renv`'s built-in JSON reader has been improved. (#2021) * Fixed an issue where `renv` could erroneously create a binding called 'object' in the global environment on startup. (#2017) # renv 1.0.11 * Fixed an issue where headers were not properly passed to `available.packages()` during `renv` bootstrap. (#1942) * `renv` now assumes that Artifactory package repositories will use a CRAN-like layout of packages for packages in the archive. (#1996) * `renv` now includes compiled extensions on Linux + macOS. These extensions are primarily used to improve the performance of `renv::dependencies()` and other static analysis code. Support for Windows may come in a future release. * Fixed an issue where `renv::snapshot()` could fail if the project contained a call to `module()` with no arguments. (#2007) * On Linux, `renv` now emits a message on load if the R temporary directory is within a folder that has been mounted as 'noexec'. This message can be suppressed by setting the `RENV_TEMPDIR_NOEXEC_CHECK=FALSE` environment variable. # renv 1.0.10 * Fixed a performance regression in `renv::dependencies()`. (#1999) * Fixed an issue where `renv` tests could fail if the `parallel` package was loaded during test execution. * `renv` now determines a package repository's type through the HTTP headers provided when accessing `src/contrib/PACKAGES`. This is mainly relevant when detecting Nexus and Artifactory repositories. # renv 1.0.9 * Fixed an issue where repository URLs were inappropriately transformed into binary repository URLs on Linux. (#1991) * Fixed an issue where code following `source("renv/activate.R")` in the project `.Rprofile` was not invoked for projects using RStudio. (#1990) # renv 1.0.8 * `renv` now infers a dependency on the `ragg` package when the `ragg_png` device is used in R Markdown / Quarto documents, for documents using the code `knitr::opts_chunk$set(dev = "ragg_png")`. (#1985) * `renv` now automatically generates a lockfile when loading a project containing a `manifest.json` file (typical for application bundles published to Posit Connect). (#1980, @toph-allen) * `renv::install()` now errs if an incompatible `type` argument is provided. * `renv::checkout()` now also checks out the version of `renv` available and associated with the requested snapshot date. (#1966) * Fixed an issue where `renv::hydrate()` did not hydrate packages which were also listed as dependencies within a project's `DESCRIPTION` file. (#1970) * Fixed an issue where `renv::checkout()` omitted some fields from lockfile records when using `actions = c("snapshot", "restore")`. (#1969) * `renv` gains the function `renv::retrieve()`, which can be used to download packages without installing them. This is primarily useful in CI / CD scenarios, where you might want to download packages in a single stage before attempting to install / restore them. (#1965) * `renv` now preserves `Remote` fields present on packages installed from public package repositories (e.g. ). (#1961) * Fixed an issue where `renv::load()` could fail to load an alternate project when `options(renv.config.autoloader.enabled = FALSE)` was set. (#1959) * `renv` now emits clickable hyperlinks for runnable code snippets and help, for front-ends which support the `ide:run` and `ide:help` ANSI escapes. * `renv::init(bioc = "")` now prompts the user in interactive sessions when requesting a version of Bioconductor which is not compatible with the current version of R. (#1943) * `renv::restore()` gains the `transactional` argument, which can be used to control whether `renv` will allow successfully-installed packages remain in the project library even if a package fails to install during a later step. (#1109) * `renv` now infers a dependency on the `xml2` package for projects using the `testthat::JunitReporter` for tests. (#1936) * Fixed an issue where `renv::dependencies()` could emit a warning when attempting to parse chunks using chunk options like `#| eval: c(1, 2)`. (#1906) * `renv::install()` now properly delegates the `type` and `rebuild` arguments to `pak` when `options(renv.config.pak.enabled = TRUE)` is set. (#1924) * `renv` now infers a dependency on the `svglite` package if it detects calls of the form `ggsave(filename = "path.svg")`. (#1930) * `renv` now supports setting of GitHub authentication credentials via any of `GITHUB_TOKEN`, `GITHUB_PAT`, and `GH_TOKEN`. (#1937) * `renv` now also passes any custom headers available to `utils::available.packages()` during bootstrap. (#1942) * Fixed an issue where `renv` could fail during installation of packages stored within repository sub-directories, if that repository also contained a top-level DESCRIPTION file. (#1941) * `renv` now normalizes lockfile entries for Bioconductor packages installed via `pak::pkg_install()`, to help prevent spurious diffs being produced via `renv::status()`. (#1920) * `renv::install()` now respects the `prompt` parameter when `pak` is enabled, as via `options(renv.config.pak.enabled = TRUE)`. (#1907) * Fixed an issue with `renv`'s `pak` integration where `renv` could install the wrong version of a GitHub package during restore if `options(renv.config.pak.enabled = TRUE)` was set. (#1883) * `renv` no longer interacts with the user during autoloading, which coincides with R startup. If the IDE offers a session init hook (RStudio does), loading is deferred until startup is complete and interaction is possible. Otherwise, any suggested renv actions, such as `renv::restore()`, are emitted as a message for the user to act on. (#1879, #1915). * Fixed an issue where installation of packages from local sources, as via `install("", repos = NULL, type = "source")`, could fail. (#1880) * A new function, `renv::lockfile_validate()`, can be used to validate your `renv.lock` against a default or custom schema. (#1889) # renv 1.0.7 * Fixed an issue where `renv`'s activate script failed to report version conflict errors when starting up. (#1874) # renv 1.0.6 * Fixed an issue where downloads could fail with curl >= 8.7.1. (#1869) * Fixed an issue where `renv::snapshot()` did not properly normalize package records generated from packages installed using the `pak` package, e.g. via `pak::pkg_install("cran::")`. * Fixed an issue where `renv` could incorrectly prompt the user to record a version of `renv` installed from GitHub in some cases. (#1857) * `renv::load()` now delegates to `base::load()` when it detects that the call was likely intended for `base::load()`, but `renv::load()` was used instead (e.g. because `base::load()` was masked by `renv::load()`). * `renv::update()` gains the `lock` argument, which can be used to instruct `renv` to automatically update the lockfile after the requested packages have been updated. (#1849) * Fixed an issue where `renv` could fail to update the project autoloader after calling `renv::upgrade()`. (#1837) * Fixed an issue where attempts to install binary packages from older PPM snapshots could fail. (#1839) * `renv` now uses a platform-specific prefix on Linux for library and cache paths by default with R (>= 4.4.0). This is equivalent to setting `RENV_PATHS_PREFIX_AUTO = TRUE`. If necessary, this behavior can be disabled by setting `RENV_PATHS_PREFIX_AUTO = FALSE` in an appropriate R startup file. (#1211) # renv 1.0.5 * `renv` now only writes a `.renvignore` file into the cache directory if the cache appears to be part of the current project. * `renv` now reports missing system libraries as part of its ABI checks in `renv::diagnostics()`. * Fixed an issue where `renv::install(lock = TRUE)` produced lockfiles containing records with extraneous data. (#1828) # renv 1.0.4 * `renv::install()` gains the `lock` argument. When `TRUE`, the project lockfile will be updated after the requested packages have been successfully installed. (#1811) * `renv` now supports the use of GitHub Enterprise servers with the `gitcreds` package for authentication credentials. (#1814) * `renv::dependencies()` now treats `#| eval: !expr <...>` chunk options as truthy by default, implying that such chunks are scanned for their R package dependencies. (#1816) * `renv::dependencies()` now requires usages of the [import](https://cran.r-project.org/package=import) package to be namespace-qualified in order to be handled via static analysis; that is, `import::from()` is parsed for dependencies, but `from(pkg)` is not. (#1815) * `renv::load()` gains the `profile` argument, allowing one to explicitly load a specific profile associated with an `renv` project. See `vignette("profiles", package = "renv")` or https://rstudio.github.io/renv/articles/profiles.html for more details. * `renv::dependencies()` no longer includes `R` dependency versions declared from a `DESCRIPTION` file in its output. (#1806) * Fixed an issue where `renv` could fail to infer dependencies from R Markdown code chunks using engine 'R' (upper-case) rather than 'r' (lower-case). (#1803) * Fixed an issue where `renv` did not report out-of-sync packages when one or more packages used in the project were not installed. (#1788) * Fixed an issue where `renv` could over-aggressively activate P3M repositories when initializing a project. (#1782) * `renv::status()` now notifies the user if the version of R recorded in the lockfile does not match the version of R currently in use. (#1779) * Fixed an issue where packages could appear multiple times in the lockfile, if that package was installed into multiple library paths. (#1781) * Installation of historical binaries of packages is once again enabled, now using the [Posit Public Package Manager (P3M)](https://packagemanager.posit.co) service instead of Microsoft's (now defunct) MRAN service. * `renv::init()` now respects the `Remotes:` field declared in a project's `DESCRIPTION` file, if any. (#1522) * `renv::embed()` now supports Quarto Markdown (`.qmd`) files. (#1700) * renv now sets the `R_INSTALL_TAR` environment variable to match the default `tar` executable (if any) when installing packages. (#1724) * renv now uses `--ssl-revoke-best-effort` by default when downloading files using `curl` on Windows. (#1739) * Fixed an issue where `renv::install()` could fail to detect incompatible recursive package dependencies. (#1671) * `renv::install()` now records remote information for packages installed from CRAN-like repositories, and uses that information when generating a lockfile and restoring those packages. This helps ensure that packages installed within an renv project via calls like `renv::install("", repos = "")` can still be successfully restored in future sessions. (#1765) * `renv::install()` now lazily resolves project remotes. (#1755) * `renv::init()` now respects Remotes declared within a project's `DESCRIPTION` file, if any. * Calling `renv::activate()` without explicitly providing the `profile` argument now preserves the current profile, if any. Use `renv::activate(profile = "default")` if you'd like to re-activate a project using the default profile. (#1217) * Fixed an issue where `renv` would try to prompt for the installation of `BiocManager` when activating a project using Bioconductor. (#1754) * Fixed an issue where directories containing a large number of files could cause `renv` to fail to activate a project. (#1733) * Expanded the set of Linux distributions detected for automatic transformation of Posit Package Manager URLs to install binary packages. `renv` now correctly detects Red Hat Enterprise Linux 9, Rocky Linux 8 and 9, SLES 15 SP4 and SP5, Debian 11 and 12, AlmaLinux 8 and 9, and Amazon Linux 2. (#1720, #1721) # renv 1.0.3 * Fixed an issue where `renv` could warn the project appeared to be out-of-sync when using packages installed without an explicit source recorded. (#1683) * `renv::install()` gains the `exclude` argument, which can be useful when installing a subset of project dependencies. * Fixed an issue where PPM repositories were not appropriately transformed into the correct binary repository in `renv::restore()` when using pak. * `renv::init()` no longer prompts the user for the intended action for projects containing a library with only renv installed. (#1668) * Fixed an issue where non-interactive usages of `renv::init()` could fail with projects containing a DESCRIPTION file. (#1663) * Fixed an issue that could cause code within a project `.Rprofile` to execute before the project had been loaded in RStudio. (#1650) * `renv::snapshot()` and `renv::status()` gain the `dev` argument. This can be used when you'd like to capture package dependencies from the *Suggests* field of your package's DESCRIPTION file. (#1019) # renv 1.0.2 * renv now parses package `NAMESPACE` files for imported dependencies. (#1637) * renv no longer locks the sandbox by default. * Fixed an issue where renv used the wrong library paths when attempting to activate the watchdog. This could cause a 10 second delay when activating the sandbox. # renv 1.0.1 * Fixed an issue where authentication headers could be duplicated when using the `libcurl` download method. (#1605) * `renv::use()` now defaults to setting `isolate = TRUE` when `sandbox = TRUE`. * Fixed an issue where the renv watchdog could fail to load, leading to slowness in activating the sandbox on startup. (#1617) * Fixed an issue where renv did not display warnings / errors from `renv::snapshot()` when `options(renv.verbose = FALSE)` was set. The display of these messages is now controlled via the `renv.caution.verbose` R option. (#1607, #1608) * `renv::load()` now notifies the user if the synchronization check took an excessive amount of time due to the number of files being scanned in the project. (#1573) * `renv::init()` gains the `load` argument, making it possible to initialize a project without explicitly loading it. (#1583) * renv now uses a lock when synchronizing installed packages with the cache. This should help alleviate issues that can arise when multiple R processes are installing and caching packages concurrently. (#1571) * Fixed a regression in parsing expressions within R Markdown chunk options. (#1558) * Fixed an issue that prevented `renv::install()` from functioning when source-only repositories were included. (#1578) * Fixed a logic error in reading `RENV_AUTOLOAD_ENABLED`. (#1580) * `renv::restore()` no longer runs without prompting on load if the library is empty. (#1543) * `renv::repair()` now checks for installed packages which lack a known remote source, and updates their `DESCRIPTION` files if it can infer an appropriate remote source. This typically occurs when a package is installed from local sources, but appears to be maintained or published on a remote repository (e.g. GitHub). This was previously done in `renv::snapshot()`, but we've rolled back that change as the prompting was over-aggressive. (#1574) * `renv::status()` now first reports on uninstalled packages, before reporting on used <-> installed mismatches. (#1538) * When the `RENV_STARTUP_DIAGNOSTICS` environment variable is set to `TRUE`, renv now displays a short diagnostics report after a project's autoloader has been run. This can be useful when diagnosing why renv is slow to load in certain projects. (#1557) * renv now ensures the sandbox is activated on load, for R processes which are launched with the renv sandbox on the library paths. (#1565) * `renv::restore()` no longer erroneously prompts when determining the packages which need to be installed. (#1544) * `renv::update()` now ensures the `prompt` parameter is properly respected during package installation. (#1540) * `renv::activate()` no longer erroneously preserves the previous library paths after restarting the session within RStudio. (#1530) * Use correct spelling of IRkernel package (#1528). * Honor `R_LIBCURL_SSL_REVOKE_BEST_EFFORT` when using an external `curl.exe` binary to download files. (#1624) # renv 1.0.0 ## New features * New `renv::checkout()` installings the latest-available packages from a repository. For example, `renv::checkout(date = "2023-02-08")` will install the packages available on 2023-02-08 from the Posit [Package Manager](https://packagemanager.rstudio.com/) repository. The `actions` argument allows you choose whether a lockfile is generated from the provided repositories ("snapshot"), or whether packages are installed from the provided repositories ("restore"). * `renv::deactivate()` gains a `clean` argument: when `TRUE` it will delete all renv files/directories, leaving the project the way it was found. * `renv::init()` now uses [Posit Public Package Manager](https://packagemanager.posit.co) by default, for new projects where the repositories have not already been configured externally. See the options `renv.config.ppm.enabled`, `renv.config.ppm.default`, and `renv.config.ppm.url` in `?config` for more details (#430). * `renv::lockfile_create()`, `renv::lockfile_read()`, `renv::lockfile_write()` and `renv::lockfile_modify()` provide a small family of functions for interacting with renv lockfiles programmatically (#1438). * Handling of development dependencies has been refined. `renv::snapshot()` and `renv::status()` no longer track development dependencies, while `install()` continues to install them (#1019). `Suggested` packages listed in `DESCRIPTION` files are declared as development dependencies regardless of whether or not they're a "package" project. * MRAN integration is now disabled by default, pending the upcoming shutdown of Microsoft's MRAN service. Users who require binaries of older R packages on Windows + macOS can consider using the instance of CRAN mirrored by the [Posit Public Package Manager](https://packagemanager.posit.co) (#1343). ## Bug fixes and minor improvements * Development versions of renv are now tracked using the Git SHA of the current commit, rather than a version number that's incremented on every change (#1327). This shouldn't have any user facing impact, but makes renv maintenance a little easier. * Fixed an issue causing "restarting interrupted promise evaluation" warnings to be displayed when querying available packages failed. (#1260) * `renv::activate()` uses a three option menu that hopefully make your choices more clear (#1372). * `renv::dependencies()` now discovers R package dependencies inside Jupyter notebooks (#929). * `renv::dependencies()` includes packages used by user profile (`~/.Rprofile`) if `renv::config$user.profile()` is `TRUE`. They are set as development dependencies, which means that they will be installed by `install()` but not recorded in the snapshot. * `renv::dependencies()` only extracts dependencies from text in YAML headers that looks like valid R code (#1288). * `renv::dependencies()` no longer treats `box::use(module/file)` as using package `module` (#1377). * `renv::init()` now prompts the user to select a snapshot type if the project contains a top-level DESCRIPTION file (#1485). * `renv::install(type = "source")` now ensures source repositories are used in projects using [PPM](https://packagemanager.posit.co/). (#927) * `renv::install()` activates Bioconductor repositories when installing a package from a remote (e.g. GitHub) which declares a Bioconductor dependency (via a non-empty 'biocViews' field) (#934). * `renv::install()` respects the project snapshot type, if set. * `renv::install()` now keeps source when installing packages from source (#522). * `renv::install()` now validates that binary packages can be loaded after installation, in a manner similar to source packages (#1275). * `renv::install()` now supports Bioconductor remotes of the form `bioc::/`, for installing packages from a particular version of Bioconductor. Aliases like 'release' and 'devel' are also supported (#1195). * `renv::install()` now requires interactive confirmation that you want to install packages (#587). * `renv::load()` gives a more informative message if a lockfile is present but no packages are installed (#353). * `renv::load()` no longer attempts to query package repositories when checking if a project is synchronized (#812). * `renv::load()` no longer duplicates entries on the `PATH` environment variable (#1095). * `renv::restore()` can now use `pak::pkg_install()` to install packages when `pak` integration is enabled. Set `RENV_CONFIG_PAK_ENABLED = TRUE` in your project's `.Renviron` if you'd like to opt-in to this behavior. Note that this requires a nightly build of `pak` (>= 0.4.0-9000); see https://pak.r-lib.org/dev/reference/install.html for more details. * `renv::restore()` now emits an error if called within a project that does not contain a lockfile (#1474). * `renv::restore()` correctly restores packages downloaded and installed from [r-universe](https://r-universe.dev/) (#1359). * `renv::snapshot()` now standardises pak metadata so CRAN packages installed via pak look the same as CRAN packages installed with renv or `install.packages()` (#1239). * If `renv::snapshot()` finds missing packages, a new prompt allows you to install them before continuing (#1198). * `renv::snapshot()` no longer requires confirmation when writing the first snapshot, since that's an action that can easily be undone (by deleting `renv.lock`) (#1281). * `renv::snapshot()` reports if the R version changes, even if no packages change (#962). * `renv::snapshot(exclude = <...>)` no longer warns when attempting to exclude a package that is not installed (#1396). * `renv::status()` now uses a more compact display when packages have some inconsistent combination of being installed, used, and recorded. * `renv::status()` now works more like `renv::restore()` when package versions are different (#675). * `renv::update()` can now update packages installed from GitLab (#136) and BitBucket (#1194). * `renv::settings$package.dependency.fields()` now only affects packages installed directly by the user, not downstream dependencies of those packages. * renv functions give a clearer error if `renv.lock` has somehow become corrupted (#1027). # renv 0.17.3 * Fixed an issue where `renv::install("bioc::")` could fail if `BiocManager` was not already installed. (#1184) * Fixed an issue where package names were not included in the output of `renv::diagnostics()`. (#1182) * The clarity of the message printed by `renv::status()` has been improved; in particular, renv should better report the recommended actions when a package required by the project is not installed. * `renv::snapshot()` gains the `exclude` argument, for explicitly excluding certain packages from the generated lockfile. * Fixed an issue where renv was passing the wrong argument name to `installed.packages()`, causing usages of renv to fail with R (<= 3.4.0). (#1173) * renv now sets the `SDKROOT` environment variable on macOS if it detects that R was built using an LLVM build of `clang` on macOS. * `renv::install()` now parses the remotes included within, for example, a `DESCRIPTION` file's `Config/Needs/...` field. * renv now checks that the index directory is writable before attempting to use it, e.g. for the `R` available packages index maintained by renv. (#1171) * renv now checks that the version of `curl` used for downloads appears to be functional, and reports a warning if it does not (for example, because a requisite system library is missing). The version of `curl` used for downloads can also be configured via the `RENV_CURL_EXECUTABLE` environment variable. # renv 0.17.2 * Fixed a regression that caused package hashes to be computed incorrectly in some cases. This was a regression in the 0.17.1 release. (#1168) # renv 0.17.1 * renv gains the configuration option `install.remotes`, which can be used to control whether renv attempts to read and use the `Remotes:` field included with installed packages. This can be set to `FALSE` if you'd like to ignore this field; e.g. because you know they will not be accessible. (#1133) * General performance optimizations. In particular, `renv::update(check = TRUE)` should now execute much faster. * renv now stores project settings within `renv/settings.json` rather than `renv/settings.dcf`. Old settings will be automatically migrated. * The renv sandbox is now placed within the renv cache directory. (#1158) * Fixed an issue where `renv::status()` could erroneously report a project was out-of-sync when using explicit snapshots. (#1159) * Fixed an issue where `renv::hydrate()` would print spurious warnings. (#1160) * `renv::status()` now suggests running `renv::restore()` if there are no packages installed in the project library. (#1060) * Fixed an issue where renv would fail to query [r-universe](https://r-universe.dev/) repositories. (#1156) * renv no longer tries to set the `SDKROOT` environment variable on macOS for R (>= 4.0.0). * Fixed an issue where installation of Bioconductor packages could fail when `BiocManager` was not installed. (#1156, #1155) * Fixed an issue where the amount of time elapsed reported by `renv::install()` failed to include the time spent retrieving packages. # renv 0.17.0 * The performance of `renv::snapshot()` has improved. * renv now maintains an index of available packages, as retrieved from the active package repositories, that is shared across `R` sessions. This should improve renv's performance when querying for available packages across multiple different `R` sessions. * `renv::hydrate()` gains the `prompt` parameter. When `TRUE` (the default), renv will prompt for confirmation before attempting to hydrate the active library. * Improved handling of package installation via SSH URLs with `git`. (#667) * Improved handling of R version validation when using projects with Bioconductor. If you find renv is erroneously reporting that your version of R is incompatible with the version of Bioconductor you are using, you can set `options(renv.bioconductor.validate = FALSE)` to disable this validation behaviour. (#1148) * Package names can now be associated with remotes in `renv::install()`; for example, you can use `renv::install("igraph=igraph/rigraph")` to install the `igraph` package. This is mainly important when using the `renv.auth` authentication tools, where the package name of a remote needs to be declared explicitly. (#667) * Fixed an issue that could prevent `renv::restore()` from functioning when attempting to install packages which had been archived on CRAN. (#1141) * `renv::install()` gains the `dependencies` argument, which behaves similarly to the equivalent argument in `remotes::install_*()`. In particular, this can be set to fields like `Config/Needs/dev` to tell renv to use custom DESCRIPTION fields for dependency resolution in installation. * Fixed an issue where the function variant of the `renv.auth` option was not resolved correctly. (#667) * `renv::install()` now supports remotes with a trailing slash -- such slashes are now removed. (#1135) * Integration with the RStudio (Posit) Package Manager is now disabled by default on aarch64 Linux instances. * The `RENV_CACHE_MODE` environment variable can now also be used to adjust the permissions set on downloaded package tarballs / binaries. (#988) * Fixed an issue where fields of the form `Remotes.1` could enter lockfile records for older versions of R. (#736) * Fixed the performance of `renv::update()` in cases where integration with MRAN is enabled. * Fixed an issue where package installation using `pak` could fail in some cases. * `renv_file_find()` can now scan up to the root directory in Docker containers. (#1115) * renv no longer uses the R temporary directory on Windows for the sandbox. The sandbox directory can be customized via the `RENV_PATHS_SANDBOX` environment variable if required. (#835) * renv now reports the elapsed time when installing packages. (#1104) * For projects using "explicit" snapshots, renv now reports if a package is required by the project, but the package is not currently installed. (#949) * Fixed an issue where `renv::snapshot()` could fail to detect when no changes had been made to the lockfile. * Fixed an issue where renv could emit JSON lockfiles which could not be parsed by external JSON readers. (#1102) * renv now marks the sandbox as non-writable, which should hopefully alleviate issues where attempts to update installed packages would inadvertently install the updated package into the sandbox. (#1090) * `renv::use()` gains the `sandbox` argument, which allows one to control whether the system library is sandboxed after a call to `renv::use()`. (#1083) * The path to the Conda `environment.yml` file created by renv can now be customized via the `RENV_PATHS_CONDA_EXPORT` environment variable. We recommend setting this within your project-local `.Renviron` file as appropriate. (#1089) * Fixed an issue where the renv sandbox location did not respect the active renv profile. (#1088) # renv 0.16.0 * renv now supports installation of packages with remotes of the form `=`. However, the provided package name is ignored and is instead parsed from the remote itself. (#1064) * renv now passes along the headers produced by the `renv.download.headers` option when bootstrapping itself in the call to `utils::download.file()`. (#1084) * renv now reports if `renv::snapshot()` will change or update the version of R recorded in the lockfile. (#1069) * renv now supports the `install.packages.check.source` R option, which is used to allow R to query source repositories even if the option `options(pkgType = "binary")` is set. (#1074) * renv better handles packages containing git submodules when installed from GitHub remotes. (#1075) * renv now handles local sources within the current working directory. (#1079) * The renv sandbox is once again enabled by default. On Unix, the sandbox is now created by default within the project's `renv/sandbox` library. On Windows, the sandbox is created within the R session's `tempdir()`. The path to the renv sandbox can be customized via the `RENV_PATHS_SANDBOX` environment variable if required. * Fixed an issue where `renv::status()` could report spurious changes when comparing packages installed using `pak` in some cases. (#1070) * `renv::restore()` now also ensures the project activate script is updated after a successful restore. (#1066) * Fixed an issue where renv could attempt to install a package from the repository archives even when `type = "binary"` was set. (#1046) * Fixed an issue where package installation could fail when the R session is configured to use multiple repositories, some of which do not provide any information on available packages for certain binary arms of the repository. (#1045) * renv now uses `jsonlite` for reading lockfiles when loaded. This should help ensure useful errors are provided for manually-edited lockfiles which contain a JSON parsing error. If the `jsonlite` package is not loaded, renv will fall back to its own internal JSON parser. (#1027) * Fixed an issue that would cause renv to fail to source the user `~/.Rprofile` if it attempted to register global calling handlers, e.g. as `prompt::set_prompt()` does. (#1036) * (Linux only) renv now resets ACLs on packages copied to the cache, to avoid persisting default ACLs that might have been inherited on packages installed into a local project library. If desired, this behavior can be disabled by setting the `RENV_CACHE_ACLS` environment variable to `FALSE`. If you need finer control over ACLs set on packages moved into the cache, consider defining a custom callback via the `renv.cache.callback` R option. (#1025) * Fixed an issue where `.gitignore` inclusion rules for sub-directories were not parsed correctly by renv for dependency discovery. (#403) * Fixed an issue where renv could report spurious diffs within `renv::status()` when comparing package records installed from `pak` versus the default R package installer. (#998) * Fixed an issue where `renv::use_python()` could cause the Requirements field for packages in the lockfile to be unintentionally dropped. (#974) * The R option `renv.cache.callback` can now be set, to run a user-defined callback after a package has been copied into the cache. This can be useful if you'd like to take some action on the cached package's contents after the package has been moved into the cache. * (Unix only) The `RENV_CACHE_MODE` environment variable can now be used to change the access permissions of packages copied into the cache. When set, after a package is copied into the cache, renv will use `chmod -Rf` to try and change the permissions of the cache entry to the requested permissions. * (Unix only) The `RENV_CACHE_USER` environment variable can now be used to change the ownership of folders copied into the cache. When set, after a package is copied into the cache, renv will use `chown -Rf` to try and change the ownership of that cache entry to the requested user account. * Fixed an issue where repositories containing multiple packages in sub-directories could fail to install. (#1016) # renv 0.15.5 * Fixed an issue where indexing of packages in the package cellar could be slow. (#1014) * Fixed an issue where installation of packages from Bioconductor's binary Linux package repositories could fail. (#1013) * `renv::restore()` now supports restoration of packages installed from [R-Forge](https://r-forge.r-project.org/). (#671) * Fixed an issue where `renv::init(bioconductor = TRUE)` would not update the version of Bioconductor used if a project setting had already been set. * It is now possible to "update" an existing lockfile by using `renv::snapshot(update = TRUE)`. When set, any records existing in the old lockfile, but not the new lockfile, will be preserved. (#727) * Fixed an issue where renv could fail to parse Git remotes for users whose username contains a number. (#1004) * renv no longer infers a dependency on the "quarto" R package in projects containing a `_quarto.yml` file. (#995) * Fixed an issue where renv could fail to download a package from MRAN if a compatible source package of the same version was available from the active R repositories. (#990) * renv now reports permission errors during download more clearly. (#985) * Fixed an issue where `RENV_PATHS_LIBRARY_ROOT_ASIS` was not interpreted correctly. (#976) * `renv::modify()` gains the `changes` argument, which can be used to modify a project lockfile non-interactively. * `renv::clean()` now returns the project directory, as documented. (#922) * Fixed an issue where renv could fail to parse embedded YAML chunk options in R Markdown documents. (#963) * renv now sets default placeholder names for the `repos` R option, for any repository URLs which happen to be unnamed. (#964) * Fixed an issue where renv didn't respect the `PATH` ordering when discovering Python installations via `renv_python_discover()`. (#961) * Fixed an issue where renv could fail to install packages containing multibyte unicode characters in their DESCRIPTION file. (#956) * Fixed detection of Rtools 4.2 (#1002) # renv 0.15.4 * Fixed an issue where package installation could fail when referencing the cache via a tilde-aliased path, e.g. `~/.local/share/renv`. (#953) # renv 0.15.3 * A variety of fixes for R CMD check. * renv gains an experimental function, `renv::autoload()`, to be used as a helper for automatically loading a project for R processes launched within a sub-directory of that project. See `?renv::autoload` for more details. * renv will now print a warning message when attempting to read a lockfile containing merge conflict markers (as from e.g. a git merge). (#945) * Fixed an issue where `renv::install()` could install into the wrong library path on Windows, if the R installation had a site-wide profile that mutated the library paths. (#941) * Fixed an issue where `renv::install()` would fail to find a package within the cache when using an abbreviated commit hash for installation. (#943) * Fixed an issue where renv's automatic snapshot was not run after calls to `renv::install()` in some cases. (#939) * Fixed an issue where renv would incorrectly copy a package from the cache, if the cached version of the package and the requested version of the package had the same package version, even if they were retrieved from different sources. (#938) * The path to an renv tarball can now be set via the environment variable `RENV_BOOTSTRAP_TARBALL`, to be used to help renv bootstrap from local sources. This can either be the path to a directory containing renv source tarballs, or the path to the tarball itself. * Fixed an issue where the R site library would not be appropriately masked for resumed RStudio sessions. (#936) # renv 0.15.2 * Fixed issues encountered in R CMD check. # renv 0.15.1 * Fixed an issue where renv inadvertently inserted extra newlines into a `DESCRIPTION` file when adding `Remotes` fields after install. (#914) * Fixed an issue where packages installed from a remote sub-directory would fail to install when later re-installed from the cache. (#913) * renv now recognizes YAML chunk options of the form `#| key: value` when used in R Markdown documents. (#912) * Fixed an issue where the R site library was visible in renv projects with the system library sandbox disabled. * Fixed an issue where renv could update the wrong `.gitignore` in R processes launched by `callr` (e.g. in `devtools::install`). (#910) * Fixed an issue where renv could fail to read mis-encoded DESCRIPTION files. (#908) * Fixed an issue where `config$cache.symlinks()` would report `NULL` when unset. (#906) # renv 0.15.0 * The development branch for renv has moved from master to main. * renv package records in the lockfile now include a `Requirements` entry, which gives a list of R packages this package depends on in some way. This is primarily for internal use by the `pak` package. * Fixed an issue where packages containing DESCRIPTION files using a latin1 encoding would not be read correctly by renv. * Fixed an issue that could cause `renv::dependencies()` to fail when a malformed `DESCRIPTION` file was encountered. (#892) * The path to the project-local renv folder can now be configured via the `RENV_PATHS_RENV` environment variable. This can be useful if you'd prefer to store your project's renv resources within an alternate location in the project. (#472) * renv now uses an external library by default for R package projects, with the library located within `tools::R_user_dir("renv", "cache")`. This directory can be configured via the `RENV_PATHS_LIBRARY_ROOT` environment variable if desired. See `vignette("packages", package = "renv")` for more details. (#384) * renv now uses the repositories as defined within the project lockfile (if any) when attempting to bootstrap itself in a project. (#820) * The renv sandbox is now disabled by default -- see #614 for more details. * renv gains the function `repair()`, to be used to attempt to repair the project library when links into the global package cache appear to be broken. (#378) * Fixed an issue where the staging library used during install could fail to inherit the same directory permissions as the target library itself. (#331) * Fixed an regression which caused `renv::history()` to fail. (#886) * renv gains experimental support for the [pak](https://pak.r-lib.org/) package. Set `RENV_CONFIG_PAK_ENABLED = TRUE` in an appropriate `.Renviron` file to enable `pak` integration. When enabled, calls to `renv::install()` will use `pak` to download and install packages. * `renv::init()` gains the `bioconductor` argument, to be used to initialize a project with a particular Bioconductor release. You can also use `renv::init(bioconductor = TRUE)` to initialize with the latest-available release for the version of R being used. * Project settings can now be overridden by setting an R option of the same name. For example, one could force a project to use Bioconductor 3.14 by setting `options(renv.settings.bioconductor.version = "3.14")` within the project `.Rprofile` (or similar startup R profile). * The ad-hoc package repository called "local sources" has been renamed to the "package cellar". In addition, the path to the cellar is now controlled by the `RENV_PATHS_CELLAR` environment variable, rather than `RENV_PATHS_LOCAL`. This change was made to reduce confusion between "local sources" of packages located somewhere on the filesystem, as opposed to packages explicitly placed in this ad-hoc repository. `RENV_PATHS_LOCAL` remains supported for backwards compatibility. * The `RENV_PATHS_CELLAR` environment variable can now be set to multiple paths. Use `;` as a separator between paths; for example, `RENV_PATHS_LOCAL=/path/to/sources/v1;/path/to/sources/v2`. (#550) * Packages installed via e.g. `renv::install("./path/to/package")` will now retain the relative path to that package within the lockfile. (#873) * Fixed an issue where invalid `config` option values were not properly reported. (#773) * renv now supports restoration of packages installed via one of the [r-universe](https://r-universe.dev/) repositories. * renv gains the `bioconductor.version` project setting, used to freeze the version of Bioconductor used in a particular project. When set, this will override any version that might be selected via the `BiocManager` package. (#864) * renv now infers that parameterized R Markdown documents have a dependency on the `shiny` package. In addition, R code included within the `params:` list will be parsed for dependencies. (#859) * renv now ignores hidden directories during dependency discovery by default. If you want to force a particular hidden directory to be included for discovery, you can use a `.renvignore` file with an explicit inclusion criteria; e.g. `!.hidden/`. * renv now supports the `*release` remotes specifier for GitHub repositories, for requesting installation of the latest declared release of a package from GitHub. (#792) * renv now handles packages stored within the sub-directory of a Git repository better. (#793) * Fixed an issue where `renv::history()` would fail to resolve the correct lockfile path if the working directory was changed. (#834) * Refined dependency discovery within `glue::glue()` expressions. * renv now parses packages referenced via the `base_format` field within an R Markdown document's YAML header. (#854) * Fixed an issue where renv would fail to produce the appropriate binary repository URL for RSPM repositories built using Red Hat Enterprise Linux. * Fixed an issue where `renv::snapshot()` could cause the environment name and / or path associated with a Python environment to be omitted from the lockfile. (#843) * Fixed an issue where `renv::restore()` could fail to restore packages which referred to their source via an explicit path in the `Source` field. (#849) * renv no longer requires explicit user consent when used within Singularity containers. (#824, @kiwiroy) * renv now respects the `R_PROFILE_USER` environment variable, in addition to the `user.profile` configuration option, when considering whether the user `.Rprofile` should be examined for package dependencies. (#821) * The renv auto-loader can now be disabled by setting the environment variable `RENV_AUTOLOADER_ENABLED = FALSE`. This can be useful if you'd like to explicitly control how a project is loaded, e.g. by calling `renv::load()` explicitly. * `renv::snapshot()` gains the `repos` argument, to be used to force the lockfile to be generated with the requested set of R repositories encoded within. * renv now validates that the `repos` option, as used by `renv::snapshot()`, is a named vector. (#811) * Fixed an issue where renv's shims, e.g. for `install.packages()`, failed to pass along other optional arguments to the shimmed function correctly. (#808) # renv 0.14.0 * renv now uses `tools::R_user_dir()` to resolve the default path to the global renv cache, for R installations 4.0.0 or greater. If the renv cache still exists at the old location, that location will be used instead. This change should only affect brand new installations of renv on newer versions of `R`. * Fixed an issue with renv tests failing with R (>= 4.2.0). * renv will no longer auto-activate itself within R processes launched via `R CMD INSTALL`. This behavior can be controlled if necessary via the `RENV_ACTIVATE_PROJECT` environment variable -- set this to `TRUE` to force the project in the current working directory to be activated, and `FALSE` to suppress the renv auto-loader altogether. (#804) * Added dependency discovery support for R utility scripts identified by a shebang line instead of a file extension. (#801; @klmr) * Fixed an issue where `renv::install("", type = "both")` would attempt to install the package from sources, even if the current system did not have the requisite build tools available. (#800) * `renv::scaffold()` gains the `settings` argument, used to create a project scaffolding with some default project options set. (#791) * renv now determines the default branch name for packages installed from GitLab without an explicit reference supplied; for example, as in `renv::install("gitlab::/")`. (#795) * renv now infers a dependency on the `bslib` package for R Markdown documents using custom themes. (#790) * renv will now prompt users to activate the current project when calling `renv::snapshot()` or `renv::restore()` from within a project that has not yet been activated. (#777) * renv now has improved handling for `git` remotes. (#776; @matthewstrasiotto) * `renv::restore()` gains the `exclude` argument, used to exclude a subset of packages during restore. (#746) * Fixed an issue where `renv::dependencies()` could fail to parse dependencies in calls to `glue::glue()` that used custom open and close delimiters. (#772) * Fixed an issue where `renv::init(bare = TRUE)` would unnecessarily scour the project for R package dependencies. (#771) * Fixed an issue where renv could fail to install packages located on GitHub within sub-subdirectories. (#759) * renv gains the function `embed()`, used to embed a lockfile with an R document (via a call to `renv::use()`). * `renv::use()` gains the lockfile argument. This can be useful for R Markdown documents and scripts that you'd like to run with the context for a particular lockfile applied. * `renv::rebuild()` gains the `type` parameter, for parity with `renv::install()`. * Fixed an issue where renv could incorrectly copy an incompatible version of an R package from a site library into the project library during install. (#750) * `renv::dependencies()` can now parse (some) usages of `parsnip::set_engine()`. * `renv::dependencies()` now has improved handling for piped expressions. * Fixed crash during dependency discovery when encountering `box::use()` declarations that use a trailing comma, and no longer treat `.` and `..` as package names. (@klmr) * `renv::clean()` no longer attempts to clean the system library by default. (#737) * Fixed an issue where `install.packages()` could fail when used within an renv project to install a package from local sources. (#724) * The chunk `renv.ignore` parameter can now be set to `FALSE`. When set, renv will explicitly parse dependencies from chunks even if other chunk metadata would have told renv to ignore it (e.g. because it was marked with `eval=F`). (#722) * Fixed an issue where chunks with the chunk option `eval=F` would still be scanned for dependencies. (#421) * In interactive sessions, `renv::use_python()` will now prompt for the version of Python to be used. Python installations in a set of common locations will be searched. See `?renv::use_python()` for more details. * Fixed an issue where renv would fail to retrieve packages from the archives if the lockfile entry was tagged with a `RemoteSha` field. * `renv::restore()` will now prefer restoring packages from the repository named in the `Repository` field if available. The name is matched against the repository names set in the R `repos` option, or as encoded in the renv lockfile. (#701) * renv now supports the discovery of dependencies within interpolated strings as used by the `glue` package. * `RENV_CONFIG_EXTERNAL_LIBRARIES` can now be configured to use multiple library paths, delimited by either `:`, `;`, or `,`. (#700) * renv gains the configuration option, `exported.functions`, controlling which functions and objects are placed on the R search path when renv is attached (e.g. via `library(renv)`). Set this to `NULL` to instruct renv not to place any functions on the search path. This helps avoid issues with, for example, `renv::load()` masking `base::load()`. When set, all usages of renv APIs must be explicitly qualified with the `renv::` prefix. # renv 0.13.2 * `renv::install("user/repo/subdir with spaces")` now works as expected. (#694) * renv can now parse package dependencies declared by `targets::tar_option_set(packages = <...>)`. (#698) * renv no longer performs an automatic snapshot following a user-canceled renv action -- for example, if `renv::restore()` is canceled, the next automatic snapshot will be suppressed. (#697) * Added the `vcs.ignore.local` project setting, to control whether the project's `renv/local` folder is added to renv's VCS ignore file (e.g. `renv/.gitignore`). (#696) * Fixed an issue where renv's bootstrapping code could inadvertently bootstrap with the wrong version of renv, if the source and binary versions of renv on CRAN were not in sync. (#695) * Fixed an issue where `renv::status()` could provide a misleading message for packages which are recorded in the lockfile, but not explicitly required by the project. (#684) # renv 0.13.1 * `renv::clean()` gains the `actions` argument, allowing the caller to control which specific actions are taken during a `clean()` operation. * renv no longer performs an automatic snapshot after a call to `renv::status()`. (#651) * Fixed an issue where attempts to transform RSPM repository URLs could fail if the copy of R was installed without a local `CRAN_mirrors.csv` file. * Fixed an issue where `renv::init()` could fail when passed a relative path to a directory. (#673) * Fixed an issue where `renv::dependencies()` would miss dependencies in R Markdown YAML headers containing multiple output formats. (#674) * `renv::install()` now better respects the `Remotes` field in a project `DESCRIPTION` file, if available. (#670) * `RENV_DOWNLOAD_METHOD` is now treated as an alias for `RENV_DOWNLOAD_FILE_METHOD`. * Fixed an issue where renv would fail to load if the `~/.Rprofile` existed but was an empty file. # renv 0.13.0 * `renv::snapshot()` no longer creates an `renv/activate.R` file in the project folder if one does not already exist. (#655) * The `renv::hydrate()` function gains the `update` argument, used to control whether `renv::hydrate()` chooses to update packages when invoked. When set to `TRUE`, if the version of a package installed in the source library is newer than that of the project library, then renv will copy that version of the package into the project library. (#647) * The `RENV_PATHS_PREFIX_AUTO` environment variable can now be set to instruct renv to include an OS-specific component as part of the library and cache paths. This is primarily useful for Linux systems, where one might want to share a global cache with multiple different operating systems. The path component is constructed from the `ID` and `VERSION_CODENAME` / `VERSION_ID` components of the system's `/etc/os-release` file. * renv's dependency discovery machinery now has preliminary support for packages imported via the [box](https://github.com/klmr/box) package; e.g. `box::use(dplyr[...])`. * Multiple cache paths can now be specified, with each cache path separated by either a `;` or `:`. This can be useful when you'd like to use multiple package caches within the same project; for example, because you'd like to share a read-only cache with a set of projects. (#653, @vandenman) * Fixed an issue where renv could fail to discover dependencies in directories with very large `.gitignore` or `.renvignore` files. (#652) * renv gains a new configuration option, `install.shortcuts`. When enabled, if renv discovers that a package to be installed is already available in the user or site libraries, renv will instead install a copy of that package into the project library. (#636) * renv gains a new function, `renv::use()`, used to download, install, and load a set of packages directly within an R script. `renv::use()` can make it easier to share a standalone R script, with the packages required to install that script embedded directly in the script. It is inspired in part by the [groundhog](https://groundhogr.com/) package. * `renv::install(".")` can now be used to install a package from sources within the current working directory. (#634) * Fixed an issue where `renv::update()` could fail if a package installed from GitHub was missing the `RemoteHost` field in its DESCRIPTION file. (#632) * renv now has support for custom project profiles. Profiles can be used to activate different sets of project libraries + lockfiles for different workflows in a given project. See `vignette("profiles", package = "renv")` for more details. * Fixed an issue where attempts to initialize an renv project in a path containing non-ASCII characters could fail on Windows. (#629) * Fixed an issue where `renv::install("")` could fail if renv chose to install the package from MRAN rather than from one of the active package repositories. (#627) * renv again defaults to using the project's `renv/staging` folder for staged / transactional installs. Use the `RENV_PATHS_LIBRARY_STAGING` environment variable if more granular control over the staging library path is required. This fixes issues on Windows with creating junction points to the global package cache on Windows. (#584) * renv no longer skips downloading a requested source package if an existing cached download exists and appears to be valid. This should help avoid issues when attempting to install a package whose associated tarball has changed remotely. (#504) * During bootstrap, renv will now attempt to download and unpack a binary copy of renv if available from the specified package repositories. * renv now always attempts to bootstrap itself from the R Project's Cloud package repository, as a fallback in case no other repository is available. (#613) * `renv::rebuild()` now uses the latest-available version of the requested package(s) if those packages are not currently installed. * Fixed an issue where `renv::restore(library = "/path/to/lib")` would fail to restore packages, if those packages were already installed on the active library paths (as reported by `.libPaths()`). (#612) * `renv::snapshot()` gains the `reprex` argument. Set this to `TRUE` if you'd like to embed an renv lockfile as part of a reproducible example, as generated by the [`reprex`](https://www.tidyverse.org/help/#reprex-pkg) package. * `renv::status()` now reports packages that are referenced in a project's scripts, but are neither installed in the project library nor recorded in the lockfile. (#588) * Fixed an issue where package installation could fail if the `configure.vars` option was set to be a named character, rather than a named list. (#609) # renv 0.12.5 * Fixed an issue where renv would fail to bootstrap. (#608) # renv 0.12.4 * renv now invalidates the available packages cache if the `https_proxy` environment variable changes. (#579) * `renv::install()` will now install the latest-available version of that package from local sources, if that package is available and newer than any package available on the active package repositories. (#591) * The configuration option `startup.quiet` has been added, allowing one to control whether renv will display the typical startup banner when a project is loaded. * renv now better handles being unloaded and reloaded within the same R session. In particular, warnings related to a corrupted lazy-load database should no longer occur. (#600) * renv no longer reinstalls packages that are already installed and up-to-date in bare calls to `renv::install()`. * renv now uses the R temporary directory for staging, when performing transactional restores / installs. If you need to control the path used for staged installs, please set the `RENV_PATHS_LIBRARY_STAGING` environment variable. * The `install.verbose` configuration option has been added. When set to `TRUE`, renv will stream the output generated by R when performing a package installation. This can be helpful in some cases when diagnosing a failed restore / install. (#330) * Fixed an issue where renv could fail to parse R Markdown chunk headers with an empty label. (#598) * The environment variable `RENV_PATHS_LIBRARY_ROOT_ASIS` can now be used to control whether the project name should be used as-is when forming the library path within the `RENV_PATHS_LIBRARY_ROOT` folder. Set this to `"TRUE"` if you would prefer renv did not append a unique identifier to your project's library path. (#593) * Fixed an issue where GitLab references were not URL encoded. (#590) * renv no longer emits warnings when parsing multi-mode R files that make use of re-used knitr chunks (those specified as `<
}} to force renv to copy packages from the cache, as opposed to symlinking them. If you'd like to disable the cache altogether for a project, you can use: \if{html}{\out{
}}\preformatted{settings$use.cache(FALSE) }\if{html}{\out{
}} to explicitly disable the cache for the project. } \examples{ \dontrun{ # isolate a project renv::isolate() } } renv/man/scaffold.Rd0000644000176200001440000000306714731330073014056 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/scaffold.R \name{scaffold} \alias{scaffold} \title{Generate project infrastructure} \usage{ scaffold( project = NULL, version = NULL, repos = getOption("repos"), settings = NULL ) } \arguments{ \item{project}{The project directory. If \code{NULL}, then the active project will be used. If no project is currently active, then the current working directory is used instead.} \item{version}{The version of renv to associate with this project. By default, the version of renv currently installed is used.} \item{repos}{The \R repositories to associate with this project.} \item{settings}{A list of renv settings, to be applied to the project after creation. These should map setting names to the desired values. See \link{settings} for more details.} } \description{ Create the renv project infrastructure. This will: \itemize{ \item Create a project library, \code{renv/library}. \item Install renv into the project library. \item Update the project \code{.Rprofile} to call \code{source("renv/activate.R")} so that renv is automatically loaded for new \R sessions launched in this project. \item Create \code{renv/.gitignore}, which tells git to ignore the project library. \item Create \code{.Rbuildignore}, if the project is also a package. This tells \verb{R CMD build} to ignore the renv infrastructure, \item Write a (bare) \link{lockfile}, \code{renv.lock}. } } \examples{ \dontrun{ # create scaffolding with 'devtools' ignored renv::scaffold(settings = list(ignored.packages = "devtools")) } } renv/man/imbue.Rd0000644000176200001440000000216714731330073013376 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/imbue.R \name{imbue} \alias{imbue} \title{Imbue an renv Installation} \usage{ imbue(project = NULL, version = NULL, quiet = FALSE) } \arguments{ \item{project}{The project directory. If \code{NULL}, then the active project will be used. If no project is currently active, then the current working directory is used instead.} \item{version}{The version of renv to install. If \code{NULL}, the version of renv currently installed will be used. The requested version of renv will be retrieved from the renv public GitHub repository, at \url{https://github.com/rstudio/renv}.} \item{quiet}{Boolean; avoid printing output during install of renv?} } \value{ The project directory, invisibly. Note that this function is normally called for its side effects. } \description{ Imbue an renv installation into a project, thereby making the requested version of renv available within. } \details{ Normally, this function does not need to be called directly by the user; it will be invoked as required by \code{\link[=init]{init()}} and \code{\link[=activate]{activate()}}. } renv/man/embed.Rd0000644000176200001440000000774214746265552013374 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/embed.R, R/use.R \name{embed} \alias{embed} \alias{use} \title{Capture and re-use dependencies within a \code{.R}, \code{.Rmd} or \code{.qmd}} \usage{ embed(path = NULL, ..., lockfile = NULL, project = NULL) use( ..., lockfile = NULL, library = NULL, isolate = TRUE, sandbox = TRUE, attach = FALSE, verbose = TRUE ) } \arguments{ \item{path}{The path to an \R or R Markdown script. The default will use the current document, if running within RStudio.} \item{...}{The \R packages to be used with this script. Ignored if \code{lockfile} is non-\code{NULL}.} \item{lockfile}{The lockfile to use. When supplied, renv will use the packages as declared in the lockfile.} \item{project}{The project directory. If \code{NULL}, then the active project will be used. If no project is currently active, then the current working directory is used instead.} \item{library}{The library path into which the requested packages should be installed. When \code{NULL} (the default), a library path within the \R temporary directory will be generated and used. Note that this same library path will be re-used on future calls to \code{renv::use()}, allowing \code{renv::use()} to be used multiple times within a single script.} \item{isolate}{Boolean; should the active library paths be included in the set of library paths activated for this script? Set this to \code{TRUE} if you only want the packages provided to \code{renv::use()} to be visible on the library paths.} \item{sandbox}{Should the system library be sandboxed? See the sandbox documentation in \link{config} for more details. You can also provide an explicit sandbox path if you want to configure where \code{renv::use()} generates its sandbox. By default, the sandbox is generated within the \R temporary directory.} \item{attach}{Boolean; should the set of requested packages be automatically attached? If \code{TRUE}, packages will be loaded and attached via a call to \code{\link[=library]{library()}} after install. Ignored if \code{lockfile} is non-\code{NULL}.} \item{verbose}{Boolean; be verbose while installing packages?} } \value{ This function is normally called for its side effects. } \description{ Together, \code{embed()} and \code{use()} provide a lightweight way to specify and restore package versions within a file. \code{use()} is a lightweight lockfile specification that \code{embed()} can automatically generate and insert into a script or document. Calling \code{embed()} inspects the dependencies of the specified document then generates and inserts a call to \code{use()} that looks something like this: \if{html}{\out{
}}\preformatted{renv::use( "digest@0.6.30", "rlang@0.3.4" ) }\if{html}{\out{
}} Then, when you next run your R script or render your \code{.Rmd} or \code{.qmd}, \code{use()} will: \enumerate{ \item Create a temporary library path. \item Install the requested packages and their recursive dependencies into that library. \item Activate the library, so it's used for the rest of the script. } \subsection{Manual usage}{ You can also create calls to \code{use()} yourself, either specifying the packages needed by hand, or by supplying the path to a lockfile, \code{renv::use(lockfile = "/path/to/renv.lock")}. This can be useful in projects where you'd like to associate different lockfiles with different documents, as in a blog where you want each post to capture the dependencies at the time of writing. Once you've finished writing each, the post, you can use \code{renv::snapshot(lockfile = "/path/to/renv.lock")} to "save" the state that was active while authoring that bost, and then use \code{renv::use(lockfile = "/path/to/renv.lock")} in that document to ensure the blog post always uses those dependencies onfuture renders. \code{renv::use()} is inspired in part by the \href{https://groundhogr.com/}{groundhog} package, which also allows one to specify a script's \R package requirements within that same \R script. } } renv/man/diagnostics.Rd0000644000176200001440000000116314731330073014577 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/diagnostics.R \name{diagnostics} \alias{diagnostics} \title{Print a diagnostics report} \usage{ diagnostics(project = NULL) } \arguments{ \item{project}{The project directory. If \code{NULL}, then the active project will be used. If no project is currently active, then the current working directory is used instead.} } \value{ This function is normally called for its side effects. } \description{ Print a diagnostics report, summarizing the state of a project using renv. This report can occasionally be useful when diagnosing issues with renv. } renv/man/checkout.Rd0000644000176200001440000000762714731330073014110 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/checkout.R \name{checkout} \alias{checkout} \title{Checkout a repository} \usage{ checkout( repos = NULL, ..., packages = NULL, date = NULL, clean = FALSE, actions = "restore", restart = NULL, project = NULL ) } \arguments{ \item{repos}{The \R package repositories to use.} \item{...}{Unused arguments, reserved for future expansion. If any arguments are matched to \code{...}, renv will signal an error.} \item{packages}{The packages to be installed. When \code{NULL} (the default), all packages currently used in the project will be installed, as determined by \code{\link[=dependencies]{dependencies()}}. The recursive dependencies of these packages will be included as well.} \item{date}{The snapshot date to use. When set, the associated snapshot as available from the Posit's public \href{https://packagemanager.rstudio.com/}{Package Manager} instance will be used. Ignored if \code{repos} is non-\code{NULL}.} \item{clean}{Boolean; remove packages not recorded in the lockfile from the target library? Use \code{clean = TRUE} if you'd like the library state to exactly reflect the lockfile contents after \code{restore()}.} \item{actions}{The action(s) to perform with the requested repositories. This can either be \code{"snapshot"}, in which \code{renv} will generate a lockfile based on the latest versions of the packages available from \code{repos}, or \code{"restore"} if you'd like to install those packages. You can use \code{c("snapshot", "restore")} if you'd like to generate a lockfile and install those packages in a single call.} \item{restart}{Should the \R session be restarted after the new packages have been checked out? When \code{NULL} (the default), the session is restarted if the \code{"restore"} action was taken.} \item{project}{The project directory. If \code{NULL}, then the active project will be used. If no project is currently active, then the current working directory is used instead.} } \description{ \code{renv::checkout()} can be used to retrieve the latest-available packages from a (set of) package repositories. } \details{ \code{renv::checkout()} is most useful with services like the Posit's \href{https://packagemanager.rstudio.com/}{Package Manager}, as it can be used to switch between different repository snapshots within an renv project. In this way, you can upgrade (or downgrade) all of the packages used in a particular renv project to the package versions provided by a particular snapshot. Note that calling \code{renv::checkout()} will also install the version of \code{renv} available as of the requested snapshot date, which might be older or lack features available in the currently-installed version of \code{renv}. In addition, the project's \code{renv/activate.R} script will be re-generated after checkout. If this is undesired, you can re-install a newer version of \code{renv} after checkout from your regular \R package repository. } \section{Caveats}{ If your library contains packages installed from other remote sources (e.g. GitHub), but a version of a package of the same name is provided by the repositories being checked out, then please be aware that the package will be replaced with the version provided by the requested repositories. This could be a concern if your project uses \R packages from GitHub whose name matches that of an existing CRAN package, but is otherwise unrelated to the package on CRAN. } \examples{ \dontrun{ # check out packages from PPM using the date '2023-01-02' renv::checkout(date = "2023-01-02") # alternatively, supply the full repository path renv::checkout(repos = c(PPM = "https://packagemanager.rstudio.com/cran/2023-01-02")) # only check out some subset of packages (and their recursive dependencies) renv::checkout(packages = "dplyr", date = "2023-01-02") # generate a lockfile based on a snapshot date renv::checkout(date = "2023-01-02", actions = "snapshot") } } renv/man/use_python.Rd0000644000176200001440000001206714731330073014472 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/use-python.R \name{use_python} \alias{use_python} \title{Use python} \usage{ use_python( python = NULL, ..., type = c("auto", "virtualenv", "conda", "system"), name = NULL, project = NULL ) } \arguments{ \item{python}{The path to the version of Python to be used with this project. See \strong{Finding Python} for more details.} \item{...}{Optional arguments; currently unused.} \item{type}{The type of Python environment to use. When \code{"auto"} (the default), virtual environments will be used.} \item{name}{The name or path that should be used for the associated Python environment. If \code{NULL} and \code{python} points to a Python executable living within a pre-existing virtual environment, that environment will be used. Otherwise, a project-local environment will be created instead, using a name generated from the associated version of Python.} \item{project}{The project directory. If \code{NULL}, then the active project will be used. If no project is currently active, then the current working directory is used instead.} } \value{ \code{TRUE}, indicating that the requested version of Python has been successfully activated. Note that this function is normally called for its side effects. } \description{ Associate a version of Python with your project. } \details{ When Python integration is active, renv will: \itemize{ \item Save metadata about the requested version of Python in \code{renv.lock} -- in particular, the Python version, and the Python type ("virtualenv", "conda", "system"), \item Capture the set of installed Python packages during \code{renv::snapshot()}, \item Re-install the set of recorded Python packages during \code{renv::restore()}. } In addition, when the project is loaded, the following actions will be taken: \itemize{ \item The \code{RENV_PYTHON} environment variable will be set, indicating the version of Python currently active for this sessions, \item The \code{RETICULATE_PYTHON} environment variable will be set, so that the reticulate package can automatically use the requested copy of Python as appropriate, \item The requested version of Python will be placed on the \code{PATH}, so that attempts to invoke Python will resolve to the expected version of Python. } You can override the version of Python used in a particular project by setting the \code{RENV_PYTHON} environment variable; e.g. as part of the project's \code{.Renviron} file. This can be useful if you find that renv is unable to automatically discover a compatible version of Python to be used in the project. } \section{Finding Python}{ In interactive sessions, when \code{python = NULL}, renv will prompt for an appropriate version of Python. renv will search a pre-defined set of locations when attempting to find Python installations on the system: \itemize{ \item \code{getOption("renv.python.root")}, \item \verb{/opt/python}, \item \verb{/opt/local/python}, \item \verb{~/opt/python}, \item \verb{/usr/local/opt} (for macOS Homebrew-installed copies of Python), \item \verb{/opt/homebrew/opt} (for M1 macOS Homebrew-installed copies of Python), \item \verb{~/.pyenv/versions}, \item Python instances available on the \code{PATH}. } In non-interactive sessions, renv will first check the \code{RETICULATE_PYTHON} environment variable; if that is unset, renv will look for Python on the \code{PATH}. It is recommended that the version of Python to be used is explicitly supplied for non-interactive usages of \code{use_python()}. } \section{Warning}{ We strongly recommend using Python virtual environments, for a few reasons: \enumerate{ \item If something goes wrong with a local virtual environment, you can safely delete that virtual environment, and then re-initialize it later, without worry that doing so might impact other software on your system. \item If you choose to use a "system" installation of Python, then any packages you install or upgrade will be visible to any other application that wants to use that same Python installation. Using a virtual environment ensures that any changes made are isolated to that environment only. \item Choosing to use Anaconda will likely invite extra frustration in the future, as you may be required to upgrade and manage your Anaconda installation as new versions of Anaconda are released. In addition, Anaconda installations tend to work poorly with software not specifically installed as part of that same Anaconda installation. } In other words, we recommend selecting "system" or "conda" only if you are an expert Python user who is already accustomed to managing Python / Anaconda installations on your own. } \examples{ \dontrun{ # use python with a project renv::use_python() # use python with a project; create the environment # within the project directory in the '.venv' folder renv::use_python(name = ".venv") # use python with a pre-existing virtual environment located elsewhere renv::use_python(name = "~/.virtualenvs/env") # use virtualenv python with a project renv::use_python(type = "virtualenv") # use conda python with a project renv::use_python(type = "conda") } } renv/man/repair.Rd0000644000176200001440000000227214731330073013554 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/repair.R \name{repair} \alias{repair} \title{Repair a project} \usage{ repair(library = NULL, lockfile = NULL, project = NULL) } \arguments{ \item{library}{The \R library to be used. When \code{NULL}, the active project library will be used instead.} \item{lockfile}{The path to a lockfile (if any). When available, renv will use the lockfile when attempting to infer the remote associated with the inaccessible version of each missing package. When \code{NULL} (the default), the project lockfile will be used.} \item{project}{The project directory. If \code{NULL}, then the active project will be used. If no project is currently active, then the current working directory is used instead.} } \description{ Use \code{repair()} to recover from some common issues that can occur with a project. Currently, two operations are performed: } \details{ \enumerate{ \item Packages with broken symlinks into the cache will be re-installed. \item Packages that were installed from sources, but appear to be from an remote source (e.g. GitHub), will have their \code{DESCRIPTION} files updated to record that remote source explicitly. } } renv/man/purge.Rd0000644000176200001440000000343714731330073013420 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/purge.R \name{purge} \alias{purge} \title{Purge packages from the cache} \usage{ purge(package, ..., version = NULL, hash = NULL, prompt = interactive()) } \arguments{ \item{package}{A single package to be removed from the cache.} \item{...}{Unused arguments, reserved for future expansion. If any arguments are matched to \code{...}, renv will signal an error.} \item{version}{The package version to be removed. When \code{NULL}, all versions of the requested package will be removed.} \item{hash}{The specific hashes to be removed. When \code{NULL}, all hashes associated with a particular package's version will be removed.} \item{prompt}{Boolean; prompt the user before taking any action? For backwards compatibility, \code{confirm} is accepted as an alias for \code{prompt}.} } \value{ The set of packages removed from the renv global cache, as a character vector of file paths. } \description{ Purge packages from the cache. This can be useful if a package which had previously been installed in the cache has become corrupted or unusable, and needs to be reinstalled. } \details{ \code{purge()} is an inherently destructive option. It removes packages from the cache, and so any project which had symlinked that package into its own project library would find that package now unavailable. These projects would hence need to reinstall any purged packages. Take heed of this in case you're looking to purge the cache of a package which is difficult to install, or if the original sources for that package are no longer available! } \examples{ \dontrun{ # remove all versions of 'digest' from the cache renv::purge("digest") # remove only a particular version of 'digest' from the cache renv::purge("digest", version = "0.6.19") } } renv/man/config.Rd0000644000176200001440000004214614761163114013546 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/config-defaults.R, R/config.R \docType{data} \name{config} \alias{config} \title{User-level settings} \usage{ config } \description{ Configure different behaviors of renv. } \details{ For a given configuration option: \enumerate{ \item If an \R option of the form \verb{renv.config.} is available, then that option's value will be used; \item If an environment variable of the form \verb{RENV_CONFIG_} is available, then that option's value will be used; \item Otherwise, the default for that particular configuration value is used. } Any periods (\code{.})s in the option name are transformed into underscores (\verb{_}) in the environment variable name, and vice versa. For example, the configuration option \code{auto.snapshot} could be configured as: \itemize{ \item \verb{options(renv.config.auto.snapshot = <...>)} \item \verb{Sys.setenv(RENV_CONFIG_AUTO_SNAPSHOT = <...>)} } Note that if both the \R option and the environment variable are defined, the \R option will be used instead. Environment variables can be more useful when you want a particular configuration to be automatically inherited by child processes; if that behavior is not desired, then the R option may be preferred. If you want to set and persist these options across multiple projects, it is recommended that you set them in a a startup \code{.Renviron} file; e.g. in your own \verb{~/.Renviron}, or in the R installation's \code{etc/Rprofile.site} file. See \link{Startup} for more details. Configuration options can also be set within the project \code{.Rprofile}, but be aware the options should be set before \code{source("renv/activate.R")} is called. } \section{Configuration}{ The following renv configuration options are available: \subsection{renv.config.activate.prompt}{Automatically prompt the user to activate the current project, if it does not appear to already be activated? This is mainly useful to help ensure that calls to \code{renv::snapshot()} and \code{renv::restore()} use the project library. See \code{?renv::activate} for more details. Defaults to \code{TRUE}.} \subsection{renv.config.autoloader.enabled}{Enable the renv auto-loader? When \code{FALSE}, renv will not automatically load a project containing an renv autoloader within its \code{.Rprofile}. In addition, renv will not write out the project auto-loader in calls to \code{renv::init()}. Defaults to \code{TRUE}.} \subsection{renv.config.auto.snapshot}{Automatically snapshot changes to the project library when the project dependencies change? Defaults to \code{FALSE}.} \subsection{renv.config.bitbucket.host}{The default Bitbucket host to be used during package retrieval. Defaults to \code{"api.bitbucket.org/2.0"}.} \subsection{renv.config.copy.method}{The method to use when attempting to copy directories. See \strong{Copy Methods} for more information. Defaults to \code{"auto"}.} \subsection{renv.config.connect.timeout}{The amount of time to spend (in seconds) when attempting to download a file. Only applicable when the \code{curl} downloader is used. Defaults to \code{20L}.} \subsection{renv.config.connect.retry}{The number of times to attempt re-downloading a file, when transient download errors occur. Only applicable when the \code{curl} downloader is used. Defaults to \code{3L}.} \subsection{renv.config.cache.enabled}{Enable the global renv package cache? When active, renv will install packages into a global cache, and link or copy packages from the cache into your \R library as appropriate. This can greatly save on disk space and install time when \R packages are shared across multiple projects in the same environment. Defaults to \code{TRUE}.} \subsection{renv.config.cache.symlinks}{Symlink packages from the global renv package cache into your project library? When \code{TRUE}, renv will use symlinks (or, on Windows, junction points) to reference packages installed in the cache. Set this to \code{FALSE} if you'd prefer to copy packages from the cache into your project library. Enabled by default, except on Windows where this feature is only enabled if the project library and global package cache are on the same volume. Defaults to \code{NULL}.} \subsection{renv.config.dependency.errors}{Many renv APIs require the enumeration of your project's \R package dependencies. This option controls how errors that occur during this enumeration are handled. By default, errors are reported but are non-fatal. Set this to \code{"fatal"} to force errors to be fatal, and \code{"ignored"} to ignore errors altogether. See \code{\link[=dependencies]{dependencies()}} for more details. Defaults to \code{"reported"}.} \subsection{renv.config.dependencies.limit}{By default, renv reports if it discovers more than this many files when looking for dependencies, as that may indicate you are running \code{dependencies()} in the wrong place. Set to \code{Inf} to disable this warning. Defaults to \code{1000L}.} \subsection{renv.config.exported.functions}{When \code{library(renv)} is called, should its exports be placed on the search path? Set this to \code{FALSE} to avoid issues that can arise with, for example, \code{renv::load()} masking \code{base::load()}. In general, we recommend referencing renv functions from its namespace explicitly; e.g. prefer \code{renv::snapshot()} over \code{snapshot()}. By default, all exported renv functions are attached and placed on the search path, for backwards compatibility with existing scripts using renv. Defaults to \code{"*"}.} \subsection{renv.config.external.libraries}{A character vector of external libraries, to be used in tandem with your projects. Be careful when using external libraries: it's possible that things can break within a project if the version(s) of packages used in your project library happen to be incompatible with packages in your external libraries; for example, if your project required \verb{xyz 1.0} but \verb{xyz 1.1} was present and loaded from an external library. Can also be an \R function that provides the paths to external libraries. Library paths will be expanded via \code{\link[=.expand_R_libs_env_var]{.expand_R_libs_env_var()}} as necessary. Defaults to \code{NULL}.} \subsection{renv.config.filebacked.cache}{Enable the renv file-backed cache? When enabled, renv will cache the contents of files that are read (e.g. DESCRIPTION files) in memory, thereby avoiding re-reading the file contents from the filesystem if the file has not changed. renv uses the file \code{mtime} to determine if the file has changed; consider disabling this if \code{mtime} is unreliable on your system. Defaults to \code{TRUE}.} \subsection{renv.config.github.host}{The default GitHub host to be used during package retrieval. Defaults to \code{"api.github.com"}.} \subsection{renv.config.gitlab.host}{The default GitLab host to be used during package retrieval. Defaults to \code{"gitlab.com"}.} \subsection{renv.config.hydrate.libpaths}{A character vector of library paths, to be used by \code{\link[=hydrate]{hydrate()}} when attempting to hydrate projects. When empty, the default set of library paths (as documented in \code{?renv::hydrate}) are used instead. See \code{\link[=hydrate]{hydrate()}} for more details. Defaults to \code{NULL}.} \subsection{renv.config.install.build}{Should downloaded package archives be built (via \verb{R CMD build}) before installation? When TRUE, package vignettes will also be built as part of package installation. Because building packages before installation may require packages within 'Suggests' to be available, this option is not enabled by default. Defaults to \code{FALSE}.} \subsection{renv.config.install.remotes}{Should renv read a package's \verb{Remotes:} field when determining how package dependencies should be installed? Defaults to \code{TRUE}.} \subsection{renv.config.install.shortcuts}{Allow for a set of 'shortcuts' when installing packages with renv? When enabled, if renv discovers that a package to be installed is already available within the user or site libraries, then it will install a local copy of that package. Defaults to \code{TRUE}.} \subsection{renv.config.install.staged}{DEPRECATED: Please use \code{renv.config.install.transactional} instead. Defaults to \code{TRUE}.} \subsection{renv.config.install.transactional}{Perform a transactional install of packages during install and restore? When enabled, renv will first install packages into a temporary library, and later copy or move those packages back into the project library only if all packages were successfully downloaded and installed. This can be useful if you'd like to avoid mutating your project library if installation of one or more packages fails. Defaults to \code{TRUE}.} \subsection{renv.config.install.verbose}{Be verbose when installing R packages from sources? When \code{TRUE}, renv will stream any output generated during package build + installation to the console. Defaults to \code{FALSE}.} \subsection{renv.config.locking.enabled}{Use interprocess locks when invoking methods which might mutate the project library? Enable this to allow multiple processes to use the same renv project, while minimizing risks relating to concurrent access to the project library. Disable this if you encounter locking issues. Locks are stored as files within the project at \code{renv/lock}; if you need to manually remove a stale lock you can do so via \code{unlink("renv/lock", recursive = TRUE)}. Defaults to \code{FALSE}.} \subsection{renv.config.mran.enabled}{DEPRECATED: MRAN is no longer maintained by Microsoft. Defaults to \code{FALSE}.} \subsection{renv.config.pak.enabled}{Use the \href{https://pak.r-lib.org/}{pak} package to install packages? Defaults to \code{FALSE}.} \subsection{renv.config.ppm.enabled}{Boolean; enable \href{https://packagemanager.posit.co/}{Posit Package Manager} integration in renv projects? When \code{TRUE}, renv will attempt to transform repository URLs used by PPM into binary URLs as appropriate for the current Linux platform. Set this to \code{FALSE} if you'd like to continue using source-only PPM URLs, or if you find that renv is improperly transforming your repository URLs. You can still set and use PPM repositories with this option disabled; it only controls whether renv tries to transform source repository URLs into binary URLs on your behalf. Defaults to \code{TRUE}.} \subsection{renv.config.ppm.default}{Boolean; should new projects use the \href{https://packagemanager.posit.co/}{Posit Public Package Manager} instance by default? When \code{TRUE} (the default), projects initialized with \code{renv::init()} will use the P3M instance if the \code{repos} R option has not already been set by some other means (for example, in a startup \code{.Rprofile}). Defaults to \code{TRUE}.} \subsection{renv.config.ppm.url}{The default PPM URL to be used for new renv projects. Defaults to the CRAN mirror maintained by Posit at \url{https://packagemanager.posit.co/}. This option can be changed if you'd like renv to use an alternate package manager instance. Defaults to \code{"https://packagemanager.posit.co/cran/latest"}.} \subsection{renv.config.repos.override}{Override the R package repositories used during \code{\link[=restore]{restore()}}? Primarily useful for deployment / continuous integration, where you might want to enforce the usage of some set of repositories over what is defined in \code{renv.lock} or otherwise set by the R session. Defaults to \code{NULL}.} \subsection{renv.config.rspm.enabled}{DEPRECATED: Please use \code{renv.config.ppm.enabled} instead. Defaults to \code{TRUE}.} \subsection{renv.config.sandbox.enabled}{Enable sandboxing for renv projects? When active, renv will attempt to sandbox the system library, preventing non-system packages installed in the system library from becoming available in renv projects. (That is, only packages with priority \code{"base"} or \code{"recommended"}, as reported by \code{installed.packages()}, are made available.) Sandboxing is done by linking or copying system packages into a separate library path, and then instructing R to use that library path as the system library path. In some environments, this action can take a large amount of time -- in such a case, you may want to disable the renv sandbox. Defaults to \code{TRUE}.} \subsection{renv.config.shims.enabled}{Should renv shims be installed on package load? When enabled, renv will install its own shims over the functions \code{install.packages()}, \code{update.packages()} and \code{remove.packages()}, delegating these functions to \code{\link[=install]{install()}}, \code{\link[=update]{update()}} and \code{\link[=remove]{remove()}} as appropriate. Defaults to \code{TRUE}.} \subsection{renv.config.snapshot.inference}{For packages which were installed from local sources, should renv try to infer the package's remote from its DESCRIPTION file? When \code{TRUE}, renv will check and prompt you to update the package's DESCRIPTION file if the remote source can be ascertained. Currently, this is only implemented for packages hosted on GitHub. Note that this check is only performed in interactive R sessions. Defaults to \code{TRUE}.} \subsection{renv.config.snapshot.validate}{Validate \R package dependencies when calling snapshot? When \code{TRUE}, renv will attempt to diagnose potential issues in the project library before creating \code{renv.lock} -- for example, if a package installed in the project library depends on a package which is not currently installed. Defaults to \code{TRUE}.} \subsection{renv.config.startup.quiet}{Be quiet during startup? When set, renv will not display the typical \verb{Project loaded. [renv ]} banner on startup. Defaults to \code{NULL}.} \subsection{renv.config.synchronized.check}{Check that the project library is synchronized with the lockfile on load? Defaults to \code{TRUE}.} \subsection{renv.config.sysreqs.check}{Check whether the requisite system packages are installed during package installation and restore? This feature uses the R System Requirements database maintained at \url{https://github.com/rstudio/r-system-requirements}. Defaults to \code{TRUE}.} \subsection{renv.config.updates.check}{Check for package updates when the session is initialized? This can be useful if you'd like to ensure that your project lockfile remains up-to-date with packages as they are released on CRAN. Defaults to \code{FALSE}.} \subsection{renv.config.updates.parallel}{Check for package updates in parallel? This can be useful when a large number of packages installed from non-CRAN remotes are installed, as these packages can then be checked for updates in parallel. Defaults to \code{2L}.} \subsection{renv.config.user.environ}{Load the user R environ (typically located at \verb{~/.Renviron}) when renv is loaded? Defaults to \code{TRUE}.} \subsection{renv.config.user.library}{Include the system library on the library paths for your projects? Note that this risks breaking project encapsulation and is not recommended for projects which you intend to share or collaborate on with other users. See also the caveats for the \code{renv.config.external.libraries} option. Defaults to \code{FALSE}.} \subsection{renv.config.user.profile}{Load the user R profile (typically located at \verb{~/.Rprofile}) when renv is loaded? This is disabled by default, as running arbitrary code from the the user \verb{~/.Rprofile} could risk breaking project encapsulation. If your goal is to set environment variables that are visible within all renv projects, then placing those in \verb{~/.Renviron} is often a better choice. Defaults to \code{FALSE}.} } \section{Copy methods}{ If you find that renv is unable to copy some directories in your environment, you may want to try setting the \code{copy.method} option. By default, renv will try to choose a system tool that is likely to succeed in copying files on your system -- \code{robocopy} on Windows, and \code{cp} on Unix. renv will also instruct these tools to preserve timestamps and attributes when copying files. However, you can select a different method as appropriate. The following methods are supported: \tabular{ll}{ \code{auto} \tab Use \code{robocopy} on Windows, and \code{cp} on Unix-alikes. \cr \code{R} \tab Use \R's built-in \code{file.copy()} function. \cr \code{cp} \tab Use \code{cp} to copy files. \cr \code{robocopy} \tab Use \code{robocopy} to copy files. (Only available on Windows.) \cr \code{rsync} \tab Use \code{rsync} to copy files. \cr } You can also provide a custom copy method if required; e.g. \if{html}{\out{
}}\preformatted{options(renv.config.copy.method = function(src, dst) \{ # copy a file from 'src' to 'dst' \}) }\if{html}{\out{
}} Note that renv will always first attempt to copy a directory first to a temporary path within the target folder, and then rename that temporary path to the final target destination. This helps avoid issues where a failed attempt to copy a directory could leave a half-copied directory behind in the final location. } \section{Project-local settings}{ For settings that should persist alongside a particular project, the various settings available in \link{settings} can be used. } \examples{ # disable automatic snapshots options(renv.config.auto.snapshot = FALSE) # disable with environment variable Sys.setenv(RENV_CONFIG_AUTO_SNAPSHOT = FALSE) } \keyword{datasets} renv/man/clean.Rd0000644000176200001440000000477514731330073013366 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/clean.R \name{clean} \alias{clean} \title{Clean a project} \usage{ clean(project = NULL, ..., actions = NULL, prompt = interactive()) } \arguments{ \item{project}{The project directory. If \code{NULL}, then the active project will be used. If no project is currently active, then the current working directory is used instead.} \item{...}{Unused arguments, reserved for future expansion. If any arguments are matched to \code{...}, renv will signal an error.} \item{actions}{The set of clean actions to take. See the documentation in \strong{Actions} for a list of available actions, and the default actions taken when no actions are supplied.} \item{prompt}{Boolean; prompt the user before taking any action? For backwards compatibility, \code{confirm} is accepted as an alias for \code{prompt}.} } \value{ The project directory, invisibly. Note that this function is normally called for its side effects. } \description{ Clean up a project and its associated \R libraries. } \section{Actions}{ The following clean actions are available: \describe{ \item{\code{package.locks}}{ During package installation, \R will create package locks in the library path, typically named \verb{00LOCK-}. On occasion, if package installation fails or \R is terminated while installing a package, these locks can be left behind and will inhibit future attempts to reinstall that package. Use this action to remove such left-over package locks. } \item{\code{library.tempdirs}}{ During package installation, \R may create temporary directories with names of the form \verb{file\\w\{12\}}, and on occasion those files can be left behind even after they are no longer in use. Use this action to remove such left-over directories. } \item{\code{system.library}}{ In general, it is recommended that only packages distributed with \R are installed into the default library (the library path referred to by \code{.Library}). Use this action to remove any user-installed packages that have been installed to the system library. Because this action is destructive, it is by default never run -- it must be explicitly requested by the user. } \item{\code{unused.packages}}{ Remove packages that are installed in the project library, but no longer appear to be used in the project sources. Because this action is destructive, it is by default only run in interactive sessions when prompting is enabled. } } } \examples{ \dontrun{ # clean the current project renv::clean() } } renv/man/record.Rd0000644000176200001440000000334314731330073013550 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/record.R \name{record} \alias{record} \title{Update package records in a lockfile} \usage{ record(records, lockfile = NULL, project = NULL) } \arguments{ \item{records}{A list of named records, mapping package names to a definition of their source. See \strong{Records} for more details.} \item{lockfile}{Path to a lockfile. When \code{NULL} (the default), the \code{renv.lock} located in the root of the current project will be used.} \item{project}{The project directory. If \code{NULL}, then the active project will be used. If no project is currently active, then the current working directory is used instead.} } \description{ Use \code{record()} to record a new entry within an existing renv lockfile. } \details{ This function can be useful when you need to change one or more of the package records within an renv lockfile -- for example, because a recorded package cannot be restored in a particular environment, and you know of a suitable alternative. } \section{Records}{ Records can be provided either using the \strong{remotes} short-hand syntax, or by using an \R list of entries to record within the lockfile. See \code{?lockfiles} for more information on the structure of a package record. } \examples{ \dontrun{ # use digest 0.6.22 from package repositories -- different ways # of specifying the remote. use whichever is most natural renv::record("digest@0.6.22") renv::record(list(digest = "0.6.22")) renv::record(list(digest = "digest@0.6.22")) # alternatively, provide a full record as a list digest_record <- list( Package = "digest", Version = "0.6.22", Source = "Repository", Repository = "CRAN" ) renv::record(list(digest = digest_record)) } } renv/man/snapshot.Rd0000644000176200001440000001620514742242046014136 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/snapshot.R \name{snapshot} \alias{snapshot} \title{Record current state of the project library in the lockfile} \usage{ snapshot( project = NULL, ..., library = NULL, lockfile = paths$lockfile(project = project), type = settings$snapshot.type(project = project), dev = FALSE, repos = getOption("repos"), packages = NULL, exclude = NULL, prompt = interactive(), update = FALSE, force = FALSE, reprex = FALSE ) } \arguments{ \item{project}{The project directory. If \code{NULL}, then the active project will be used. If no project is currently active, then the current working directory is used instead.} \item{...}{Unused arguments, reserved for future expansion. If any arguments are matched to \code{...}, renv will signal an error.} \item{library}{The \R libraries to snapshot. When \code{NULL}, the active \R libraries (as reported by \code{.libPaths()}) are used.} \item{lockfile}{The location where the generated lockfile should be written. By default, the lockfile is written to a file called \code{renv.lock} in the project directory. When \code{NULL}, the lockfile (as an \R object) is returned directly instead.} \item{type}{The type of snapshot to perform: \itemize{ \item \code{"implicit"}, (the default), uses all packages captured by \code{\link[=dependencies]{dependencies()}}. \item \code{"explicit"} uses packages recorded in \code{DESCRIPTION}. \item \code{"all"} uses all packages in the project library. \item \code{"custom"} uses a custom filter. } See \strong{Snapshot types} below for more details.} \item{dev}{Boolean; include development dependencies? These packages are typically required when developing the project, but not when running it (i.e. you want them installed when humans are working on the project but not when computers are deploying it). Development dependencies include packages listed in the \code{Suggests} field of a \code{DESCRIPTION} found in the project root, and roxygen2 or devtools if their use is implied by other project metadata. They also include packages used in \verb{~/.Rprofile} if \code{config$user.profile()} is \code{TRUE}.} \item{repos}{The \R repositories to be recorded in the lockfile. Defaults to the currently active package repositories, as retrieved by \code{getOption("repos")}.} \item{packages}{A vector of packages to be included in the lockfile. When \code{NULL} (the default), all packages relevant for the type of snapshot being performed will be included. When set, the \code{type} argument is ignored. Recursive dependencies of the specified packages will be added to the lockfile as well.} \item{exclude}{A vector of packages to be explicitly excluded from the lockfile. Note that transitive package dependencies will always be included, to avoid potentially creating an incomplete / non-functional lockfile.} \item{prompt}{Boolean; prompt the user before taking any action? For backwards compatibility, \code{confirm} is accepted as an alias for \code{prompt}.} \item{update}{Boolean; if the lockfile already exists, then attempt to update that lockfile without removing any prior package records.} \item{force}{Boolean; force generation of a lockfile even when pre-flight validation checks have failed?} \item{reprex}{Boolean; generate output appropriate for embedding the lockfile as part of a \href{https://www.tidyverse.org/help/#reprex}{reprex}?} } \value{ The generated lockfile, as an \R object (invisibly). Note that this function is normally called for its side effects. } \description{ Call \code{renv::snapshot()} to update a \link{lockfile} with the current state of dependencies in the project library. The lockfile can be used to later \link{restore} these dependencies as required. It's also possible to call \code{renv::snapshot()} with a non-renv project, in which case it will record the current state of dependencies in the current library paths. This makes it possible to \link{restore} the current packages, providing lightweight portability and reproducibility without isolation. If you want to automatically snapshot after each change, you can set \code{config$config$auto.snapshot(TRUE)} -- see \code{?config} for more details. } \section{Snapshot types}{ Depending on how you prefer to manage your \R package dependencies, you may want to enable an alternate snapshot type.. The types available are as follows: \describe{ \item{\code{"implicit"}}{ (The default) Capture only packages which appear to be used in your project, as determined by \code{renv::dependencies()}. This ensures that only the packages actually required by your project will enter the lockfile; the downside if it might be slow if your project contains a large number of files. If speed becomes an issue, you might consider using \code{.renvignore} files to limit which files renv uses for dependency discovery, or switching to explicit mode, as described next. } \item{\code{"explicit"}}{ Only capture packages which are explicitly listed in the project \code{DESCRIPTION} file. This workflow is recommended for users who wish to manage their project's \R package dependencies directly, and can be used for both package and non-package \R projects. Packages used in this manner should be recorded in either the \code{Depends} or \code{Imports} field of the \code{DESCRIPTION} file. } \item{\code{"all"}}{ Capture all packages within the active \R libraries in the lockfile. This is the quickest and simplest method, but may lead to undesired packages (e.g. development dependencies) entering the lockfile. } \item{\code{"custom"}}{ Like \code{"implicit"}, but use a custom user-defined filter instead. The filter should be specified by the \R option \code{renv.snapshot.filter}, and should either be a character vector naming a function (e.g. \code{"package::method"}), or be a function itself. The function should only accept one argument (the project directory), and should return a vector of package names to include in the lockfile. } } You can change the snapshot type for the current project with \code{\link[=settings]{settings()}}. For example, the following code will switch to using \code{"explicit"} snapshots: \if{html}{\out{
}}\preformatted{renv::settings$snapshot.type("explicit") }\if{html}{\out{
}} When the \code{packages} argument is set, \code{type} is ignored, and instead only the requested set of packages, and their recursive dependencies, will be written to the lockfile. } \examples{ \dontrun{ # disable automatic snapshots auto.snapshot <- getOption("renv.config.auto.snapshot") options(renv.config.auto.snapshot = FALSE) # initialize a new project (with an empty R library) renv::init(bare = TRUE) # install digest 0.6.19 renv::install("digest@0.6.19") # save library state to lockfile renv::snapshot() # remove digest from library renv::remove("digest") # check library status renv::status() # restore lockfile, thereby reinstalling digest 0.6.19 renv::restore() # restore automatic snapshots options(renv.config.auto.snapshot = auto.snapshot) } } \seealso{ More on handling package \code{\link[=dependencies]{dependencies()}} Other reproducibility: \code{\link{lockfiles}}, \code{\link{restore}()} } \concept{reproducibility} renv/man/remove.Rd0000644000176200001440000000275314731330073013573 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/remove.R \name{remove} \alias{remove} \title{Remove packages} \usage{ remove(packages, ..., library = NULL, project = NULL) } \arguments{ \item{packages}{A character vector of \R packages to remove.} \item{...}{Unused arguments, reserved for future expansion. If any arguments are matched to \code{...}, renv will signal an error.} \item{library}{The library from which packages should be removed. When \code{NULL}, the active library (that is, the first entry reported in \code{.libPaths()}) is used instead.} \item{project}{The project directory. If \code{NULL}, then the active project will be used. If no project is currently active, then the current working directory is used instead.} } \value{ A vector of package records, describing the packages (if any) which were successfully removed. } \description{ Remove (uninstall) \R packages. } \examples{ \dontrun{ # disable automatic snapshots auto.snapshot <- getOption("renv.config.auto.snapshot") options(renv.config.auto.snapshot = FALSE) # initialize a new project (with an empty R library) renv::init(bare = TRUE) # install digest 0.6.19 renv::install("digest@0.6.19") # save library state to lockfile renv::snapshot() # remove digest from library renv::remove("digest") # check library status renv::status() # restore lockfile, thereby reinstalling digest 0.6.19 renv::restore() # restore automatic snapshots options(renv.config.auto.snapshot = auto.snapshot) } } renv/man/dependencies.Rd0000644000176200001440000001636614742242046014735 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/dependencies.R \name{dependencies} \alias{dependencies} \title{Find R package dependencies in a project} \usage{ dependencies( path = getwd(), root = NULL, ..., quiet = NULL, progress = TRUE, errors = c("reported", "fatal", "ignored"), dev = FALSE ) } \arguments{ \item{path}{The path to a \code{.R}, \code{.Rmd}, \code{.qmd}, \code{DESCRIPTION}, a directory containing such files, or an R function. The default uses all files found within the current working directory and its children.} \item{root}{The root directory to be used for dependency discovery. Defaults to the active project directory. You may need to set this explicitly to ensure that your project's \code{.renvignore}s (if any) are properly handled.} \item{...}{Unused arguments, reserved for future expansion. If any arguments are matched to \code{...}, renv will signal an error.} \item{quiet}{Boolean; be quiet while checking for dependencies? Setting \code{quiet = TRUE} is equivalent to setting \code{progress = FALSE} and \code{errors = "ignored"}, and overrides those options when not \code{NULL}.} \item{progress}{Boolean; report progress output while enumerating dependencies?} \item{errors}{How should errors that occur during dependency enumeration be handled? \itemize{ \item \code{"reported"} (the default): errors are reported to the user, but otherwise ignored. \item \code{"fatal"}: errors are fatal and stop execution. \item \code{"ignored"}: errors are ignored and not reported to the user. }} \item{dev}{Boolean; include development dependencies? These packages are typically required when developing the project, but not when running it (i.e. you want them installed when humans are working on the project but not when computers are deploying it). Development dependencies include packages listed in the \code{Suggests} field of a \code{DESCRIPTION} found in the project root, and roxygen2 or devtools if their use is implied by other project metadata. They also include packages used in \verb{~/.Rprofile} if \code{config$user.profile()} is \code{TRUE}.} } \value{ An \R \code{data.frame} of discovered dependencies, mapping inferred package names to the files in which they were discovered. Note that the \code{Package} field might name a package remote, rather than just a plain package name. } \description{ \code{dependencies()} will scan files within your project, looking for \R files and the packages used within those \R files. This is done primarily by parsing the code and looking for calls of the form \code{library(package)}, \code{require(package)}, \code{requireNamespace("package")}, and \code{package::method()}. renv also supports package loading with \href{https://cran.r-project.org/package=box}{box} (\code{box::use(...)}) and \href{https://cran.r-project.org/package=pacman}{pacman} (\code{pacman::p_load(...)}). For \R package projects, \code{renv} will also detect dependencies expressed in the \code{DESCRIPTION} file. For projects using Python, \R dependencies within the \R code chunks of your project's \code{.ipynb} files will also be used. Note that the \code{\link[rmarkdown:rmarkdown-package]{rmarkdown}} package is required in order to scan dependencies in R Markdown files. } \section{Missing dependencies}{ \code{dependencies()} uses static analysis to determine which packages are used by your project. This means that it inspects, but doesn't run, the \R code in your project. Static analysis generally works well, but is not 100\% reliable in detecting the packages required by your project. For example, \code{renv} is unable to detect this kind of usage: \if{html}{\out{
}}\preformatted{for (package in c("dplyr", "ggplot2")) \{ library(package, character.only = TRUE) \} }\if{html}{\out{
}} It also can't generally tell if one of the packages you use, uses one of its suggested packages. For example, the \code{tidyr::separate_wider_delim()} function requires the \code{stringr} package, but \code{stringr} is only suggested, not required, by \code{tidyr}. If you find that renv's dependency discovery misses one or more packages that you actually use in your project, one escape hatch is to include a file called \verb{_dependencies.R} that includes straightforward library calls: \if{html}{\out{
}}\preformatted{library(dplyr) library(ggplot2) library(stringr) }\if{html}{\out{
}} } \section{Ignoring files}{ By default, renv will read your project's \code{.gitignore}s (if present) to determine whether certain files or folders should be included when traversing directories. If preferred, you can also create a \code{.renvignore} file (with entries of the same format as a standard \code{.gitignore} file) to tell renv which files to ignore within a directory. If both \code{.renvignore} and \code{.gitignore} exist within a folder, the \code{.renvignore} will be used in lieu of the \code{.gitignore}. See \url{https://git-scm.com/docs/gitignore} for documentation on the \code{.gitignore} format. Some simple examples here: \if{html}{\out{
}}\preformatted{# ignore all R Markdown files *.Rmd # ignore all data folders data/ # ignore only data folders from the root of the project /data/ }\if{html}{\out{
}} Using ignore files is important if your project contains a large number of files; for example, if you have a \verb{data/} directory containing many text files. } \section{Errors}{ renv's attempts to enumerate package dependencies in your project can fail -- most commonly, because of failures when attempting to parse your \R code. You can use the \code{errors} argument to suppress these problems, but a more robust solution is tell renv not to look at the problematic code. As well as using \code{.renvignore}, as described above, you can also suppress errors discovered within individual \code{.Rmd} chunks by including \code{renv.ignore=TRUE} in the chunk header. For example: \if{html}{\out{
}}\preformatted{```\{r chunk-label, renv.ignore=TRUE\} # code in this chunk will be ignored by renv ``` }\if{html}{\out{
}} Similarly, if you'd like renv to parse a chunk that is otherwise ignored (e.g. because it has \code{eval=FALSE} as a chunk header), you can set: \if{html}{\out{
}}\preformatted{```\{r chunk-label, eval=FALSE, renv.ignore=FALSE\} # code in this chunk will _not_ be ignored ``` }\if{html}{\out{
}} } \section{Development dependencies}{ renv has some support for distinguishing between development and run-time dependencies. For example, your Shiny app might rely on \href{https://ggplot2.tidyverse.org}{ggplot2} (a run-time dependency) but while you use \href{https://usethis.r-lib.org}{usethis} during development, your app doesn't need it to run (i.e. it's only a development dependency). You can record development dependencies by listing them in the \code{Suggests} field of your project's \code{DESCRIPTION} file. Development dependencies will be installed by \code{\link[=install]{install()}} (when called without arguments) but will not be tracked in the project snapshot. If you need greater control, you can also try project profiles as discussed in \code{vignette("profiles")}. } \examples{ \dontrun{ # find R package dependencies in the current directory renv::dependencies() } } renv/man/paths.Rd0000644000176200001440000001526314731330073013415 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/paths.R \docType{data} \name{paths} \alias{paths} \title{Path for storing global state} \usage{ paths } \description{ By default, renv stores global state in the following OS-specific folders: \tabular{ll}{ \strong{Platform} \tab \strong{Location} \cr Linux \tab \verb{~/.cache/R/renv} \cr macOS \tab \verb{~/Library/Caches/org.R-project.R/R/renv} \cr Windows \tab \verb{\%LOCALAPPDATA\%/R/cache/R/renv} \cr } If desired, this path can be customized by setting the \code{RENV_PATHS_ROOT} environment variable. This can be useful if you'd like, for example, multiple users to be able to share a single global cache. } \section{Customising individual paths}{ The various state sub-directories can also be individually adjusted, if so desired (e.g. you'd prefer to keep the cache of package installations on a separate volume). The various environment variables that can be set are enumerated below: \tabular{ll}{ \strong{Environment Variable} \tab \strong{Description} \cr \code{RENV_PATHS_ROOT} \tab The root path used for global state storage. \cr \code{RENV_PATHS_LIBRARY} \tab The path to the project library. \cr \code{RENV_PATHS_LIBRARY_ROOT} \tab The parent path for project libraries. \cr \code{RENV_PATHS_LIBRARY_STAGING} \tab The parent path used for staged package installs. \cr \code{RENV_PATHS_SANDBOX} \tab The path to the sandboxed \R system library. \cr \code{RENV_PATHS_LOCKFILE} \tab The path to the \link{lockfile}. \cr \code{RENV_PATHS_CELLAR} \tab The path to the cellar, containing local package binaries and sources. \cr \code{RENV_PATHS_SOURCE} \tab The path containing downloaded package sources. \cr \code{RENV_PATHS_BINARY} \tab The path containing downloaded package binaries. \cr \code{RENV_PATHS_CACHE} \tab The path containing cached package installations. \cr \code{RENV_PATHS_PREFIX} \tab An optional prefix to prepend to the constructed library / cache paths. \cr \code{RENV_PATHS_RENV} \tab The path to the project's renv folder. For advanced users only. \cr \code{RENV_PATHS_RTOOLS} \tab (Windows only) The path to \href{https://cran.r-project.org/bin/windows/Rtools/}{Rtools}. \cr \code{RENV_PATHS_EXTSOFT} \tab (Windows only) The path containing external software needed for compilation of Windows source packages. \cr } (If you want these settings to persist in your project, it is recommended that you add these to an appropriate \R startup file. For example, these could be set in: a project-local \code{.Renviron}, the user-level \code{.Renviron}, or a site-wide file at \code{file.path(R.home("etc"), "Renviron.site")}. See \link{Startup} for more details). Note that renv will append platform-specific and version-specific entries to the set paths as appropriate. For example, if you have set: \if{html}{\out{
}}\preformatted{Sys.setenv(RENV_PATHS_CACHE = "/mnt/shared/renv/cache") }\if{html}{\out{
}} then the directory used for the cache will still depend on the renv cache version (e.g. \code{v2}), the \R version (e.g. \code{3.5}) and the platform (e.g. \code{x86_64-pc-linux-gnu}). For example: \if{html}{\out{
}}\preformatted{/mnt/shared/renv/cache/v2/R-3.5/x86_64-pc-linux-gnu }\if{html}{\out{
}} This ensures that you can set a single \code{RENV_PATHS_CACHE} environment variable globally without worry that it may cause collisions or errors if multiple versions of \R needed to interact with the same cache. If reproducibility of a project is desired on a particular machine, it is highly recommended that the renv cache of installed packages + binary packages is backed up and persisted, so that packages can be easily restored in the future -- installation of packages from source can often be arduous. } \section{Sharing state across operating systems}{ If you need to share the same cache with multiple different Linux operating systems, you may want to set the \code{RENV_PATHS_PREFIX} environment variable to help disambiguate the paths used on Linux. For example, setting \code{RENV_PATHS_PREFIX = "ubuntu-bionic"} would instruct renv to construct a cache path like: \if{html}{\out{
}}\preformatted{/mnt/shared/renv/cache/v2/ubuntu-bionic/R-3.5/x86_64-pc-linux-gnu }\if{html}{\out{
}} If this is required, it's strongly recommended that this environment variable is set in your \R installation's \code{Renviron.site} file, typically located at \code{file.path(R.home("etc"), "Renviron.site")}, so that it can be active for any \R sessions launched on that machine. Starting from \verb{renv 0.13.0}, you can also instruct renv to auto-generate an OS-specific component to include as part of library and cache paths, by setting the environment variable: \if{html}{\out{
}}\preformatted{RENV_PATHS_PREFIX_AUTO = TRUE }\if{html}{\out{
}} The prefix will be constructed based on fields within the system's \verb{/etc/os-release} file. Note that this is the default behavior with \verb{renv 1.0.6} when using R 4.4.0 or later. } \section{Package cellar}{ If your project depends on one or more \R packages that are not available in any remote location, you can still provide a locally-available tarball for renv to use during restore. By default, these packages should be made available in the folder as specified by the \code{RENV_PATHS_CELLAR} environment variable. The package sources should be placed in a file at one of these locations: \itemize{ \item \verb{$\{RENV_PATHS_CELLAR\}/_.} \item \verb{$\{RENV_PATHS_CELLAR\}//_.} \item \verb{/renv/cellar/_.} \item \verb{/renv/cellar//_.} } where \verb{.} is \code{.tar.gz} for source packages, or \code{.tgz} for binaries on macOS and \code{.zip} for binaries on Windows. During \code{restore()}, renv will search the cellar for a compatible package, and prefer installation with that copy of the package if appropriate. } \section{Older versions}{ Older version of renv used a different default cache location. Those cache locations are: \tabular{ll}{ \strong{Platform} \tab \strong{Location} \cr Linux \tab \verb{~/.local/share/renv} \cr macOS \tab \verb{~/Library/Application Support/renv} \cr Windows \tab \verb{\%LOCALAPPDATA\%/renv} \cr } If an renv root directory has already been created in one of the old locations, that will still be used. This change was made to comply with the CRAN policy requirements of \R packages. } \examples{ # get the path to the project library path <- renv::paths$library() } \keyword{datasets} renv/man/lockfiles.Rd0000644000176200001440000001366014742242046014254 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/lockfiles.R \name{lockfiles} \alias{lockfiles} \alias{lockfile_create} \alias{lockfile_read} \alias{lockfile_write} \alias{lockfile_modify} \title{Lockfiles} \usage{ lockfile_create( type = settings$snapshot.type(project = project), libpaths = .libPaths(), packages = NULL, exclude = NULL, prompt = interactive(), force = FALSE, ..., project = NULL ) lockfile_read(file = NULL, ..., project = NULL) lockfile_write(lockfile, file = NULL, ..., project = NULL) lockfile_modify( lockfile = NULL, ..., remotes = NULL, repos = NULL, project = NULL ) } \arguments{ \item{type}{The type of snapshot to perform: \itemize{ \item \code{"implicit"}, (the default), uses all packages captured by \code{\link[=dependencies]{dependencies()}}. \item \code{"explicit"} uses packages recorded in \code{DESCRIPTION}. \item \code{"all"} uses all packages in the project library. \item \code{"custom"} uses a custom filter. } See \strong{Snapshot types} below for more details.} \item{libpaths}{The library paths to be used when generating the lockfile.} \item{packages}{A vector of packages to be included in the lockfile. When \code{NULL} (the default), all packages relevant for the type of snapshot being performed will be included. When set, the \code{type} argument is ignored. Recursive dependencies of the specified packages will be added to the lockfile as well.} \item{exclude}{A vector of packages to be explicitly excluded from the lockfile. Note that transitive package dependencies will always be included, to avoid potentially creating an incomplete / non-functional lockfile.} \item{prompt}{Boolean; prompt the user before taking any action? For backwards compatibility, \code{confirm} is accepted as an alias for \code{prompt}.} \item{force}{Boolean; force generation of a lockfile even when pre-flight validation checks have failed?} \item{...}{Unused arguments, reserved for future expansion. If any arguments are matched to \code{...}, renv will signal an error.} \item{project}{The project directory. If \code{NULL}, then the active project will be used. If no project is currently active, then the current working directory is used instead.} \item{file}{A file path, or \R connection.} \item{lockfile}{An \code{renv} lockfile; typically created by either \code{lockfile_create()} or \code{lockfile_read()}.} \item{remotes}{An \R vector of remote specifications.} \item{repos}{A named vector, mapping \R repository names to their URLs.} } \description{ A \strong{lockfile} records the state of a project at some point in time. } \details{ A lockfile captures the state of a project's library at some point in time. In particular, the package names, their versions, and their sources (when known) are recorded in the lockfile. Projects can be restored from a lockfile using the \code{\link[=restore]{restore()}} function. This implies reinstalling packages into the project's private library, as encoded within the lockfile. While lockfiles are normally generated and used with \code{\link[=snapshot]{snapshot()}} / \code{\link[=restore]{restore()}}, they can also be edited by hand if so desired. Lockfiles are written as \code{.json}, to allow for easy consumption by other tools. An example lockfile follows: \if{html}{\out{
}}\preformatted{\{ "R": \{ "Version": "3.6.1", "Repositories": [ \{ "Name": "CRAN", "URL": "https://cloud.r-project.org" \} ] \}, "Packages": \{ "markdown": \{ "Package": "markdown", "Version": "1.0", "Source": "Repository", "Repository": "CRAN", "Hash": "4584a57f565dd7987d59dda3a02cfb41" \}, "mime": \{ "Package": "mime", "Version": "0.7", "Source": "Repository", "Repository": "CRAN", "Hash": "908d95ccbfd1dd274073ef07a7c93934" \} \} \} }\if{html}{\out{
}} The sections used within a lockfile are described next. \subsection{renv}{ Information about the version of renv used to manage this project. \tabular{ll}{ \strong{Version} \tab The version of the renv package used with this project. \cr } } \subsection{R}{ Properties related to the version of \R associated with this project. \tabular{ll}{ \strong{Version} \tab The version of \R used. \cr \strong{Repositories} \tab The \R repositories used in this project. \cr } } \subsection{Packages}{ \R package records, capturing the packages used or required by a project at the time when the lockfile was generated. \tabular{ll}{ \strong{Package} \tab The package name. \cr \strong{Version} \tab The package version. \cr \strong{Source} \tab The location from which this package was retrieved. \cr \strong{Repository} \tab The name of the repository (if any) from which this package was retrieved. \cr \strong{Hash} \tab (Optional) A unique hash for this package, used for package caching. \cr } Additional remote fields, further describing how the package can be retrieved from its corresponding source, will also be included as appropriate (e.g. for packages installed from GitHub). } \subsection{Python}{ Metadata related to the version of Python used with this project (if any). \tabular{ll}{ \strong{Version} \tab The version of Python being used. \cr \strong{Type} \tab The type of Python environment being used ("virtualenv", "conda", "system") \cr \strong{Name} \tab The (optional) name of the environment being used. } Note that the \code{Name} field may be empty. In that case, a project-local Python environment will be used instead (when not directly using a system copy of Python). } } \section{Caveats}{ These functions are primarily intended for expert users -- in most cases, \code{\link[=snapshot]{snapshot()}} and \code{\link[=restore]{restore()}} are the primariy tools you will need when creating and using lockfiles. } \seealso{ Other reproducibility: \code{\link{restore}()}, \code{\link{snapshot}()} } \concept{reproducibility} renv/man/sysreqs.Rd0000644000176200001440000000612314761163114014005 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/sysreqs.R \name{sysreqs} \alias{sysreqs} \title{R System Requirements} \usage{ sysreqs( packages = NULL, ..., local = FALSE, check = NULL, report = TRUE, distro = NULL, collapse = FALSE, project = NULL ) } \arguments{ \item{packages}{A vector of \R package names. When \code{NULL} (the default), the project's package dependencies as reported via \code{\link[=dependencies]{dependencies()}} are used.} \item{...}{Unused arguments, reserved for future expansion. If any arguments are matched to \code{...}, renv will signal an error.} \item{local}{Boolean; should \code{renv} rely on locally-installed copies of packages when resolving system requirements? When \code{FALSE}, \code{renv} will use \url{https://crandb.r-pkg.org} to resolve the system requirements for these packages.} \item{check}{Boolean; should \code{renv} also check whether the requires system packages appear to be installed on the current system?} \item{report}{Boolean; should \code{renv} also report the commands which could be used to install all of the requisite package dependencies?} \item{distro}{The name of the Linux distribution for which system requirements should be checked -- typical values are "ubuntu", "debian", and "redhat". These should match the distribution names used by the R system requirements database.} \item{collapse}{Boolean; when reporting which packages need to be installed, should the report be collapsed into a single installation command? When \code{FALSE} (the default), a separate installation line is printed for each required system package.} \item{project}{The project directory. If \code{NULL}, then the active project will be used. If no project is currently active, then the current working directory is used instead.} } \description{ Compute the system requirements (system libraries; operating system packages) required by a set of \R packages. } \details{ This function relies on the database of package system requirements maintained by Posit at \url{https://github.com/rstudio/r-system-requirements}, as well as the "meta-CRAN" service at \url{https://crandb.r-pkg.org}. This service primarily exists to map the (free-form) \code{SystemRequirements} field used by \R packages to the system packages made available by a particular operating system. As an example, the \code{curl} R package depends on the \code{libcurl} system library, and declares this with a \code{SystemRequirements} field of the form: \itemize{ \item libcurl (>= 7.62): libcurl-devel (rpm) or libcurl4-openssl-dev (deb) } This dependency can be satisfied with the following command line invocations on different systems: \itemize{ \item Debian: \verb{sudo apt install libcurl4-openssl-dev} \item Redhat: \verb{sudo dnf install libcurl-devel} } and so \code{sysreqs("curl")} would help provide the name of the package whose installation would satisfy the \code{libcurl} dependency. } \examples{ \dontrun{ # report the required system packages for this system sysreqs() # report the required system packages for a specific OS sysreqs(platform = "ubuntu") } } renv/man/rehash.Rd0000644000176200001440000000174214731330073013545 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/rehash.R \name{rehash} \alias{rehash} \title{Re-hash packages in the renv cache} \usage{ rehash(prompt = interactive(), ...) } \arguments{ \item{prompt}{Boolean; prompt the user before taking any action? For backwards compatibility, \code{confirm} is accepted as an alias for \code{prompt}.} \item{...}{Unused arguments, reserved for future expansion. If any arguments are matched to \code{...}, renv will signal an error.} } \description{ Re-hash packages in the renv cache, ensuring that any previously-cached packages are copied to a new cache location appropriate for this version of renv. This can be useful if the cache scheme has changed in a new version of renv, but you'd like to preserve your previously-cached packages. } \details{ Any packages which are re-hashed will retain links to the location of the newly-hashed package, ensuring that prior installations of renv can still function as expected. } renv/man/graph.Rd0000644000176200001440000000344314731330073013374 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/graph.R \name{graph} \alias{graph} \title{Generate a Package Dependency Graph} \usage{ graph( root = NULL, leaf = NULL, ..., suggests = FALSE, enhances = FALSE, resolver = NULL, renderer = c("DiagrammeR", "visNetwork"), attributes = list(), project = NULL ) } \arguments{ \item{root}{The top-most package dependencies of interest in the dependency graph.} \item{leaf}{The bottom-most package dependencies of interest in the dependency graph.} \item{...}{Unused arguments, reserved for future expansion. If any arguments are matched to \code{...}, renv will signal an error.} \item{suggests}{Should suggested packages be included within the dependency graph?} \item{enhances}{Should enhanced packages be included within the dependency graph?} \item{resolver}{An \R function accepting a package name, and returning the contents of its \code{DESCRIPTION} file (as an \R \code{data.frame} or \code{list}). When \code{NULL} (the default), an internal resolver is used.} \item{renderer}{Which package should be used to render the resulting graph?} \item{attributes}{An \R list of graphViz attributes, mapping node names to attribute key-value pairs. For example, to ask graphViz to prefer orienting the graph from left to right, you can use \code{list(graph = c(rankdir = "LR"))}.} \item{project}{The project directory. If \code{NULL}, then the active project will be used. If no project is currently active, then the current working directory is used instead.} } \description{ Generate a package dependency graph. } \examples{ \dontrun{ # graph the relationship between devtools and rlang graph(root = "devtools", leaf = "rlang") # figure out why a project depends on 'askpass' graph(leaf = "askpass") } } \keyword{internal} renv/man/run.Rd0000644000176200001440000000257014731330073013077 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/run.R \name{run} \alias{run} \title{Run a script} \usage{ run(script, ..., job = NULL, name = NULL, args = NULL, project = NULL) } \arguments{ \item{script}{The path to an \R script.} \item{...}{Unused arguments, reserved for future expansion. If any arguments are matched to \code{...}, renv will signal an error.} \item{job}{Run the requested script as an RStudio job? Requires a recent version of both RStudio and the rstudioapi packages. When \code{NULL}, the script will be run as a job if possible, and as a regular \R process launched by \code{\link[=system2]{system2()}} if not.} \item{name}{The name to associate with the job, for scripts run as a job.} \item{args}{description A character vector of command line arguments to be passed to the launched job. These parameters can be accessed via \code{commandArgs(trailingOnly = FALSE)}.} \item{project}{The path to the renv project. This project will be loaded before the requested script is executed. When \code{NULL} (the default), renv will automatically determine the project root for the associated script if possible.} } \value{ The project directory, invisibly. Note that this function is normally called for its side effects. } \description{ Run an \R script, in the context of a project using renv. The script will be run within an \R sub-process. } renv/man/consent.Rd0000644000176200001440000000233414731330073013742 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/consent.R \name{consent} \alias{consent} \title{Consent to usage of renv} \usage{ consent(provided = FALSE) } \arguments{ \item{provided}{The default provided response. If you need to provide consent from a non-interactive \R session, you can invoke \code{renv::consent(provided = TRUE)} explicitly.} } \value{ \code{TRUE} if consent is provided, or an \R error otherwise. } \description{ Provide consent to renv, allowing it to write and update certain files on your filesystem. } \details{ As part of its normal operation, renv will write and update some files in your project directory, as well as an application-specific cache directory. These paths are documented within \link{paths}. In accordance with the \href{https://cran.r-project.org/web/packages/policies.html}{CRAN Repository Policy}, renv must first obtain consent from you, the user, before these actions can be taken. Please call \code{renv::consent()} first to provide this consent. You can also set the \R option: \if{html}{\out{
}}\preformatted{options(renv.consent = TRUE) }\if{html}{\out{
}} to implicitly provide consent for e.g. non-interactive \R sessions. } renv/man/settings.Rd0000644000176200001440000001313714746265552014153 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/settings.R \docType{data} \name{settings} \alias{settings} \title{Project settings} \usage{ settings } \value{ A named list of renv settings. } \description{ Define project-local settings that can be used to adjust the behavior of renv with your particular project. \itemize{ \item Get the current value of a setting with (e.g.) \code{settings$snapshot.type()} \item Set current value of a setting with (e.g.) \code{settings$snapshot.type("explicit")}. } Settings are automatically persisted across project sessions by writing to \code{renv/settings.json}. You can also edit this file by hand, but you'll need to restart the session for those changes to take effect. \subsection{\code{bioconductor.version}}{ The Bioconductor version to be used with this project. Use this if you'd like to lock the version of Bioconductor used on a per-project basis. When unset, renv will try to infer the appropriate Bioconductor release using the BiocVersion package if installed; if not, renv uses \code{BiocManager::version()} to infer the appropriate Bioconductor version. } \subsection{\code{external.libraries}}{ A vector of library paths, to be used in addition to the project's own private library. This can be useful if you have a package available for use in some system library, but for some reason renv is not able to install that package (e.g. sources or binaries for that package are not publicly available, or you have been unable to orchestrate the pre-requisites for installing some packages from source on your machine). } \subsection{\code{ignored.packages}}{ A vector of packages, which should be ignored when attempting to snapshot the project's private library. Note that if a package has already been added to the lockfile, that entry in the lockfile will not be ignored. } \subsection{\code{package.dependency.fields}}{ When explicitly installing a package with \code{install()}, what fields should be used to determine that packages dependencies? The default uses \code{Imports}, \code{Depends} and \code{LinkingTo} fields, but you also want to install \code{Suggests} dependencies for a package, you can set this to \code{c("Imports", "Depends", "LinkingTo", "Suggests")}. } \subsection{\code{ppm.enabled}}{ Enable \href{https://packagemanager.posit.co/}{Posit Package Manager} integration in this project? When \code{TRUE}, renv will attempt to transform repository URLs used by PPM into binary URLs as appropriate for the current Linux platform. Set this to \code{FALSE} if you'd like to continue using source-only PPM URLs, or if you find that renv is improperly transforming your repository URLs. You can still set and use PPM repositories with this option disabled; it only controls whether renv tries to transform source repository URLs into binary URLs on your behalf. } \subsection{\code{ppm.ignored.urls}}{ When \href{https://packagemanager.posit.co/}{Posit Package Manager} integration is enabled, \code{renv} will attempt to transform source repository URLs into binary repository URLs. This setting can be used if you'd like to avoid this transformation with some subset of repository URLs. } \subsection{\code{r.version}}{ The version of \R to encode within the lockfile. This can be set as a project-specific option if you'd like to allow multiple users to use the same renv project with different versions of \R. renv will still warn the user if the major + minor version of \R used in a project does not match what is encoded in the lockfile. } \subsection{\code{snapshot.type}}{ The type of snapshot to perform by default. See \link{snapshot} for more details. } \subsection{\code{use.cache}}{ Enable the renv package cache with this project. When active, renv will install packages into a global cache, and link packages from the cache into your renv projects as appropriate. This can greatly save on disk space and install time when for \R packages which are used across multiple projects in the same environment. } \subsection{\code{vcs.manage.ignores}}{ Should renv attempt to manage the version control system's ignore files (e.g. \code{.gitignore}) within this project? Set this to \code{FALSE} if you'd prefer to take control. Note that if this setting is enabled, you will need to manually ensure internal data in the project's \verb{renv/} folder is explicitly ignored. } \subsection{\code{vcs.ignore.cellar}}{ Set whether packages within a project-local package cellar are excluded from version control. See \code{vignette("package-sources", package = "renv")} for more information. } \subsection{\code{vcs.ignore.library}}{ Set whether the renv project library is excluded from version control. } \subsection{\code{vcs.ignore.local}}{ Set whether renv project-specific local sources are excluded from version control. } } \section{Defaults}{ You can change the default values of these settings for newly-created renv projects by setting \R options for \code{renv.settings} or \verb{renv.settings.}. For example: \if{html}{\out{
}}\preformatted{options(renv.settings = list(snapshot.type = "all")) options(renv.settings.snapshot.type = "all") }\if{html}{\out{
}} If both of the \code{renv.settings} and \verb{renv.settings.} options are set for a particular key, the option associated with \verb{renv.settings.} is used instead. We recommend setting these in an appropriate startup profile, e.g. \verb{~/.Rprofile} or similar. } \examples{ \dontrun{ # view currently-ignored packaged renv::settings$ignored.packages() # ignore a set of packages renv::settings$ignored.packages("devtools", persist = FALSE) } } \keyword{datasets} renv/man/activate.Rd0000644000176200001440000000435514731330073014076 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/activate.R, R/deactivate.R \name{activate} \alias{activate} \alias{deactivate} \title{Activate or deactivate a project} \usage{ activate(project = NULL, profile = NULL) deactivate(project = NULL, clean = FALSE) } \arguments{ \item{project}{The project directory. If \code{NULL}, then the active project will be used. If no project is currently active, then the current working directory is used instead.} \item{profile}{The profile to be activated. See \code{vignette("profiles", package = "renv")} for more information. When \code{NULL} (the default), the profile is not changed. Use \code{profile = "default"} to revert to the default \code{renv} profile.} \item{clean}{If \code{TRUE}, will also remove the \verb{renv/} directory and the lockfile.} } \value{ The project directory, invisibly. Note that this function is normally called for its side effects. } \description{ \code{activate()} enables renv for a project in both the current session and in all future sessions. You should not generally need to call \code{activate()} yourself as it's called automatically by \code{\link[=init]{init()}}, which is the best way to start using renv in a new project. \code{activate()} first calls \code{\link[=scaffold]{scaffold()}} to set up the project infrastructure. Most importantly, this creates a project library and adds a an auto-loader to \code{.Rprofile} to ensure that the project library is automatically used for all future instances of the project. It then restarts the session to use that auto-loader. \code{deactivate()} removes the infrastructure added by \code{activate()}, and restarts the session. By default it will remove the auto-loader from the \code{.Rprofile}; use \code{clean = TRUE} to also delete the lockfile and the project library. } \section{Temporary deactivation}{ If you need to temporarily disable autoload activation you can set the \code{RENV_CONFIG_AUTOLOADER_ENABLED} envvar, e.g. \code{Sys.setenv(RENV_CONFIG_AUTOLOADER_ENABLED = "false")}. } \examples{ \dontrun{ # activate the current project renv::activate() # activate a separate project renv::activate(project = "~/projects/analysis") # deactivate the currently-activated project renv::deactivate() } } renv/man/hydrate.Rd0000644000176200001440000000564214731330073013736 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/hydrate.R \name{hydrate} \alias{hydrate} \title{Copy packages from user libraries to a project library} \usage{ hydrate( packages = NULL, ..., library = NULL, repos = getOption("repos"), update = FALSE, sources = NULL, prompt = interactive(), report = TRUE, project = NULL ) } \arguments{ \item{packages}{The set of \R packages to install. When \code{NULL}, the packages found by \code{\link[=dependencies]{dependencies()}} are used.} \item{...}{Unused arguments, reserved for future expansion. If any arguments are matched to \code{...}, renv will signal an error.} \item{library}{The \R library to be hydrated. When \code{NULL}, the active library as reported by \code{.libPaths()} is used.} \item{repos}{The \R repositories to be used. If the project depends on any \R packages which cannot be found within the user library paths, then those packages will be installed from these repositories instead.} \item{update}{Boolean; should \code{hydrate()} attempt to update already-installed packages if the requested package is already installed in the project library? Set this to \code{"all"} if you'd like \emph{all} packages to be refreshed from the source library if possible.} \item{sources}{A vector of library paths where renv should look for packages. When \code{NULL} (the default), \code{hydrate()} will look in the system libraries (the user library, the site library and the default library) then the renv cache. If a package is not found in any of these locations, \code{hydrate()} will try to install it from the active R repositories.} \item{prompt}{Boolean; prompt the user before taking any action? Ignored when \code{report = FALSE}.} \item{report}{Boolean; display a report of what packages will be installed by \code{renv::hydrate()}?} \item{project}{The project directory. If \code{NULL}, then the active project will be used. If no project is currently active, then the current working directory is used instead.} } \value{ A named \R list, giving the packages that were used for hydration as well as the set of packages which were not found. } \description{ \code{hydrate()} installs missing packages from a user library into the project library. \code{hydrate()} is called automatically by \code{\link[=init]{init()}}, and it is rare that you should need it otherwise, as it can easily get your project into an inconsistent state. It may very occasionally be useful to call \code{hydrate(update = "all")} if you want to update project packages to match those installed in your global library (as opposed to using \code{\link[=update]{update()}} which will get the latest versions from CRAN). In this case, you should verify that your code continues to work, then call \code{\link[=snapshot]{snapshot()}} to record updated package versions in the lockfile. } \examples{ \dontrun{ # hydrate the active library renv::hydrate() } } \keyword{internal} renv/man/renv-package.Rd0000644000176200001440000000210014731330073014623 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/renv-package.R \docType{package} \name{renv-package} \alias{renv} \alias{renv-package} \title{Project-local Environments for R} \description{ Project-local environments for \R. } \details{ You can use renv to construct isolated, project-local \R libraries. Each project using renv will share package installations from a global cache of packages, helping to avoid wasting disk space on multiple installations of a package that might otherwise be shared across projects. } \seealso{ Useful links: \itemize{ \item \url{https://rstudio.github.io/renv/} \item \url{https://github.com/rstudio/renv} \item Report bugs at \url{https://github.com/rstudio/renv/issues} } } \author{ \strong{Maintainer}: Kevin Ushey \email{kevin@rstudio.com} (\href{https://orcid.org/0000-0003-2880-7407}{ORCID}) Authors: \itemize{ \item Hadley Wickham \email{hadley@rstudio.com} (\href{https://orcid.org/0000-0003-4757-117X}{ORCID}) } Other contributors: \itemize{ \item Posit Software, PBC [copyright holder, funder] } } renv/man/migrate.Rd0000644000176200001440000000350114731330073013716 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/migrate.R \name{migrate} \alias{migrate} \title{Migrate a project from packrat to renv} \usage{ migrate( project = NULL, packrat = c("lockfile", "sources", "library", "options", "cache") ) } \arguments{ \item{project}{The project directory. If \code{NULL}, then the active project will be used. If no project is currently active, then the current working directory is used instead.} \item{packrat}{Components of the Packrat project to migrate. See the default argument list for components of the Packrat project that can be migrated. Select a subset of those components for migration as appropriate.} } \value{ The project directory, invisibly. Note that this function is normally called for its side effects. } \description{ Migrate a project's infrastructure from packrat to renv. } \section{Migration}{ When migrating Packrat projects to renv, the set of components migrated can be customized using the \code{packrat} argument. The set of components that can be migrated are as follows: \tabular{ll}{ \strong{Name} \tab \strong{Description} \cr \code{lockfile} \tab Migrate the Packrat lockfile (\code{packrat/packrat.lock}) to the renv lockfile (\code{renv.lock}). \cr \code{sources} \tab Migrate package sources from the \code{packrat/src} folder to the renv sources folder. Currently, only CRAN packages are migrated to renv -- packages retrieved from other sources (e.g. GitHub) are ignored. \cr \code{library} \tab Migrate installed packages from the Packrat library to the renv project library. \cr \code{options} \tab Migrate compatible Packrat options to the renv project. \cr \code{cache} \tab Migrate packages from the Packrat cache to the renv cache. \cr } } \examples{ \dontrun{ # migrate Packrat project infrastructure to renv renv::migrate() } } renv/man/figures/0000755000176200001440000000000014731330073013444 5ustar liggesusersrenv/man/figures/logo.svg0000644000176200001440000021552614731330073015140 0ustar liggesusersrenv/man/restore.Rd0000644000176200001440000001173314731330073013757 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/restore.R \name{restore} \alias{restore} \title{Restore project library from a lockfile} \usage{ restore( project = NULL, ..., library = NULL, lockfile = NULL, packages = NULL, exclude = NULL, rebuild = FALSE, repos = NULL, clean = FALSE, transactional = NULL, prompt = interactive() ) } \arguments{ \item{project}{The project directory. If \code{NULL}, then the active project will be used. If no project is currently active, then the current working directory is used instead.} \item{...}{Unused arguments, reserved for future expansion. If any arguments are matched to \code{...}, renv will signal an error.} \item{library}{The library paths to be used during restore.} \item{lockfile}{Path to a lockfile. When \code{NULL} (the default), the \code{renv.lock} located in the root of the current project will be used.} \item{packages}{A subset of packages recorded in the lockfile to restore. When \code{NULL} (the default), all packages available in the lockfile will be restored. Any required recursive dependencies of the requested packages will be restored as well.} \item{exclude}{A subset of packages to be excluded during restore. This can be useful for when you'd like to restore all but a subset of packages from a lockfile. Note that if you attempt to exclude a package which is required as the recursive dependency of another package, your request will be ignored.} \item{rebuild}{Force packages to be rebuilt, thereby bypassing any installed versions of the package available in the cache? This can either be a boolean (indicating that all installed packages should be rebuilt), or a vector of package names indicating which packages should be rebuilt.} \item{repos}{The repositories to use when restoring packages installed from CRAN or a CRAN-like repository. By default, the repositories recorded in the lockfile will be used, ensuring that (e.g.) CRAN packages are re-installed from the same CRAN mirror. Use \code{repos = getOption("repos")} to override with the repositories set in the current session, or see the \code{repos.override} option in \link{config} for an alternate way override.} \item{clean}{Boolean; remove packages not recorded in the lockfile from the target library? Use \code{clean = TRUE} if you'd like the library state to exactly reflect the lockfile contents after \code{restore()}.} \item{transactional}{Whether or not to use a 'transactional' restore. See \strong{Transactional Restore} for more details. When \code{NULL} (the default), the value of the \code{install.transactional} \code{\link{config}} option will be used.} \item{prompt}{Boolean; prompt the user before taking any action? For backwards compatibility, \code{confirm} is accepted as an alias for \code{prompt}.} } \value{ A named list of package records which were installed by renv. } \description{ Restore a project's dependencies from a lockfile, as previously generated by \code{\link[=snapshot]{snapshot()}}. } \details{ \code{renv::restore()} compares packages recorded in the lockfile to the packages installed in the project library. Where there are differences it resolves them by installing the lockfile-recorded package into the project library. If \code{clean = TRUE}, \code{restore()} will additionally delete any packages in the project library that don't appear in the lockfile. } \section{Transactional Restore}{ By default, \code{renv::restore()} will perform a 'transactional' restore, wherein the project library is mutated only if all packages within the lockfile are successfully restored. The intention here is to prevent the private library from entering an inconsistent state, if some subset of packages were to install successfully but some other subset of packages did not. \code{renv::restore(transactional = FALSE)} can be useful if you're attempting to restore packages from a lockfile, but would like to update or change certain packages piece-meal if they fail to install. The term 'transactional' here borrows from the parlance of a 'database transaction', where the failure of any intermediate step implies that the whole transaction will be rolled back, so that the state of the database before the transaction was initiated can be preserved. See \url{https://en.wikipedia.org/wiki/Database_transaction} for more details. } \examples{ \dontrun{ # disable automatic snapshots auto.snapshot <- getOption("renv.config.auto.snapshot") options(renv.config.auto.snapshot = FALSE) # initialize a new project (with an empty R library) renv::init(bare = TRUE) # install digest 0.6.19 renv::install("digest@0.6.19") # save library state to lockfile renv::snapshot() # remove digest from library renv::remove("digest") # check library status renv::status() # restore lockfile, thereby reinstalling digest 0.6.19 renv::restore() # restore automatic snapshots options(renv.config.auto.snapshot = auto.snapshot) } } \seealso{ Other reproducibility: \code{\link{lockfiles}}, \code{\link{snapshot}()} } \concept{reproducibility} renv/man/modify.Rd0000644000176200001440000000176014731330073013562 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/modify.R \name{modify} \alias{modify} \title{Modify a Lockfile} \usage{ modify(project = NULL, changes = NULL) } \arguments{ \item{project}{The project directory. If \code{NULL}, then the active project will be used. If no project is currently active, then the current working directory is used instead.} \item{changes}{A list of changes to be merged into the lockfile. When \code{NULL} (the default), the lockfile is instead opened for interactive editing.} } \value{ The project directory, invisibly. Note that this function is normally called for its side effects. } \description{ Modify a project's lockfile, either interactively or non-interactively. } \details{ After edit, if the lockfile edited is associated with the active project, any state-related changes (e.g. to \R repositories) will be updated in the current session. } \examples{ \dontrun{ # modify an existing lockfile if (interactive()) renv::modify() } } renv/man/remote.Rd0000644000176200001440000000100314731330073013554 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/remotes.R \name{remote} \alias{remote} \title{Resolve a Remote} \usage{ remote(spec) } \arguments{ \item{spec}{A remote specification. This should be a string, conforming to the Remotes specification as defined in \url{https://remotes.r-lib.org/articles/dependencies.html}.} } \description{ Given a remote specification, resolve it into an renv package record that can be used for download and installation (e.g. with \link{install}). } renv/man/sandbox.Rd0000644000176200001440000000544114731330073013731 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/sandbox.R \docType{data} \name{sandbox} \alias{sandbox} \title{The default library sandbox} \usage{ sandbox } \description{ An \R installation can have up to three types of library paths available to the user: \itemize{ \item The \emph{user library}, where \R packages downloaded and installed by the current user are installed. This library path is only visible to that specific user. \item The \emph{site library}, where \R packages maintained by administrators of a system are installed. This library path, if it exists, is visible to all users on the system. \item The \emph{default library}, where \R packages distributed with \R itself are installed. This library path is visible to all users on the system. } Normally, only so-called "base" and "recommended" packages should be installed in the default library. (You can get a list of these packages with \code{installed.packages(priority = c("base", "recommended"))}). However, it is possible for users and administrators to install packages into the default library, if the filesystem permissions permit them to do so. (This, for example, is the default behavior on macOS.) Because the site and default libraries are visible to all users, having those accessible in renv projects can potentially break isolation -- that is, if a package were updated in the default library, that update would be visible to all \R projects on the system. To help defend against this, renv uses something called the "sandbox" to isolate renv projects from non-"base" packages that are installed into the default library. When an renv project is loaded, renv will: \itemize{ \item Create a new, empty library path (called the "sandbox"), \item Link only the "base" and "recommended" packages from the default library into the sandbox, \item Mark the sandbox as read-only, so that users are unable to install packages into this library, \item Instruct the \R session to use the "sandbox" as the default library. } This process is mostly transparent to the user. However, because the sandbox is read-only, if you later need to remove the sandbox, you'll need to reset file permissions manually; for example, with \code{renv::sandbox$unlock()}. If you'd prefer to keep the sandbox unlocked, you can also set: \if{html}{\out{
}}\preformatted{RENV_SANDBOX_LOCKING_ENABLED = FALSE }\if{html}{\out{
}} in an appropriate startup \code{.Renviron} or \code{Renviron.site} file. The sandbox can also be disabled entirely with: \if{html}{\out{
}}\preformatted{RENV_CONFIG_SANDBOX_ENABLED = FALSE }\if{html}{\out{
}} The sandbox library path can also be configured using the \code{RENV_PATHS_SANDBOX} environment variable: see \link{paths} for more details. } \keyword{datasets} renv/man/history.Rd0000644000176200001440000000237014731330073013772 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/history.R \name{history} \alias{history} \alias{revert} \title{View and revert to a historical lockfile} \usage{ history(project = NULL) revert(commit = "HEAD", ..., project = NULL) } \arguments{ \item{project}{The project directory. If \code{NULL}, then the active project will be used. If no project is currently active, then the current working directory is used instead.} \item{commit}{The commit associated with a prior version of the lockfile.} \item{...}{Optional arguments; currently unused.} } \value{ \code{history()} returns a \code{data.frame} summarizing the commits in which \code{renv.lock} has been changed. \code{revert()} is usually called for its side-effect but also invisibly returns the \code{commit} used. } \description{ \code{history()} uses your version control system to show prior versions of the lockfile and \code{revert()} allows you to restore one of them. These functions are currently only implemented for projects that use git. } \examples{ \dontrun{ # get history of previous versions of renv.lock in VCS db <- renv::history() # choose an older commit commit <- db$commit[5] # revert to that version of the lockfile renv::revert(commit = commit) } } renv/man/load.Rd0000644000176200001440000000522414731330073013211 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/load.R \name{load} \alias{load} \title{Load a project} \usage{ load(project = NULL, quiet = FALSE, profile = NULL, ...) } \arguments{ \item{project}{The project directory. If \code{NULL}, then the active project will be used. If no project is currently active, then the current working directory is used instead.} \item{quiet}{Boolean; be quiet during load?} \item{profile}{The profile to be activated. See \code{vignette("profiles", package = "renv")} for more information. When \code{NULL} (the default), the profile is not changed. Use \code{profile = "default"} to revert to the default \code{renv} profile.} \item{...}{Unused arguments, reserved for future expansion. If any arguments are matched to \code{...}, renv will signal an error.} } \value{ The project directory, invisibly. Note that this function is normally called for its side effects. } \description{ \code{renv::load()} sets the library paths to use a project-local library, sets up the system library \link{sandbox}, if needed, and creates shims for \code{install.packages()}, \code{update.packages()}, and \code{remove.packages()}. You should not generally need to call \code{renv::load()} yourself, as it's called automatically by the project auto-loader created by \code{\link[=init]{init()}}/ \code{\link[=activate]{activate()}}. However, if needed, you can use \code{renv::load("")} to explicitly load an renv project located at a particular path. } \section{Shims}{ To help you take advantage of the package cache, renv places a couple of shims on the search path: \itemize{ \item \code{install.packages()} instead calls \code{renv::install()}. \item \code{remove.packages()} instead calls \code{renv::remove()}. \item \code{update.packages()} instead calls \code{renv::update()}. } This allows you to keep using your existing muscle memory for installing, updating, and remove packages, while taking advantage of renv features like the package cache. If you'd like to bypass these shims within an \R session, you can explicitly call the version of these functions from the utils package, e.g. with \verb{utils::install.packages(<...>)}. If you'd prefer not to use the renv shims at all, they can be disabled by setting the R option \code{options(renv.config.shims.enabled = FALSE)} or by setting the environment variable \code{RENV_CONFIG_SHIMS_ENABLED = FALSE}. See \code{?config} for more details. } \examples{ \dontrun{ # load a project -- note that this is normally done automatically # by the project's auto-loader, but calling this explicitly to # load a particular project may be useful in some circumstances renv::load() } } renv/man/autoload.Rd0000644000176200001440000000237214731330073014103 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/autoload.R \name{autoload} \alias{autoload} \title{Auto-load the active project} \usage{ autoload() } \description{ Automatically load the renv project associated with a particular directory. renv will search parent directories for the renv project root; if found, that project will be loaded via \code{\link[=load]{load()}}. } \details{ To enable the renv auto-loader, you can place: \if{html}{\out{
}}\preformatted{renv::autoload() }\if{html}{\out{
}} into your site-wide or user \code{.Rprofile} to ensure that renv projects are automatically loaded for any newly-launched \R sessions, even if those \R sessions are launched within the sub-directory of an renv project. If you'd like to launch \R within the sub-directory of an renv project without auto-loading renv, you can set the environment variable: \if{html}{\out{
}}\preformatted{RENV_AUTOLOAD_ENABLED = FALSE }\if{html}{\out{
}} before starting \R. Note that \code{renv::autoload()} is only compatible with projects using \verb{renv 0.15.3} or newer, as it relies on features within the \code{renv/activate.R} script that are only generated with newer versions of renv. } renv/man/rebuild.Rd0000644000176200001440000000334114731330073013716 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/rebuild.R \name{rebuild} \alias{rebuild} \title{Rebuild the packages in your project library} \usage{ rebuild( packages = NULL, recursive = TRUE, ..., type = NULL, prompt = interactive(), library = NULL, project = NULL ) } \arguments{ \item{packages}{The package(s) to be rebuilt. When \code{NULL}, all packages in the library will be reinstalled.} \item{recursive}{Boolean; should dependencies of packages be rebuilt recursively? Defaults to \code{TRUE}.} \item{...}{Unused arguments, reserved for future expansion. If any arguments are matched to \code{...}, renv will signal an error.} \item{type}{The type of package to install ("source" or "binary"). Defaults to the value of \code{getOption("pkgType")}.} \item{prompt}{Boolean; prompt the user before taking any action? For backwards compatibility, \code{confirm} is accepted as an alias for \code{prompt}.} \item{library}{The \R library to be used. When \code{NULL}, the active project library will be used instead.} \item{project}{The project directory. If \code{NULL}, then the active project will be used. If no project is currently active, then the current working directory is used instead.} } \value{ A named list of package records which were installed by renv. } \description{ Rebuild and reinstall packages in your library. This can be useful as a diagnostic tool -- for example, if you find that one or more of your packages fail to load, and you want to ensure that you are starting from a clean slate. } \examples{ \dontrun{ # rebuild the 'dplyr' package + all of its dependencies renv::rebuild("dplyr", recursive = TRUE) # rebuild only 'dplyr' renv::rebuild("dplyr", recursive = FALSE) } } renv/man/update.Rd0000644000176200001440000000477614731330073013567 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/update.R \name{update} \alias{update} \title{Update packages} \usage{ update( packages = NULL, ..., exclude = NULL, library = NULL, type = NULL, rebuild = FALSE, check = FALSE, prompt = interactive(), lock = FALSE, project = NULL ) } \arguments{ \item{packages}{A character vector of \R packages to update. When \code{NULL} (the default), all packages (apart from any listed in the \code{ignored.packages} project setting) will be updated.} \item{...}{Unused arguments, reserved for future expansion. If any arguments are matched to \code{...}, renv will signal an error.} \item{exclude}{A set of packages to explicitly exclude from updating. Use \verb{renv::update(exclude = <...>)} to update all packages except for a specific set of excluded packages.} \item{library}{The \R library to be used. When \code{NULL}, the active project library will be used instead.} \item{type}{The type of package to install ("source" or "binary"). Defaults to the value of \code{getOption("pkgType")}.} \item{rebuild}{Force packages to be rebuilt, thereby bypassing any installed versions of the package available in the cache? This can either be a boolean (indicating that all installed packages should be rebuilt), or a vector of package names indicating which packages should be rebuilt.} \item{check}{Boolean; check for package updates without actually installing available updates? This is useful when you'd like to determine what updates are available, without actually installing those updates.} \item{prompt}{Boolean; prompt the user before taking any action? For backwards compatibility, \code{confirm} is accepted as an alias for \code{prompt}.} \item{lock}{Boolean; update the \code{renv.lock} lockfile after the successful installation of the requested packages?} \item{project}{The project directory. If \code{NULL}, then the active project will be used. If no project is currently active, then the current working directory is used instead.} } \value{ A named list of package records which were installed by renv. } \description{ Update packages which are currently out-of-date. Currently supports CRAN, Bioconductor, other CRAN-like repositories, GitHub, GitLab, Git, and BitBucket. Updates will only be checked from the same source -- for example, if a package was installed from GitHub, but a newer version is available on CRAN, that updated version will not be seen. } \examples{ \dontrun{ # update the 'dplyr' package renv::update("dplyr") } } renv/man/vendor.Rd0000644000176200001440000000234714731330073013572 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/vendor.R \name{vendor} \alias{vendor} \title{Vendor renv in an R package} \usage{ vendor(version = "main", project = getwd()) } \arguments{ \item{version}{The version of renv to vendor. \code{renv} sources will be pulled from GitHub, and so \code{version} should refer to either a commit hash or a branch name.} \item{project}{The project in which renv should be vendored.} } \description{ Calling \code{renv:::vendor()} will: \itemize{ \item Compile a vendored copy of renv to \code{inst/vendor/renv.R}, \item Generate an renv auto-loader at \code{R/renv.R}. } Using this, projects can take a dependency on renv, and use renv internals, in a CRAN-compliant way. After vendoring renv, you can use renv APIs in your package via the embedded renv environment; for example, you could call the \code{\link[=dependencies]{dependencies()}} function with: \if{html}{\out{
}}\preformatted{renv$dependencies() }\if{html}{\out{
}} Be aware that renv internals might change in future releases, so if you need to rely on renv internal functions, we strongly recommend testing your usages of these functions to avoid potential breakage. } \keyword{internal} renv/man/init.Rd0000644000176200001440000001011614731330073013231 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/init.R \name{init} \alias{init} \title{Use renv in a project} \usage{ init( project = NULL, ..., profile = NULL, settings = NULL, bare = FALSE, force = FALSE, repos = NULL, bioconductor = NULL, load = TRUE, restart = interactive() ) } \arguments{ \item{project}{The project directory. When \code{NULL} (the default), the current working directory will be used. The \R working directory will be changed to match the requested project directory.} \item{...}{Unused arguments, reserved for future expansion. If any arguments are matched to \code{...}, renv will signal an error.} \item{profile}{The profile to be activated. See \code{vignette("profiles", package = "renv")} for more information. When \code{NULL} (the default), the profile is not changed. Use \code{profile = "default"} to revert to the default \code{renv} profile.} \item{settings}{A list of \link{settings} to be used with the newly-initialized project.} \item{bare}{Boolean; initialize the project with an empty project library, without attempting to discover and install \R package dependencies?} \item{force}{Boolean; force initialization? By default, renv will refuse to initialize the home directory as a project, to defend against accidental misusages of \code{init()}.} \item{repos}{The \R repositories to be used in this project. See \strong{Repositories} for more details.} \item{bioconductor}{The version of Bioconductor to be used with this project. Setting this may be appropriate if renv is unable to determine that your project depends on a package normally available from Bioconductor. Set this to \code{TRUE} to use the default version of Bioconductor recommended by the BiocManager package.} \item{load}{Boolean; should the project be loaded after it is initialized?} \item{restart}{Boolean; attempt to restart the \R session after initializing the project? A session restart will be attempted if the \code{"restart"} \R option is set by the frontend hosting \R.} } \value{ The project directory, invisibly. Note that this function is normally called for its side effects. } \description{ Call \code{renv::init()} to start using renv in the current project. This will: \enumerate{ \item Set up project infrastructure (as described in \code{\link[=scaffold]{scaffold()}}) including the project library and the \code{.Rprofile} that ensures renv will be used in all future sessions, \item Discover the packages that are currently being used in your project (via \code{\link[=dependencies]{dependencies()}}), and install them into the project library (as described in \code{\link[=hydrate]{hydrate()}}), \item Create a lockfile that records the state of the project library so it can be restored by others (as described in \code{\link[=snapshot]{snapshot()}}), \item Restart R (if running inside RStudio). } If you call \code{renv::init()} with a project that is already using renv, it will attempt to do the right thing: it will restore the project library if it's missing, or otherwise ask you what to do. } \section{Repositories}{ If the default \R repositories have not already been set, renv will use the \href{https://packagemanager.posit.co/}{Posit Public Package Manager} CRAN mirror for package installation. The primary benefit to using this mirror is that it can provide pre-built binaries for \R packages on a variety of commonly-used Linux distributions. This behavior can be configured or disabled if desired -- see the options in \code{\link[=config]{config()}} for more details. } \examples{ \dontrun{ # disable automatic snapshots auto.snapshot <- getOption("renv.config.auto.snapshot") options(renv.config.auto.snapshot = FALSE) # initialize a new project (with an empty R library) renv::init(bare = TRUE) # install digest 0.6.19 renv::install("digest@0.6.19") # save library state to lockfile renv::snapshot() # remove digest from library renv::remove("digest") # check library status renv::status() # restore lockfile, thereby reinstalling digest 0.6.19 renv::restore() # restore automatic snapshots options(renv.config.auto.snapshot = auto.snapshot) } } renv/man/install.Rd0000644000176200001440000001351714731330073013744 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/install.R \name{install} \alias{install} \title{Install packages} \usage{ install( packages = NULL, ..., include = NULL, exclude = NULL, library = NULL, type = NULL, rebuild = FALSE, repos = NULL, prompt = interactive(), dependencies = NULL, verbose = NULL, lock = FALSE, project = NULL ) } \arguments{ \item{packages}{Either \code{NULL} (the default) to install all packages required by the project, or a character vector of packages to install. renv supports a subset of the remotes syntax used for package installation, e.g: \itemize{ \item \code{pkg}: install latest version of \code{pkg} from CRAN. \item \code{pkg@version}: install specified version of \code{pkg} from CRAN. \item \code{username/repo}: install package from GitHub \item \code{bioc::pkg}: install \code{pkg} from Bioconductor. } See \url{https://remotes.r-lib.org/articles/dependencies.html} and the examples below for more details. renv deviates from the remotes spec in one important way: subdirectories are separated from the main repository specification with a \code{:}, not \code{/}. So to install from the \code{subdir} subdirectory of GitHub package \code{username/repo} you'd use \verb{"username/repo:subdir}.} \item{...}{Unused arguments, reserved for future expansion. If any arguments are matched to \code{...}, renv will signal an error.} \item{include}{Packages which should be installed. \code{include} can occasionally be useful when you'd like to call \code{renv::install()} with no arguments, but restrict package installation to only some subset of dependencies in the project.} \item{exclude}{Packages which should not be installed. \code{exclude} is useful when using \code{renv::install()} to install all dependencies in a project, except for a specific set of packages.} \item{library}{The \R library to be used. When \code{NULL}, the active project library will be used instead.} \item{type}{The type of package to install ("source" or "binary"). Defaults to the value of \code{getOption("pkgType")}.} \item{rebuild}{Force packages to be rebuilt, thereby bypassing any installed versions of the package available in the cache? This can either be a boolean (indicating that all installed packages should be rebuilt), or a vector of package names indicating which packages should be rebuilt.} \item{repos}{The repositories to use when restoring packages installed from CRAN or a CRAN-like repository. By default, the repositories recorded in the lockfile will be used, ensuring that (e.g.) CRAN packages are re-installed from the same CRAN mirror. Use \code{repos = getOption("repos")} to override with the repositories set in the current session, or see the \code{repos.override} option in \link{config} for an alternate way override.} \item{prompt}{Boolean; prompt the user before taking any action? For backwards compatibility, \code{confirm} is accepted as an alias for \code{prompt}.} \item{dependencies}{A vector of DESCRIPTION field names that should be used for package dependency resolution. When \code{NULL} (the default), the value of \code{renv::settings$package.dependency.fields} is used. The aliases "strong", "most", and "all" are also supported. See \code{\link[tools:package_dependencies]{tools::package_dependencies()}} for more details.} \item{verbose}{Boolean; report output from \verb{R CMD build} and \verb{R CMD INSTALL} during installation? When \code{NULL} (the default), the value of \code{config$install.verbose()} will be used. When \code{FALSE}, installation output will be emitted only if a package fails to install.} \item{lock}{Boolean; update the \code{renv.lock} lockfile after the successful installation of the requested packages?} \item{project}{The project directory. If \code{NULL}, then the active project will be used. If no project is currently active, then the current working directory is used instead.} } \value{ A named list of package records which were installed by renv. } \description{ Install one or more \R packages, from a variety of remote sources. \code{install()} uses the same machinery as \code{\link[=restore]{restore()}} (i.e. it uses cached packages where possible) but it does not respect the lockfile, instead installing the latest versions available from CRAN. See \code{vignette("package-install")} for more details. } \section{\code{Remotes}}{ \code{install()} (called without arguments) will respect the \code{Remotes} field of the \code{DESCRIPTION} file (if present). This allows you to specify places to install a package other than the latest version from CRAN. See \url{https://remotes.r-lib.org/articles/dependencies.html} for details. } \section{Bioconductor}{ Packages from Bioconductor can be installed by using the \verb{bioc::} prefix. For example, \if{html}{\out{
}}\preformatted{renv::install("bioc::Biobase") }\if{html}{\out{
}} will install the latest-available version of Biobase from Bioconductor. renv depends on BiocManager (or, for older versions of \R, BiocInstaller) for the installation of packages from Bioconductor. If these packages are not available, renv will attempt to automatically install them before fulfilling the installation request. } \examples{ \dontrun{ # install the latest version of 'digest' renv::install("digest") # install an old version of 'digest' (using archives) renv::install("digest@0.6.18") # install 'digest' from GitHub (latest dev. version) renv::install("eddelbuettel/digest") # install a package from GitHub, using specific commit renv::install("eddelbuettel/digest@df55b00bff33e945246eff2586717452e635032f") # install a package from Bioconductor # (note: requires the BiocManager package) renv::install("bioc::Biobase") # install a package, specifying path explicitly renv::install("~/path/to/package") # install packages as declared in the project DESCRIPTION file renv::install() } } renv/man/lockfile_validate.Rd0000644000176200001440000000470314731330073015734 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/lockfile-validate.R \name{lockfile_validate} \alias{lockfile_validate} \title{Validate an renv lockfile against a schema} \usage{ lockfile_validate( project = NULL, lockfile = NULL, schema = NULL, greedy = FALSE, error = FALSE, verbose = FALSE, strict = FALSE ) } \arguments{ \item{project}{The project directory. If \code{NULL}, then the active project will be used. If no project is currently active, then the current working directory is used instead.} \item{lockfile}{Contents of the lockfile, or a filename containing one. If not provided, it defaults to the project's lockfile.} \item{schema}{Contents of a renv schema, or a filename containing a schema. If not provided, renv's default schema is used.} \item{greedy}{Boolean. Continue after first error?} \item{error}{Boolean. Throw an error on parse failure?} \item{verbose}{Boolean. If \code{TRUE}, then an attribute \code{errors} will list validation failures as a \code{data.frame}.} \item{strict}{Boolean. Set whether the schema should be parsed strictly or not. If in strict mode schemas will error to "prevent any unexpected behaviours or silently ignored mistakes in user schema". For example it will error if encounters unknown formats or unknown keywords. See https://ajv.js.org/strict-mode.html for details.} } \value{ Boolean. \code{TRUE} if validation passes. \code{FALSE} if validation fails. } \description{ \code{renv::lockfile_validate()} can be used to validate your \code{renv.lock} against a default or custom schema. It can be used to automate checks, check for obvious errors, and ensure that any custom fields you add fit your specific needs. } \details{ See the \href{https://json-schema.org/}{JSON Schema docs} for more information on JSON schemas, their use in validation, and how to write your own schema. \code{renv::lockfile_validate()} wraps ROpenSci's \href{https://docs.ropensci.org/jsonvalidate/}{\code{jsonvalidate}} package, passing many of its parameters to that package's \code{json_validate()} function. Use \code{?jsonvalidate::json_validate} for more information. } \examples{ \dontrun{ # validate the project's lockfile renv::lockfile_validate() # validate the project's lockfile using a non-default schema renv::lockfile_validate(schema = "/path/to/your/custom/schema.json") # validate a lockfile using its path renv::lockfile_validate(lockfile = "/path/to/your/renv.lock") } } \keyword{internal} renv/man/renv_lockfile_from_manifest.Rd0000644000176200001440000000243014731330073020021 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/manifest-convert.R \name{renv_lockfile_from_manifest} \alias{renv_lockfile_from_manifest} \title{Generate \code{renv.lock} from an RStudio Connect \code{manifest.json}} \usage{ renv_lockfile_from_manifest( manifest = "manifest.json", lockfile = NA, project = NULL ) } \arguments{ \item{manifest}{The path to a \code{manifest.json} file.} \item{lockfile}{The path to the lockfile to be generated and / or updated. When \code{NA} (the default), the generated lockfile is returned as an \R object; otherwise, the lockfile will be written to the path specified by \code{lockfile}.} } \value{ An renv lockfile. } \description{ Use \code{renv_lockfile_from_manifest()} to convert a \code{manifest.json} file from an RStudio Connect content bundle into an \code{renv.lock} lockfile. This function can be useful when you need to recreate the package environment of a piece of content that is deployed to RStudio Connect. The content bundle contains a \code{manifest.json} file that is used to recreate the package environment. This function will let you convert that manifest file to an \code{renv.lock} file. Run \code{renv::restore()} after you've converted the file to restore the package environment. } \keyword{internal} renv/man/retrieve.Rd0000644000176200001440000000545614731330073014126 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/retrieve.R \name{retrieve} \alias{retrieve} \title{Retrieve packages} \usage{ retrieve(packages = NULL, ..., lockfile = NULL, destdir = NULL, project = NULL) } \arguments{ \item{packages}{Either \code{NULL} (the default) to install all packages required by the project, or a character vector of packages to install. renv supports a subset of the remotes syntax used for package installation, e.g: \itemize{ \item \code{pkg}: install latest version of \code{pkg} from CRAN. \item \code{pkg@version}: install specified version of \code{pkg} from CRAN. \item \code{username/repo}: install package from GitHub \item \code{bioc::pkg}: install \code{pkg} from Bioconductor. } See \url{https://remotes.r-lib.org/articles/dependencies.html} and the examples below for more details. renv deviates from the remotes spec in one important way: subdirectories are separated from the main repository specification with a \code{:}, not \code{/}. So to install from the \code{subdir} subdirectory of GitHub package \code{username/repo} you'd use \verb{"username/repo:subdir}.} \item{...}{Unused arguments, reserved for future expansion. If any arguments are matched to \code{...}, renv will signal an error.} \item{lockfile}{The path to an \code{renv} lockfile. When set, \code{renv} will retrieve the packages as defined within that lockfile. If \code{packages} is also non-\code{NULL}, then only those packages will be retrieved.} \item{destdir}{The directory where packages should be downloaded. When \code{NULL} (the default), the default internal storage locations (normally used by e.g. \code{\link[=install]{install()}} or \code{\link[=restore]{restore()}}) will be used.} \item{project}{The project directory. If \code{NULL}, then the active project will be used. If no project is currently active, then the current working directory is used instead.} } \value{ A named vector, mapping package names to the paths where those packages were downloaded. } \description{ Retrieve (download) one or more packages from external sources. Using \code{renv::retrieve()} can be useful in CI / CD workflows, where you might want to download all packages listed in a lockfile before later invoking \code{\link[=restore]{restore()}}. Packages will be downloaded to an internal path within \code{renv}'s local state directories -- see \link{paths} for more details. } \details{ If \code{destdir} is \code{NULL} and the requested package is already available within the \code{renv} cache, \code{renv} will return the path to that package directory in the cache. } \examples{ \dontrun{ # retrieve package + versions as defined in the lockfile # normally used as a pre-flight step to renv::restore() renv::retrieve() # download one or more packages locally renv::retrieve("rlang", destdir = ".") } } renv/man/equip.Rd0000644000176200001440000000104214731330073013407 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/equip.R \name{equip} \alias{equip} \title{Install required system libraries} \usage{ equip() } \value{ This function is normally called for its side effects. } \description{ Equip your system with libraries commonly-used during compilation of base and recommended \R packages. This was previously useful with older versions of R on windows, but is no longer terribly helpful. } \examples{ \dontrun{ # download useful build tools renv::equip() } } \keyword{internal} renv/man/refresh.Rd0000644000176200001440000000215514731330073013730 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/refresh.R \name{refresh} \alias{refresh} \title{Refresh the local cache of available packages} \usage{ refresh() } \value{ A list of package databases, invisibly -- one for each repository currently active in the \R session. Note that this function is normally called for its side effects. } \description{ Query the active R package repositories for available packages, and update the in-memory cache of those packages. } \details{ Note that \R also maintains its own on-disk cache of available packages, which is used by \code{available.packages()}. Calling \code{refresh()} will force an update of both types of caches. renv prefers using an in-memory cache as on occasion the temporary directory can be slow to access (e.g. when it is a mounted network filesystem). } \examples{ \dontrun{ # check available packages db <- available.packages() # wait some time (suppose packages are uploaded / changed in this time) Sys.sleep(5) # refresh the local available packages database # (the old locally cached db will be removed) db <- renv::refresh() } } renv/man/upgrade.Rd0000644000176200001440000000377214761167234013741 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/upgrade.R \name{upgrade} \alias{upgrade} \title{Upgrade renv} \usage{ upgrade(project = NULL, version = NULL, reload = NULL, prompt = interactive()) } \arguments{ \item{project}{The project directory. If \code{NULL}, then the active project will be used. If no project is currently active, then the current working directory is used instead.} \item{version}{The version of renv to be installed. When \code{NULL} (the default), the latest version of renv will be installed as available from CRAN (or whatever active package repositories are active) Alternatively, you can install the latest development version with \code{"main"}, or a specific commit with a SHA, e.g. \code{"5049cef8a"}.} \item{reload}{Boolean; reload renv after install? When \code{NULL} (the default), renv will be re-loaded only if updating renv for the active project. Since it's not possible to guarantee a clean reload in the current session, this will attempt to restart your R session.} \item{prompt}{Boolean; prompt upgrade before proceeding?} } \value{ A boolean value, indicating whether the requested version of renv was successfully installed. Note that this function is normally called for its side effects. } \description{ Upgrade the version of renv associated with a project, including using a development version from GitHub. Automatically snapshots the updated renv, updates the activate script, and restarts R. If you want to update all packages (including renv) to their latest CRAN versions, use \code{\link[=update]{update()}}. } \section{Note}{ \code{upgrade()} is expected to work for renv versions >= 1.0.1. To upgrade from prior versions of renv, users should \code{renv::deactivate();} \code{install.packages("renv");} \code{renv::activate();} \code{renv::record("renv")} } \examples{ \dontrun{ # upgrade to the latest version of renv renv::upgrade() # upgrade to the latest version of renv on GitHub (development version) renv::upgrade(version = "main") } } renv/man/project.Rd0000644000176200001440000000101414731330073013731 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/project.R \name{project} \alias{project} \title{Retrieve the active project} \usage{ project(default = NULL) } \arguments{ \item{default}{The value to return when no project is currently active. Defaults to \code{NULL}.} } \value{ The active project directory, as a length-one character vector. } \description{ Retrieve the path to the active project (if any). } \examples{ \dontrun{ # get the currently-active renv project renv::project() } } renv/DESCRIPTION0000644000176200001440000000333114761174012012735 0ustar liggesusersPackage: renv Type: Package Title: Project Environments Version: 1.1.2 Authors@R: c( person("Kevin", "Ushey", role = c("aut", "cre"), email = "kevin@rstudio.com", comment = c(ORCID = "0000-0003-2880-7407")), person("Hadley", "Wickham", role = c("aut"), email = "hadley@rstudio.com", comment = c(ORCID = "0000-0003-4757-117X")), person("Posit Software, PBC", role = c("cph", "fnd")) ) Description: A dependency management toolkit for R. Using 'renv', you can create and manage project-local R libraries, save the state of these libraries to a 'lockfile', and later restore your library as required. Together, these tools can help make your projects more isolated, portable, and reproducible. License: MIT + file LICENSE URL: https://rstudio.github.io/renv/, https://github.com/rstudio/renv BugReports: https://github.com/rstudio/renv/issues Imports: utils Suggests: BiocManager, cli, compiler, covr, cpp11, devtools, gitcreds, jsonlite, jsonvalidate, knitr, miniUI, modules, packrat, pak, R6, remotes, reticulate, rmarkdown, rstudioapi, shiny, testthat, uuid, waldo, yaml, webfakes Encoding: UTF-8 RoxygenNote: 7.3.2 VignetteBuilder: knitr Config/Needs/website: tidyverse/tidytemplate Config/testthat/edition: 3 Config/testthat/parallel: true Config/testthat/start-first: bioconductor,python,install,restore,snapshot,retrieve,remotes NeedsCompilation: no Packaged: 2025-03-02 23:50:06 UTC; kevin Author: Kevin Ushey [aut, cre] (), Hadley Wickham [aut] (), Posit Software, PBC [cph, fnd] Maintainer: Kevin Ushey Repository: CRAN Date/Publication: 2025-03-03 00:30:02 UTC