diff --git a/DESCRIPTION b/DESCRIPTION index e6ee52f1a..4a6026eb7 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -14,7 +14,7 @@ Description: A dependency management toolkit for R. Using 'renv', you can create 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/ +URL: https://rstudio.github.io/renv/, https://github.com/rstudio/renv BugReports: https://github.com/rstudio/renv/issues Imports: utils Suggests: BiocManager, cli, covr, cpp11, devtools, gitcreds, jsonlite, knitr, miniUI, diff --git a/LICENSE b/LICENSE index 96b859c08..5fbc99ab0 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright 2021 RStudio, PBC +Copyright 2023 Posit Software, PBC Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: diff --git a/NEWS.md b/NEWS.md index 875954159..588a59806 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,148 +1,144 @@ -# renv 0.18.0 (UNRELEASED) +# renv 1.0.0 -* `renv::checkout()` gains the `actions` argument, allowing you choose whether - a lockfile is generated from the provided repositories ("snapshot"), or - whether packages are installed from the provided repositories ("restore"). +## New features -* `renv::status()` gets new, more compact, display when packages have some - inconsistent combination of being installed, used, and recorded. +* 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::init()` now prompts the user to select a snapshot type, for projects - containing a top-level DESCRIPTION file. (#1485) +* `renv::deactivate()` gains a `clean` argument: when `TRUE` it will delete + all renv files/directories, leaving the project the way it was found. -* `renv::load()` makes the loading message a little more prominent, and we fixed a - bug that prevented out-of-sync repos from being reported. +* `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::dependencies()` now discovers R dependencies inside jupyter notebooks (#929). +* `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). -* `renv::install(type = "source")` now ensures source repositories are used - in projects using [PPM](https://packagemanager.posit.co/). (#927) - -* `renv::restore()` now emits an error if called in a project that - does not contain a lockfile. (#1474) +* 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. -* If `renv::config$user.profile()` is `TRUE`, the packages it uses are now included - in the development dependencies, which means that they will be installed - by `install()` but not recorded in the snapshot. +* 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). -* renv now attempts to infer remote dependencies for GitHub packages that - appear to be installed from sources; that is, for packages without a - recognizable remote source encoded in its DESCRIPTION file. (#841) +## Bug fixes and minor improvements -* `renv::load()` gives a more informative message if a lockfile is present but - no packages are installed (#353). +* 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. -* renv now provides a small family of functions for interacting with renv - lockfiles -- see `?lockfile` for more details. (#1438) +* Fixed an issue causing "restarting interrupted promise evaluation" warnings + to be displayed when querying available packages failed. (#1260) -* renv now activates the 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::activate()` uses a three option menu that hopefully make your choices + more clear (#1372). -* renv no longer attempts to query package repositories when checking - if a project is synchronized on startup. (#812) +* `renv::dependencies()` now discovers R package dependencies inside Jupyter + notebooks (#929). -* `renv::update()` can now update packages installed from GitLab (#136) and - BitBucket (#1194). +* `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::snapshot()` now standardises pak metadata so CRAN packages installed via - pak look the same as CRAN packages installed with renv or `install.packages()` - (#1239). - -* `renv::install()` now keeps source when installing packages from source (#522). +* `renv::dependencies()` only extracts dependencies from text in YAML + headers that looks like valid R code (#1288). -* `renv::install()` now validates that binary packages can be loaded after - installation, in a manner similar to source packages. (#1275) +* `renv::dependencies()` no longer treats `box::use(module/file)` as using + package `module` (#1377). -* `renv::snapshot()` and `renv::status()` no longer track development dependencies. - `install()` will continue to install them (#1019). +* `renv::init()` now prompts the user to select a snapshot type if the project + contains a top-level DESCRIPTION file (#1485). -* `renv::install()` respects the project snapshot type, if set. +* `renv::install(type = "source")` now ensures source repositories are used + in projects using [PPM](https://packagemanager.posit.co/). (#927) -* `renv::dependencies()` now marks `Suggested` packages listed in `DESCRIPTION` files - as development dependencies regardless of whether or not they're a "package" - project. +* `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::settings$package.dependency.fields()` now only affects packages installed - directly by the user, not downstream dependencies of those packages. +* `renv::install()` respects the project snapshot type, if set. -* Fixed an issue where `renv::snapshot(exclude = <...>)` could warn when - attempting to exclude a package which was not already installed. (#1396) +* `renv::install()` now keeps source when installing packages from source (#522). -* If `renv::snapshot()` finds missing packages, a new prompt allows you to - install them before continuing (#1198). +* `renv::install()` now validates that binary packages can be loaded after + installation, in a manner similar to source packages (#1275). -* When prompting to activate a project, you now get three options that hopefully - make your choices more clear. - -* `renv::dependencies()` no longer treats `box::use(module/file)` as using - package `module` (#1377). +* `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). -* Fixed an issue where `renv::restore()` could fail to restore packages - downloaded and installed from [r-universe](https://r-universe.dev/). (#1359) +* `renv::load()` gives a more informative message if a lockfile is present but + no packages are installed (#353). -* `renv::deactivate()` gains a `clean` argument: when `TRUE` it will delete - all renv files/directories, leaving the project the way it was found. +* `renv::load()` no longer attempts to query package repositories when checking + if a project is synchronized (#812). -* `renv::status()` now works more like `renv::restore()` when package versions - are different (#675). +* `renv::load()` no longer duplicates entries on the `PATH` environment variable + (#1095). -* renv functions give a clearer error if `renv.lock` has somehow become - corrupted (#1027). +* `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::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::restore()` correctly restores packages downloaded and installed + from [r-universe](https://r-universe.dev/) (#1359). -* 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). +* `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). -* `renv::dependencies()` only extracts dependencies from text in YAML - headers that looks like valid R code (#1288). +* 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()` now shows if the R version changes, even if no packages +* `renv::snapshot()` reports if the R version changes, even if no packages change (#962). -* 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. +* `renv::snapshot(exclude = <...>)` no longer warns when attempting to exclude + a package that is not installed (#1396). -* Fixed an issue causing "restarting interrupted promise evaluation" warnings - to be displayed when querying available packages failed. (#1260) +* `renv::status()` now uses a more compact display when packages have some + inconsistent combination of being installed, used, and recorded. -* `renv::load()` no longer duplicates entries on the `PATH` environment variable - (#1095). +* `renv::status()` now works more like `renv::restore()` when package versions + are different (#675). -* renv gains a new function `renv::checkout()`, for installing the - latest-available packages from a repository. For example, one can - use `renv::checkout(date = "2023-02-08")` to install the packages available - on 2023-02-08 from the Posit [Package Manager](https://packagemanager.rstudio.com/) - repository. - -* `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::update()` can now update packages installed from GitLab (#136) and + BitBucket (#1194). -* `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::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 diff --git a/R/activate.R b/R/activate.R index 801d3e15f..f25c96451 100644 --- a/R/activate.R +++ b/R/activate.R @@ -78,7 +78,7 @@ renv_activate_impl <- function(project, renv_imbue_self(project) # restart session if requested - if (restart && !renv_tests_running()) + if (restart && !is_testing()) return(renv_restart_request(project, reason = "renv activated")) if (renv_rstudio_available()) diff --git a/R/cache.R b/R/cache.R index a1e3638f5..47f2d38bb 100644 --- a/R/cache.R +++ b/R/cache.R @@ -114,11 +114,11 @@ renv_cache_synchronize <- function(record, linkable = FALSE) { if (!file.exists(path)) return(FALSE) - # bail if the package source is unknown (assume that packages with an - # unknown source are not cacheable) + # 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"))) + if (identical(source, list(Source = "unknown"))) return(FALSE) # bail if record not cacheable diff --git a/R/config-defaults.R b/R/config-defaults.R index b9a529c08..adb9af51b 100644 --- a/R/config-defaults.R +++ b/R/config-defaults.R @@ -303,6 +303,15 @@ config <- 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", diff --git a/R/dependencies.R b/R/dependencies.R index 315dc718d..312e7fe5a 100644 --- a/R/dependencies.R +++ b/R/dependencies.R @@ -504,7 +504,7 @@ renv_dependencies_discover_preflight <- function(paths, errors) { "A large number of files (%i in total) have been discovered.", "It may take renv a long time to crawl these files for dependencies.", "Consider using .renvignore to ignore irrelevant files.", - "See `?dependencies` for more information.", + "See `?renv::dependencies` for more information.", "Set `options(renv.config.dependencies.limit = Inf)` to disable this warning.", "" ) @@ -723,7 +723,7 @@ renv_dependencies_discover_rmd_yaml_header <- function(path, mode) { # check for parameterized documents status <- catch(renv_dependencies_discover_rmd_yaml_header_params(yaml, deps)) if (inherits(status, "error")) - renv_error_report(status) + renv_dependencies_error_push(path, status) # get list of dependencies packages <- deps$data() @@ -1634,19 +1634,26 @@ renv_dependencies_scope <- function(root = NULL, scope = parent.frame()) { 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)) - # check for missing state (e.g. if internal method called directly) - state <- renv_dependencies_state() - if (!is.null(state)) { - path <- path %||% state$path - problem <- list(file = path, error = error) - state$problems$push(problem) - } + # push the error report + renv_dependencies_error_push(path, error) # return dependency list renv_dependencies_list(path, packages) diff --git a/R/download.R b/R/download.R index b22d78803..36ed0ce7f 100644 --- a/R/download.R +++ b/R/download.R @@ -655,7 +655,7 @@ renv_download_report <- function(elapsed, file) { return() info <- renv_file_info(file) - size <- if (renv_tests_running()) + size <- if (is_testing()) "XXXX bytes" else structure(info$size, class = "object_size") diff --git a/R/errors.R b/R/errors.R index 40e0d8730..7be26dfd6 100644 --- a/R/errors.R +++ b/R/errors.R @@ -156,8 +156,3 @@ renv_error_tag <- function(e) { renv_error_handler_call <- function() { as.call(list(renv_error_handler)) } - -renv_error_report <- function(error = NULL) { - if (renv_tests_running() && inherits(error, "error")) - stop(error) -} diff --git a/R/init.R b/R/init.R index 98e81281b..6940d40f7 100644 --- a/R/init.R +++ b/R/init.R @@ -119,11 +119,13 @@ init <- function(project = NULL, renv_init_settings(project, settings) # for bare inits, just activate the project - if (bare) + if (bare) { + renv_imbue_impl(project) return(renv_init_fini(project, profile, restart)) + } # compute and cache dependencies to (a) reveal problems early and (b) compute once - deps <- renv_snapshot_dependencies(project, dev = TRUE) + deps <- renv_snapshot_dependencies(project, type = type, dev = TRUE) # determine appropriate action action <- renv_init_action(project, library, lockfile, bioconductor) diff --git a/R/install.R b/R/install.R index c230973ff..f0ba7ba61 100644 --- a/R/install.R +++ b/R/install.R @@ -172,6 +172,10 @@ install <- function(packages = NULL, 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 diff --git a/R/license.R b/R/license.R index 49cdccd09..ac50304a5 100644 --- a/R/license.R +++ b/R/license.R @@ -11,7 +11,7 @@ renv_license_generate <- function() { contents <- c( paste("YEAR:", format(Sys.Date(), "%Y")), - "COPYRIGHT HOLDER: RStudio, PBC" + "COPYRIGHT HOLDER: Posit Software, PBC" ) writeLines(contents, con = "LICENSE") @@ -22,3 +22,4 @@ renv_license_generate <- function() { if (identical(.packageName, "renv")) renv_license_generate() + diff --git a/R/lockfile-api.R b/R/lockfile-api.R new file mode 100644 index 000000000..609ac7b2c --- /dev/null +++ b/R/lockfile-api.R @@ -0,0 +1,141 @@ + +# 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) %>% exclude(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) + +} diff --git a/R/lockfiles.R b/R/lockfiles.R index 971f37906..de0cfb388 100644 --- a/R/lockfiles.R +++ b/R/lockfiles.R @@ -111,12 +111,12 @@ #' @param file A file path, or \R connection. #' #' @family reproducibility -#' @name lockfile -#' @rdname lockfile +#' @name lockfiles +#' @rdname lockfiles NULL #' @param libpaths The library paths to be used when generating the lockfile. -#' @rdname lockfile +#' @rdname lockfiles #' @export lockfile_create <- function(type = settings$snapshot.type(project = project), libpaths = .libPaths(), @@ -141,7 +141,7 @@ lockfile_create <- function(type = settings$snapshot.type(project = project), ) } -#' @rdname lockfile +#' @rdname lockfiles #' @export lockfile_read <- function(file = NULL, ..., project = NULL) { project <- renv_project_resolve(project) @@ -149,7 +149,7 @@ lockfile_read <- function(file = NULL, ..., project = NULL) { renv_lockfile_read(file = file) } -#' @rdname lockfile +#' @rdname lockfiles #' @export lockfile_write <- function(lockfile, file = NULL, ..., project = NULL) { project <- renv_project_resolve(project) @@ -161,7 +161,7 @@ lockfile_write <- function(lockfile, file = NULL, ..., project = NULL) { #' #' @param repos A named vector, mapping \R repository names to their URLs. #' -#' @rdname lockfile +#' @rdname lockfiles #' @export lockfile_modify <- function(lockfile = NULL, ..., diff --git a/R/snapshot.R b/R/snapshot.R index e0ab8f84c..093d7e6c2 100644 --- a/R/snapshot.R +++ b/R/snapshot.R @@ -256,6 +256,8 @@ renv_snapshot_preserve_impl <- function(record) { renv_snapshot_preflight <- function(project, libpaths) { lapply(libpaths, renv_snapshot_preflight_impl, project = project) + if (interactive()) + renv_snapshot_preflight_check_sources(project, libpaths[[1L]]) } renv_snapshot_preflight_impl <- function(project, library) { @@ -287,6 +289,81 @@ renv_snapshot_preflight_library_exists <- function(project, library) { } +renv_snapshot_preflight_check_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_snapshot_preflight_check_sources <- function(project, library) { + + # allow user to disable snapshot validation, just in case + enabled <- config$snapshot.inference() + if (!enabled) + return(TRUE) + + # get package description files + packages <- installed_packages(library, field = "Package") + descpaths <- file.path(library, packages, "DESCRIPTION") + dcfs <- lapply(descpaths, renv_description_read) + names(dcfs) <- packages + + # try to infer sources as necessary + inferred <- map(dcfs, renv_snapshot_preflight_check_sources_infer) + inferred <- filter(inferred, Negate(is.null)) + if (length(inferred) == 0L) + return(TRUE) + + # ask used + renv_scope_options(renv.verbose = TRUE) + renv_pretty_print( + "The following package(s) were installed from sources, but may be available from the following remotes:", + sprintf("%s [%s]", format(names(inferred)), inferred), + c( + "Would you like to use these remote sources for these packages?", + "See 'snapshot.inference' in ?renv::config for more details.", + "Otherwise, these packages will be recorded with an unknown source." + ) + ) + + update <- ask("Allow renv to update the remote sources for these packages?", default = TRUE) + if (!update) + return(TRUE) + + enumerate(inferred, function(package, remote) { + record <- renv_remotes_resolve(remote) + record[["RemoteSha"]] <- NULL + renv_package_augment(file.path(library, package), record) + }) + + TRUE + +} + renv_snapshot_validate <- function(project, lockfile, libpaths) { # allow user to disable snapshot validation, just in case @@ -703,9 +780,6 @@ renv_snapshot_description <- function(path = NULL, package = NULL) { renv_snapshot_description_impl <- function(dcf, path = NULL) { - # infer remotes for packages installed from sources - dcf <- renv_snapshot_description_infer(dcf) - # figure out the package source source <- renv_snapshot_description_source(dcf) dcf[names(source)] <- source @@ -799,127 +873,32 @@ renv_snapshot_description_source <- function(dcf) { # 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), + renv_snapshot_description_source_hack(package, dcf), error = function(e) list(Source = "unknown") ) } -renv_snapshot_description_infer <- function(dcf) { - - inferred <- tryCatch( - renv_snapshot_description_infer_impl(dcf), - error = function(err) { - fmt <- "Failed to infer remote for package '%s' which was installed from source:\n%s" - warningf(fmt, dcf$Package, conditionMessage(err)) - dcf - } - ) - - # if the inferred package version appears to be less than the source one, - # don't use it - if (renv_version_lt(inferred[["Version"]], dcf[["Version"]])) - return(dcf) - - # use the inferred record - inferred - -} - -renv_snapshot_description_infer_impl <- 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(dcf) - - # skip in project synchronization checks - if (the$project_synchronized_check_running) - return(dcf) - - # check and see if this package is available from package repositories. - # if it is, then assume this is a dev. package the installed copy is newer, - # or if it has more version components than the published package - trydev <- local({ - - # check for record - package <- dcf[["Package"]] - record <- catch(renv_available_packages_latest(package)) - if (inherits(record, "error")) - return(TRUE) - - # pull out versions - lhs <- dcf[["Version"]] - rhs <- record[["Version"]] - - # check for local record being newer than the remote record - if (renv_version_gt(lhs, rhs)) - return(TRUE) - - # check for local record having more version components - if (renv_version_length(lhs) > renv_version_length(rhs)) - return(TRUE) - - # the source copy seems older than CRAN; don't try to use it - FALSE - - }) - - if (!trydev) - return(dcf) - - # 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) { - remote <- paste(matches[[2L]], matches[[3L]], sep = "/") - return(renv_remotes_resolve(remote)) - } - } - } - - # 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) - - # no match; fall back to default - dcf - -} - -renv_snapshot_description_source_hack <- function(package) { +renv_snapshot_description_source_hack <- function(package, dcf) { + # check cellar for (type in renv_package_pkgtypes()) { - - # check cellar cellar <- renv_available_packages_cellar(type) if (package %in% cellar$Package) return(list(Source = "Cellar")) + } - # check available packages - dbs <- available_packages(type = type, quiet = TRUE) - for (i in seq_along(dbs)) { - if (package %in% dbs[[i]]$Package) { - return(list( - Source = "Repository", - Repository = names(dbs)[[i]] - )) - } - } + # 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")) - list(Source = "unknown") + # ok, this package appears to be from a package repository + list(Source = "Repository", Repository = latest[["Repository"]]) } @@ -1035,7 +1014,7 @@ renv_snapshot_dependencies_impl <- function(project, type = NULL, dev = FALSE) { "", "NOTE: Dependency discovery took %s during snapshot.", "Consider using .renvignore to ignore files, or switching to explicit snapshots.", - "See `?dependencies` for more information.", + "See `?renv::dependencies` for more information.", "" ) @@ -1056,7 +1035,7 @@ renv_snapshot_packages <- function(packages, libpaths, project) { ignored <- c( renv_packages_base(), renv_project_ignored_packages(project = project), - if (is_testing()) "renv" + if (renv_tests_running()) "renv" ) callback <- function(package, location, project) { diff --git a/R/status.R b/R/status.R index 35441ccc6..dfd333555 100644 --- a/R/status.R +++ b/R/status.R @@ -117,48 +117,69 @@ status <- function(project = NULL, invisible(renv_status_impl(project, libpaths, lockpath, sources, cache)) } -renv_status_impl <- function(project, libpaths, lockpath, sources, cache) { +renv_status_check_initialized <- function(project, lockpath = NULL) { - # check to see if we've initialized this project - has_library <- file.exists(renv_paths_library(project = project)) - has_lockfile <- file.exists(lockpath) - - if (!has_library || !has_lockfile) { - if (!has_library && !has_lockfile) { - writef(c( - "Project does not use renv.", - "Call renv::init() to setup project to use renv." - )) - } else if (!has_library) { - writef(c( - "Project lacks a library.", - "Call renv::restore() to install packages defined in lockfile." - )) - } else { - writef(c( - "Project lacks a lockfile.", - "Call renv::snapshot() to create one." - )) - } - - return(list(library = list(), lockfile = list(), synchronized = FALSE)) + projlib <- renv_paths_library(project = project) + lockpath <- lockpath %||% renv_paths_lockfile(project = project) + + haslib <- file.exists(projlib) + haslock <- file.exists(lockpath) + + if (haslib && haslock) + return(TRUE) + + 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_impl <- function(project, libpaths, lockpath, sources, cache) { + # mark status as running the$status_running <- TRUE defer(the$status_running <- FALSE) + # check to see if we've initialized this project + if (!renv_status_check_initialized(project, lockpath)) { + return(list( + library = list(Packages = named(list())), + lockfile = list(Packages = named(list())), + synchronized = FALSE + )) + } + # get all dependencies, including transitive dependencies <- renv_snapshot_dependencies(project, dev = FALSE) packages <- sort(union(dependencies, "renv")) - paths <- renv_package_dependencies(packages, project = project) + paths <- renv_package_dependencies(packages, libpaths = libpaths, project = project) packages <- as.character(names(paths)) - # get lockfile records - lockfile <- renv_lockfile_records(renv_lockfile_read(lockpath)) + # read project lockfile + lockfile <- renv_lockfile_read(lockpath) - # get library records - library <- renv_snapshot_libpaths(libpaths = libpaths, 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( @@ -166,9 +187,10 @@ renv_status_impl <- function(project, libpaths, lockpath, sources, cache) { renv_packages_base(), if (renv_tests_running()) "renv" ) + packages <- setdiff(packages, ignored) - lockfile <- exclude(lockfile, ignored) - library <- exclude(library, ignored) + renv_lockfile_records(lockfile) <- exclude(renv_lockfile_records(lockfile), ignored) + renv_lockfile_records(library) <- exclude(renv_lockfile_records(library), ignored) synchronized <- renv_status_check_consistent(lockfile, library, packages) && @@ -183,9 +205,9 @@ renv_status_impl <- function(project, libpaths, lockpath, sources, cache) { renv_status_check_cache(project) if (synchronized) - writef("No issues found.") + writef("No issues found -- the project is in a consistent state.") else - writef(c("", "See ?status() for advice on resolving the issues.")) + writef(c("", "See ?renv::status() for advice on resolving these issues.")) list( library = library, @@ -196,11 +218,14 @@ renv_status_impl <- function(project, libpaths, lockpath, sources, cache) { } renv_status_check_unknown_sources <- function(project, lockfile) { - renv_check_unknown_source(lockfile, project) + 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( @@ -229,10 +254,16 @@ renv_status_check_consistent <- function(lockfile, library, used) { writef() print(issues, row.names = FALSE, right = FALSE) } + 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") @@ -248,6 +279,7 @@ renv_status_check_synchronized <- function(lockfile, library) { ) FALSE + } renv_status_check_cache <- function(project) { diff --git a/R/tests.R b/R/tests.R index 7519ba012..b530d52d0 100644 --- a/R/tests.R +++ b/R/tests.R @@ -1,6 +1,10 @@ the$tests_root <- NULL +# NOTE: Prefer using 'is_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) } diff --git a/R/verbose.R b/R/verbose.R index 468538818..3008f9ea5 100644 --- a/R/verbose.R +++ b/R/verbose.R @@ -9,14 +9,17 @@ renv_verbose <- function() { if (!is.na(verbose)) return(as.logical(verbose)) - if (is_testing()) { + if (is_testing()) return(FALSE) - } interactive() || !renv_tests_running() } +# NOTE: Prefer using 'is_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. is_testing <- function() { identical(Sys.getenv("TESTTHAT"), "true") } diff --git a/README.md b/README.md index 02d3810db..1da0963a1 100644 --- a/README.md +++ b/README.md @@ -41,15 +41,17 @@ install.packages("renv") 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. -Use `renv::init()` to initialize renv with a new or existing project. -This will set up your project with a private library, containing all the -packages you’re currently using. The packages (and all the metadata -needed to reinstall them) are also recorded into a *lockfile*, -`renv.lock`. - -As you work in your project, you will install and upgrade packages. -After you’ve confirmed your code works as expected, call -`renv::snapshot()` to record their versions in the lockfile. +Use `renv::init()` to initialize renv in a new or existing project. This +will set up up **project library**, containing all the packages you’re +currently using. The packages (and all the metadata needed to reinstall +them) are recorded into a **lockfile**, `renv.lock`, and a `.Rprofile` +ensures that the library is used every time you open that project. + +As you continue to work on your project, you will install and upgrade +packages, either using `install.packages()` and `update.packages` or +`renv::install()` and `renv::update()`. After you’ve confirmed your code +works as expected, use `renv::snapshot()` to record the packages and +their sources in the lockfile. Later, if you need to share your code with someone else or run your code on new machine, your collaborator (or you) can call `renv::restore()` to @@ -57,9 +59,10 @@ reinstall the specific package versions recorded in the lockfile. ## Learning more -If this is your first time using renv, we strongly recommend reading the -[Introduction to -renv](https://rstudio.github.io/renv/articles/renv.html) vignette. +If this is your first time using renv, we strongly recommend starting +with the [Introduction to +renv](https://rstudio.github.io/renv/articles/renv.html) vignette: this +will help you understand the most important verbs and nouns of renv. If you have a question about renv, please first check the [FAQ](https://rstudio.github.io/renv/articles/faq.html) to see whether diff --git a/_pkgdown.yml b/_pkgdown.yml index 13586b857..6aa49fc18 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -44,7 +44,7 @@ reference: - title: Lockfile Management contents: - - lockfile + - lockfiles - record - remote - modify @@ -71,12 +71,10 @@ reference: - title: internal contents: + - renv-package - sandbox - imbue - - lockfile - graph - - renv-package - - renv_lockfile_from_manifest - sandbox articles: diff --git a/inst/config.yml b/inst/config.yml index b645a6e72..ea367be99 100644 --- a/inst/config.yml +++ b/inst/config.yml @@ -146,7 +146,7 @@ description: > A character vector of library paths, to be used by [hydrate()] when attempting to hydrate projects. When empty, the default set of library paths - (as documented in `?hydrate`) are used instead. See [`hydrate()`] for more details. + (as documented in `?renv::hydrate`) are used instead. See [`hydrate()`] for more details. - name: "install.build" type: "logical[1]" @@ -211,8 +211,7 @@ type: "logical[1]" default: false description: > - Attempt to download binaries from [MRAN](https://mran.microsoft.com/) - during restore? See `vignette("mran", package = "renv")` for more details. + DEPRECATED: MRAN is no longer maintained by Microsoft. - name: "pak.enabled" type: "logical[1]" @@ -248,9 +247,8 @@ default: "https://packagemanager.posit.co/cran/latest" description: > The default PPM URL to be used for new renv projects. Defaults to the - [CRAN mirror](https://packagemanager.posit.co/cran/latest) maintained by - Posit. This option can be changed if you'd like renv to use a separate - package manager instance. + CRAN mirror maintained by Posit at . + This option can be changed if you'd like renv to use an alternate package manager instance. - name: "repos.override" type: "character[*]" @@ -291,6 +289,16 @@ and `remove.packages()`, delegating these functions to [`renv::install()`], [`renv::update()`] and [`renv::remove()`] as appropriate. +- name: "snapshot.inference" + type: "logical[1]" + default: true + description: > + For packages which were installed from local sources, should renv try to infer the + package's remote from its DESCRIPTION file? When `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. + - name: "snapshot.validate" type: "logical[1]" default: true diff --git a/man/config.Rd b/man/config.Rd index 0973f47a9..fd60883dd 100644 --- a/man/config.Rd +++ b/man/config.Rd @@ -81,7 +81,7 @@ Defaults to \code{TRUE}.} 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{?hydrate}) are used instead. See \code{\link[=hydrate]{hydrate()}} for more details. +\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}.} @@ -97,7 +97,7 @@ Defaults to \code{TRUE}.} 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}{Attempt to download binaries from \href{https://mran.microsoft.com/}{MRAN} during restore? See \code{vignette("mran", package = "renv")} for more details. +\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}.} @@ -105,7 +105,7 @@ Defaults to \code{FALSE}.} 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 \href{https://packagemanager.posit.co/cran/latest}{CRAN mirror} maintained by Posit. This option can be changed if you'd like renv to use a separate package manager instance. +\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}.} @@ -116,6 +116,8 @@ Sandboxing is done by linking or copying system packages into a separate library 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. diff --git a/man/lockfile-api.Rd b/man/lockfile-api.Rd new file mode 100644 index 000000000..8c9d63f00 --- /dev/null +++ b/man/lockfile-api.Rd @@ -0,0 +1,47 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/lockfile-api.R +\name{lockfile-api} +\alias{lockfile-api} +\alias{lockfile} +\title{Programmatically Create and Modify a Lockfile} +\usage{ +lockfile(file = NULL, project = NULL) +} +\arguments{ +\item{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 \code{file = NA} 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.} +} +\description{ +This function provides an API for creating and modifying \code{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. +} +\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") + +} + +} +\seealso{ +\code{\link{lockfiles}}, for a description of the structure of an +\code{renv} lockfile. +} +\keyword{internal} diff --git a/man/lockfile.Rd b/man/lockfiles.Rd similarity index 99% rename from man/lockfile.Rd rename to man/lockfiles.Rd index 89d8ea019..45304c95e 100644 --- a/man/lockfile.Rd +++ b/man/lockfiles.Rd @@ -1,7 +1,7 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/lockfiles.R -\name{lockfile} -\alias{lockfile} +\name{lockfiles} +\alias{lockfiles} \alias{lockfile_create} \alias{lockfile_read} \alias{lockfile_write} diff --git a/man/renv-package.Rd b/man/renv-package.Rd index f9521b526..b73fadf22 100644 --- a/man/renv-package.Rd +++ b/man/renv-package.Rd @@ -18,6 +18,7 @@ installations of a package that might otherwise be shared across projects. 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} } diff --git a/man/restore.Rd b/man/restore.Rd index ddc67a410..e2678e76b 100644 --- a/man/restore.Rd +++ b/man/restore.Rd @@ -106,7 +106,7 @@ options(renv.config.auto.snapshot = auto.snapshot) } \seealso{ Other reproducibility: -\code{\link{lockfile}}, +\code{\link{lockfiles}}, \code{\link{snapshot}()} } \concept{reproducibility} diff --git a/man/snapshot.Rd b/man/snapshot.Rd index a5ffcbf6d..80f1242bd 100644 --- a/man/snapshot.Rd +++ b/man/snapshot.Rd @@ -171,7 +171,7 @@ options(renv.config.auto.snapshot = auto.snapshot) } \seealso{ Other reproducibility: -\code{\link{lockfile}}, +\code{\link{lockfiles}}, \code{\link{restore}()} } \concept{reproducibility} diff --git a/tests/testthat/_snaps/bioconductor.md b/tests/testthat/_snaps/bioconductor.md index 03f45e03d..ae8305079 100644 --- a/tests/testthat/_snaps/bioconductor.md +++ b/tests/testthat/_snaps/bioconductor.md @@ -10,14 +10,14 @@ BiocManager y n y BiocVersion y n y - See ?status() for advice on resolving the issues. + See ?renv::status() for advice on resolving these issues. --- Code status() Output - No issues found. + No issues found -- the project is in a consistent state. # auto-bioc install happens silently diff --git a/tests/testthat/_snaps/dependencies.md b/tests/testthat/_snaps/dependencies.md index 566b8c927..bf04cebce 100644 --- a/tests/testthat/_snaps/dependencies.md +++ b/tests/testthat/_snaps/dependencies.md @@ -6,7 +6,7 @@ A large number of files (7 in total) have been discovered. It may take renv a long time to crawl these files for dependencies. Consider using .renvignore to ignore irrelevant files. - See `?dependencies` for more information. + See `?renv::dependencies` for more information. Set `options(renv.config.dependencies.limit = Inf)` to disable this warning. Finding R package dependencies ... Done! @@ -19,7 +19,7 @@ A large number of files (11 in total) have been discovered. It may take renv a long time to crawl these files for dependencies. Consider using .renvignore to ignore irrelevant files. - See `?dependencies` for more information. + See `?renv::dependencies` for more information. Set `options(renv.config.dependencies.limit = Inf)` to disable this warning. Finding R package dependencies ... Done! diff --git a/tests/testthat/_snaps/snapshot.md b/tests/testthat/_snaps/snapshot.md index dbcf2e3e6..b4c7e18ce 100644 --- a/tests/testthat/_snaps/snapshot.md +++ b/tests/testthat/_snaps/snapshot.md @@ -206,11 +206,6 @@ Output - Automatic snapshot has updated '/renv.lock'. -# we can infer github remotes from packages installed from sources - - Code - . <- renv_snapshot_description(path = descfile) - # we report if dependency discover during snapshot() is slow Code @@ -219,7 +214,7 @@ NOTE: Dependency discovery took XXXX seconds during snapshot. Consider using .renvignore to ignore files, or switching to explicit snapshots. - See `?dependencies` for more information. + See `?renv::dependencies` for more information. - The lockfile is already up to date. diff --git a/tests/testthat/_snaps/status.md b/tests/testthat/_snaps/status.md index 641b2f380..21c567b82 100644 --- a/tests/testthat/_snaps/status.md +++ b/tests/testthat/_snaps/status.md @@ -3,31 +3,31 @@ Code status() Output - Project does not use renv. - Call renv::init() to setup project to use renv. + This project does not appear to be using renv. + Use renv::init() to initialize the project. --- Code status() Output - Project lacks a lockfile. - Call renv::snapshot() to create one. + This project does not contain a lockfile. + Use renv::snapshot() to create a lockfile. --- Code status() Output - Project lacks a library. - Call renv::restore() to install packages defined in lockfile. + 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. + No issues found -- the project is in a consistent state. # reports synchronisation problems with non-installed packages @@ -41,7 +41,7 @@ egg n y y oatmeal n y ? - See ?status() for advice on resolving the issues. + See ?renv::status() for advice on resolving these issues. # reports synchronisation problems with installed packages @@ -54,7 +54,7 @@ bread y n y egg y y n - See ?status() for advice on resolving the issues. + See ?renv::status() for advice on resolving these issues. # reports version differences @@ -68,5 +68,5 @@ - oatmeal [repo: * -> CRAN; ver: 0.9.0 -> 1.0.0] - See ?status() for advice on resolving the issues. + See ?renv::status() for advice on resolving these issues. diff --git a/tests/testthat/test-snapshot.R b/tests/testthat/test-snapshot.R index 94a3be6e8..d38fe34df 100644 --- a/tests/testthat/test-snapshot.R +++ b/tests/testthat/test-snapshot.R @@ -486,28 +486,28 @@ test_that("automatic snapshot works as expected", { }) -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 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", { diff --git a/vignettes/package-sources.Rmd b/vignettes/package-sources.Rmd index 6ca7b91b1..467927b90 100644 --- a/vignettes/package-sources.Rmd +++ b/vignettes/package-sources.Rmd @@ -49,10 +49,9 @@ 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_. See [here](cellar.html) for more details. -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. +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 @@ -221,60 +220,7 @@ 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. -## MRAN - -When working on macOS and Windows, users will often download and install -package binaries, rather than sources, as provided by CRAN. However, CRAN only -provides binaries for the latest-available version of a package, and so binaries -for older versions of a package will become inaccessible as that package is -updated. - -[MRAN](https://mran.microsoft.com/) is a service provided by Microsoft that -mirrors CRAN every day, and allows users to use particular snapshots of CRAN -as their active repositories within their R session. - -Starting with `renv 0.10.0`, renv can also make use of MRAN binary packages -when restoring packages on Windows and macOS. When invoking `renv::install()` -or `renv::restore()`, renv will attempt to install the package from the -latest-available MRAN snapshot that still had this package available. - -As an example, the stringi package was updated from version `1.4.5` to version -`1.4.6` on 2020-02-17, and binaries for that version of stringi were made -available for macOS on 2020-02-20. Because of this, the last date on which -`stringi 1.4.5` macOS binaries were available on CRAN was `2020-02-19`. - -Fortunately, because MRAN snapshotted CRAN on this date, we can retrieve that -binary. For example, on macOS with R 3.6: - -```r -> renv::install("stringi@1.4.5") -Retrieving 'https://mran.microsoft.com/snapshot/2020-02-19/bin/macosx/el-capitan/contrib/3.6/stringi_1.4.5.tgz' ... - OK [file is up to date] -Installing stringi [1.4.5] ... - OK [installed binary] -``` - -When binaries are available from MRAN, renv should transparently download and -use them when possible. When binaries are not available, renv will fall back -to the old behavior, and attempt to install packages from sources. - -If you prefer not to make use of MRAN (e.g. because you are using renv in an -environment without external internet access), you can disable it with: - -``` -options(renv.config.mran.enabled = FALSE) -``` - -### Caveats - -While being able to install binary packages from arbitrary MRAN snapshots can -be useful, one must be aware of potential incompatibility issues. In particular, -we need to consider: - -- ABI compatibility between different versions of binaries; -- Inadvertent build-time dependencies taken by a package. - -#### ABI compatibility +## 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 @@ -291,7 +237,8 @@ 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 + +## 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: diff --git a/vignettes/packrat.Rmd b/vignettes/packrat.Rmd index 136421b42..c57d6473e 100644 --- a/vignettes/packrat.Rmd +++ b/vignettes/packrat.Rmd @@ -1,8 +1,8 @@ --- -title: "packrat vs renv" +title: "packrat vs. renv" output: rmarkdown::html_vignette vignette: > - %\VignetteIndexEntry{packrat} + %\VignetteIndexEntry{packrat vs. renv} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- diff --git a/vignettes/profiles.Rmd b/vignettes/profiles.Rmd index ee2360a1c..08236f6b2 100644 --- a/vignettes/profiles.Rmd +++ b/vignettes/profiles.Rmd @@ -111,5 +111,5 @@ be used when the lockfile is generated. See `?renv::snapshot` for more details. -[shiny]: https://shiny.rstudio.com/ +[shiny]: https://shiny.posit.co/ [tidyverse]: https://www.tidyverse.org/ diff --git a/vignettes/renv.Rmd b/vignettes/renv.Rmd index 315f7dc27..0df675f87 100644 --- a/vignettes/renv.Rmd +++ b/vignettes/renv.Rmd @@ -58,7 +58,7 @@ You can see your current libraries with `.libPaths()` and see which packages are 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 which is available to install packages from in just about every R session. -Other freely available repositories include [Bioconductor](https://bioconductor.org), the [Posit Public Package Manager](https://packagemanager.rstudio.com/client/#/), and [R Universe](https://r-universe.dev/search/) (which turns GitHub organisations into repositories). +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/search/) (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. diff --git a/vignettes/rsconnect.Rmd b/vignettes/rsconnect.Rmd index ad73808fd..685c97836 100644 --- a/vignettes/rsconnect.Rmd +++ b/vignettes/rsconnect.Rmd @@ -20,7 +20,7 @@ knitr::opts_chunk$set( [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.rstudio.com/) or [RMarkdown +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