diff --git a/.github/workflows/pkgdown.yaml b/.github/workflows/pkgdown.yaml index 087f0b05..ed7650c7 100644 --- a/.github/workflows/pkgdown.yaml +++ b/.github/workflows/pkgdown.yaml @@ -19,6 +19,8 @@ jobs: group: pkgdown-${{ github.event_name != 'pull_request' || github.run_id }} env: GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} + permissions: + contents: write steps: - uses: actions/checkout@v3 diff --git a/.github/workflows/test-coverage.yaml b/.github/workflows/test-coverage.yaml index 5ff1ccf5..65742c9b 100644 --- a/.github/workflows/test-coverage.yaml +++ b/.github/workflows/test-coverage.yaml @@ -32,7 +32,7 @@ jobs: covr::codecov( quiet = FALSE, clean = FALSE, - install_path = file.path(Sys.getenv("RUNNER_TEMP"), "package") + install_path = file.path(normalizePath(Sys.getenv("RUNNER_TEMP"), winslash = "/"), "package") ) shell: Rscript {0} diff --git a/LICENSE b/LICENSE index d9a2760c..c47b3cf6 100644 --- a/LICENSE +++ b/LICENSE @@ -1,2 +1,2 @@ -YEAR: 2021 +YEAR: 2023 COPYRIGHT HOLDER: vetiver authors diff --git a/LICENSE.md b/LICENSE.md index d9ed87bf..199e633e 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,6 +1,6 @@ # MIT License -Copyright (c) 2021 vetiver authors +Copyright (c) 2023 vetiver authors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/R/attach-pkgs.R b/R/attach-pkgs.R index 11554bb8..5e05a3b5 100644 --- a/R/attach-pkgs.R +++ b/R/attach-pkgs.R @@ -47,13 +47,13 @@ load_pkgs <- function(pkgs) { namespace_handling(pkgs, loadNamespace, "Namespace(s) could not be loaded:") } -namespace_handling <- function(pkgs, func, error_msg) { +namespace_handling <- function(pkgs, func, error_msg, call = rlang::caller_env()) { safe_load <- safely(withr::with_preserve_seed(func)) did_load <- map(pkgs, safe_load) bad <- compact(map(did_load, "error")) bad <- map_chr(bad, "package") if (length(bad) >= 1) { - abort(c(error_msg, bad)) + abort(c(error_msg, bad), call = call) } invisible(TRUE) diff --git a/R/handlers.R b/R/handlers.R index aa5b30b1..cfd40bcc 100644 --- a/R/handlers.R +++ b/R/handlers.R @@ -86,9 +86,9 @@ vetiver_type_convert <- function(new_data, ptype) { new_data } -type_convert_strict <- function(new_data, col_types) { +type_convert_strict <- function(new_data, col_types, call = rlang::caller_env()) { warn_to_error <- function(e) { - abort(conditionMessage(e)) + abort(conditionMessage(e), call = call) } tryCatch( diff --git a/R/prototype.R b/R/prototype.R index 8816c3ef..0250603d 100644 --- a/R/prototype.R +++ b/R/prototype.R @@ -92,9 +92,9 @@ expr_contains <- function(expr, what) { ) } -call_contains <- function(expr, what) { +call_contains <- function(expr, what, call = rlang::caller_env()) { if (length(expr) == 0L) { - abort("Internal error, `expr` should be at least length 1.") + abort("Internal error, `expr` should be at least length 1.", call = call) } # Recurse into elements diff --git a/R/renv.R b/R/renv.R index 5e42e4cd..61ca0804 100644 --- a/R/renv.R +++ b/R/renv.R @@ -1,7 +1,7 @@ # -# renv 1.0.1.9000 [rstudio/renv#9f4cbe1]: A dependency management toolkit for R. -# Generated using `renv:::vendor()` at 2023-08-14 12:25:04. +# renv 1.0.3.9000 [rstudio/renv#4b11818]: A dependency management toolkit for R. +# Generated using `renv:::vendor()` at 2023-10-31 16:35:16.988562. # @@ -70,7 +70,7 @@ renv$initialize <- function() { # initialize metadata renv$the$metadata <- list( embedded = TRUE, - version = structure("1.0.1.9000", sha = "9f4cbe1e1024a31a9fc0601d3cd4e44edb86b728") + version = structure("1.0.3.9000", sha = "4b11818ca81897f10bf1def73db6b69c9fd1af0f") ) # run our load / attach hooks so internal state is initialized diff --git a/R/write-docker.R b/R/write-docker.R index 984e4cd7..65ea10ca 100644 --- a/R/write-docker.R +++ b/R/write-docker.R @@ -138,7 +138,7 @@ vetiver_required_pkgs <- function(pkgs) { sort(unique(pkgs)) } -glue_sys_reqs <- function(pkgs) { +glue_sys_reqs <- function(pkgs, call = rlang::caller_env()) { rlang::check_installed(c("curl", "jsonlite")) rspm <- Sys.getenv("RSPM_ROOT", DEFAULT_RSPM) rspm_repo_id <- Sys.getenv("RSPM_REPO_ID", DEFAULT_RSPM_REPO_ID) @@ -153,7 +153,7 @@ glue_sys_reqs <- function(pkgs) { res <- curl::curl_fetch_memory(req_url) sys_reqs <- jsonlite::fromJSON(rawToChar(res$content), simplifyVector = FALSE) if (!is.null(sys_reqs$error)) { - rlang::abort(sys_reqs$error) + rlang::abort(sys_reqs$error, call = call) } sys_reqs <- map(sys_reqs$requirements, pluck, "requirements", "packages") sys_reqs <- sort(unique(unlist(sys_reqs))) @@ -173,6 +173,7 @@ glue_sys_reqs <- function(pkgs) { #' function to create these needed files in the directory located at `path`. #' #' @inheritParams vetiver_write_plumber +#' @par #' @inheritParams vetiver_deploy_rsconnect #' @param path A path to write the Plumber file, Dockerfile, and lockfile, #' capturing the model's dependencies. diff --git a/README.Rmd b/README.Rmd index c233e596..cef95632 100644 --- a/README.Rmd +++ b/README.Rmd @@ -13,7 +13,7 @@ knitr::opts_chunk$set( ) ``` -# vetiver +# vetiver vetiver website [![R-CMD-check](https://github.com/rstudio/vetiver-r/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/rstudio/vetiver-r/actions/workflows/R-CMD-check.yaml) diff --git a/README.md b/README.md index 276ba1b9..94dcc977 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ -# vetiver +# vetiver vetiver website @@ -24,23 +24,23 @@ API endpoint. The vetiver package is extensible, with generics that can support many kinds of models, and available for both R and Python. To learn more about vetiver, see: -- the documentation at -- the Python package at +- the documentation at +- the Python package at You can use vetiver with: -- a [tidymodels](https://www.tidymodels.org/) workflow (including - [stacks](https://stacks.tidymodels.org/)) -- [caret](https://topepo.github.io/caret/) -- [mlr3](https://mlr3.mlr-org.com/) -- [XGBoost](https://xgboost.readthedocs.io/en/latest/R-package/) -- [ranger](https://cran.r-project.org/package=ranger) -- [`lm()`](https://stat.ethz.ch/R-manual/R-patched/library/stats/html/lm.html) - and - [`glm()`](https://stat.ethz.ch/R-manual/R-patched/library/stats/html/glm.html) -- GAMS fit with [mgcv](https://CRAN.R-project.org/package=mgcv) -- [keras](https://tensorflow.rstudio.com/) -- [the luz API for torch](https://torch.mlverse.org/) +- a [tidymodels](https://www.tidymodels.org/) workflow (including + [stacks](https://stacks.tidymodels.org/)) +- [caret](https://topepo.github.io/caret/) +- [mlr3](https://mlr3.mlr-org.com/) +- [XGBoost](https://xgboost.readthedocs.io/en/latest/R-package/) +- [ranger](https://cran.r-project.org/package=ranger) +- [`lm()`](https://stat.ethz.ch/R-manual/R-patched/library/stats/html/lm.html) + and + [`glm()`](https://stat.ethz.ch/R-manual/R-patched/library/stats/html/glm.html) +- GAMS fit with [mgcv](https://CRAN.R-project.org/package=mgcv) +- [keras](https://tensorflow.rstudio.com/) +- [the luz API for torch](https://torch.mlverse.org/) ## Installation @@ -160,14 +160,14 @@ This project is released with a [Contributor Code of Conduct](https://www.contributor-covenant.org/version/2/1/CODE_OF_CONDUCT.html). By contributing to this project, you agree to abide by its terms. -- For questions and discussions about modeling, machine learning, and - MLOps please [post on RStudio - Community](https://community.rstudio.com/new-topic?category_id=15&tags=vetiver,question). +- For questions and discussions about modeling, machine learning, and + MLOps please [post on RStudio + Community](https://community.rstudio.com/new-topic?category_id=15&tags=vetiver,question). -- If you think you have encountered a bug, please [submit an - issue](https://github.com/rstudio/vetiver-r/issues). +- If you think you have encountered a bug, please [submit an + issue](https://github.com/rstudio/vetiver-r/issues). -- Either way, learn how to create and share a - [reprex](https://reprex.tidyverse.org/articles/articles/learn-reprex.html) - (a minimal, reproducible example), to clearly communicate about your - code. +- Either way, learn how to create and share a + [reprex](https://reprex.tidyverse.org/articles/articles/learn-reprex.html) + (a minimal, reproducible example), to clearly communicate about your + code. diff --git a/_pkgdown.yml b/_pkgdown.yml index d7cb0a63..5a2e55d9 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -1,3 +1,4 @@ +url: https://rstudio.github.io/vetiver-r/ template: bootstrap: 5 package: tidytemplate diff --git a/inst/vendor/renv.R b/inst/vendor/renv.R index 7ad0419d..843df4ea 100644 --- a/inst/vendor/renv.R +++ b/inst/vendor/renv.R @@ -1,6 +1,6 @@ # -# renv 1.0.1.9000 [rstudio/renv#9f4cbe1]: A dependency management toolkit for R. -# Generated using `renv:::vendor()` at 2023-08-14 12:25:04. +# renv 1.0.3.9000 [rstudio/renv#4b11818]: A dependency management toolkit for R. +# Generated using `renv:::vendor()` at 2023-10-31 16:35:16.988562. # # aaa.R ---------------------------------------------------------------------- @@ -9,6 +9,11 @@ # global variables the <- new.env(parent = emptyenv()) +# detect if we're running on CI +ci <- function() { + !is.na(Sys.getenv("CI", unset = NA)) +} + # detect if we're running within R CMD build building <- function() { nzchar(Sys.getenv("R_CMD")) && @@ -887,8 +892,7 @@ renv_autoload_impl <- function() { return(FALSE) # bail if load is already being called - loading <- getOption("renv.load.running") - if (identical(loading, TRUE)) + if (the$load_running) return(FALSE) # avoid recursion @@ -3027,19 +3031,6 @@ renv_bootstrap_run <- function(version, libpath) { } -renv_bootstrap_in_rstudio <- function() { - commandArgs()[[1]] == "RStudio" -} - -# Used to work around buglet in RStudio if hook uses readline -renv_bootstrap_flush_console <- function() { - tryCatch({ - tools <- as.environment("tools:rstudio") - tools$.rs.api.sendToConsole("", echo = FALSE, focus = FALSE) - }, error = function(cnd) {}) -} - - # cache.R -------------------------------------------------------------------- @@ -3573,37 +3564,8 @@ renv_cache_format_path <- function(paths) { # nocov end renv_cache_clean_empty <- function(cache = NULL) { - - # no-op for Solaris - if (renv_platform_solaris()) - return(FALSE) - - # move to cache root caches <- cache %||% renv_paths_cache() - for (cache in caches) - renv_cache_clean_empty_impl(cache) - - TRUE - -} - -renv_cache_clean_empty_impl <- function(cache) { - - # move to cache directory - renv_scope_wd(cache) - - # construct 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 - + map(caches, renv_cleanse_empty) } renv_cache_package_validate <- function(path) { @@ -4508,6 +4470,7 @@ renv_clean_cache <- function(project, prompt) { # remove the directories unlink(diff, recursive = TRUE) renv_cache_clean_empty() + writef("- %i package(s) have been removed.", length(diff)) TRUE @@ -4515,6 +4478,77 @@ renv_clean_cache <- function(project, prompt) { # nocov end +# cleanse.R ------------------------------------------------------------------ + + +# 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 + +} + + # cli.R ---------------------------------------------------------------------- @@ -6204,6 +6238,7 @@ renv_dependencies_callback <- function(path) { cbname <- list( ".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), @@ -6345,7 +6380,7 @@ renv_dependencies_find_dir_children <- function(path, root, depth) { # remove hard-coded ignores # (only keep DESCRIPTION files at the top level) - ignored <- c("packrat", "renv", "revdep", "vendor", if (depth) "DESCRIPTION") + ignored <- c("packrat", "renv", "revdep", "vendor", if (depth) c("DESCRIPTION", "NAMESPACE")) children <- children[!basename(children) %in% ignored] # compute exclusions @@ -6500,6 +6535,37 @@ renv_dependencies_discover_description <- function(path, } +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 @@ -9270,9 +9336,12 @@ renv_dynamic_envir <- function(envir = NULL) { renv_dynamic_envir_impl <- function() { - for (envir in sys.frames()) + 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") @@ -10453,7 +10522,7 @@ renv_file_copy_dir_cp <- function(source, target) { target <- path.expand(target) # build 'cp' arguments - args <- c("-pPR", renv_shell_path(source), renv_shell_path(target)) + args <- c("-PR", renv_shell_path(source), renv_shell_path(target)) # execute command renv_system_exec("cp", args, action = "copying directory") @@ -11059,9 +11128,7 @@ renv_git_root <- function(project) { #' #' @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"))`. See -#' for a full list of the attributes supported by `graphViz`. +#' the graph from left to right, you can use `list(graph = c(rankdir = "LR"))`. #' #' @examples #' @@ -12900,8 +12967,8 @@ the$init_running <- FALSE #' the project library and the `.Rprofile` that ensures renv will be #' used in all future sessions. #' -#' 1. Discover the packages that you currently and install them into an -#' project library (as described in [hydrate()]). +#' 1. Discover the packages that are currently being used in your project 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()]). @@ -13110,6 +13177,13 @@ renv_init_action_conflict_library <- function(project, library, lockfile) { if (!interactive()) return("nothing") + # if the project library exists, but it's empty, or only renv is installed, + # 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 || identical(db$Package, "renv")) + 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.", @@ -13190,31 +13264,13 @@ renv_init_repos <- function() { if (!enabled) return(repos) - # if we're using the global CDN from RStudio, use PPM instead + # check for default repositories set rstudio <- attr(repos, "RStudio", exact = TRUE) - if (identical(rstudio, TRUE)) { + if (identical(rstudio, TRUE) || identical(repos, list(CRAN = "@CRAN@"))) { repos[["CRAN"]] <- config$ppm.url() return(repos) } - # otherwise, check for some common 'default' CRAN settings - cran <- repos[["CRAN"]] - if (is.character(cran) && length(cran) == 1L) { - cran <- sub("/*$", "", cran) - defaults <- c( - "@CRAN@", - "https://cloud.R-project.org", - "https://cran.rstudio.com", - "https://cran.rstudio.org" - ) - - if (tolower(cran) %in% tolower(defaults)) { - repos[["CRAN"]] <- config$ppm.url() - return(repos) - } - - } - # repos appears to have been configured separately; just use it repos @@ -13302,6 +13358,7 @@ the$install_step_width <- 48L #' fulfilling the installation request. #' #' @inherit renv-params +#' #' @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, @@ -13320,6 +13377,10 @@ the$install_step_width <- 48L #' So to install from the `subdir` subdirectory of GitHub package #' `username/repo` you'd use `"username/repo:subdir`. #' +#' @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. +#' #' @return A named list of package records which were installed by renv. #' #' @export @@ -13352,6 +13413,7 @@ the$install_step_width <- 48L #' } install <- function(packages = NULL, ..., + exclude = NULL, library = NULL, type = NULL, rebuild = FALSE, @@ -13410,6 +13472,11 @@ install <- function(packages = NULL, # 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) + if (empty(packages)) { writef("- There are no packages to install.") return(invisible(list())) @@ -13901,7 +13968,7 @@ renv_install_test <- function(package) { }, list(package = package)) # write it to a tempfile - script <- renv_scope_tempfile("renv-install-") + script <- renv_scope_tempfile("renv-install-", fileext = ".R") writeLines(deparse(code), con = script) # check that the package can be loaded in a separate process @@ -14890,6 +14957,9 @@ if (identical(.packageName, "renv")) # load.R --------------------------------------------------------------------- +# are we currently running 'load()'? +the$load_running <- FALSE + #' Load a project #' #' @description @@ -14960,11 +15030,7 @@ load <- function(project = NULL, quiet = FALSE) { renv_project_lock(project = project) # indicate that we're now loading the project - renv_scope_options(renv.load.running = TRUE) - - # avoid suppressing the next auto snapshot - the$auto_snapshot_running <- TRUE - defer(the$auto_snapshot_running <- FALSE) + renv_scope_binding(the, "load_running", TRUE) # if load is being called via the autoloader, # then ensure RENV_PROJECT is unset @@ -15030,12 +15096,23 @@ renv_load_action <- function(project) { if (!interactive()) return("load") - # if this project doesn't yet contain an 'renv' folder, assume - # that it has not yet been initialized, and prompt the user + # 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") + # if we're running within RStudio at this point, and we're running + # within the auto-loader, we need to defer execution here so that + # the console is able to properly receive user input and update + # https://github.com/rstudio/renv/issues/1650 + autoloading <- getOption("renv.autoloader.running", default = FALSE) + if (autoloading && renv_rstudio_available()) { + setHook("rstudio.sessionInit", function() { + renv::load(project) + }) + } + # 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"))) @@ -16070,6 +16147,20 @@ renv_lockfile_diff_record <- function(before, after) { 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))) @@ -16576,22 +16667,23 @@ renv_lockfile_create <- function(project, packages = NULL, exclude = NULL, prompt = NULL, - force = 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), + 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) + 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) { +renv_lockfile_create_impl <- function(project, type, libpaths, packages, exclude, prompt, force, dev = FALSE) { lockfile <- renv_lockfile_init(project) @@ -16599,7 +16691,7 @@ renv_lockfile_create_impl <- function(project, type, libpaths, packages, exclude packages <- packages %||% renv_snapshot_dependencies( project = project, type = type, - dev = FALSE + dev = dev ) # expand the recursive dependencies of these packages @@ -16614,8 +16706,11 @@ renv_lockfile_create_impl <- function(project, type, libpaths, packages, exclude missing <- setdiff(packages, c(names(records), ignored)) # cancel automatic snapshots if we have missing packages - if (length(missing) && the$auto_snapshot_running) - invokeRestart("cancel") + 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 # @@ -17046,7 +17141,7 @@ renv_lockfile_from_manifest <- function(manifest, } # extract version - version <- numeric_version(manifest[["platform"]] %||% getRversion()) + version <- format(manifest[["platform"]] %||% getRversion()) # create R field for lockfile r <- list(Version = version, Repositories = repos) @@ -17235,11 +17330,6 @@ renv_methods_map <- function() { renv_file_broken = c( unix = "renv_file_broken_unix", win32 = "renv_file_broken_win32" - ), - - renv_paths_sandbox = c( - unix = "renv_paths_sandbox_unix", - win32 = "renv_paths_sandbox_win32" ) ) @@ -18812,6 +18902,12 @@ renv_pak_restore <- function(lockfile, { 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) @@ -18837,6 +18933,11 @@ renv_pak_restore <- function(lockfile, # not to install the package if a newer version was available. Hence, we need # to preserve the exact remote we wish to install here. + # return early if there are zero remotes to restore + if (length(remotes) == 0L) { + return(invisible(TRUE)) + } + # perform installation pak$pkg_install(remotes) } @@ -19392,49 +19493,21 @@ renv_paths_activate <- function(project = NULL) { } renv_paths_sandbox <- function(project = NULL) { - if (renv_platform_unix()) - renv_paths_sandbox_unix(project) - else - renv_paths_sandbox_win32(project) -} - -renv_paths_sandbox_unix <- function(project = NULL) { - - # construct a platform prefix - hash <- substring(renv_hash_text(R()), 1L, 8L) - prefix <- paste(renv_platform_prefix(), hash, sep = "/") - - # check for override - root <- Sys.getenv("RENV_PATHS_SANDBOX", unset = NA) - if (!is.na(root)) - return(paste(root, prefix, sep = "/")) - - # otherwise, build path in user data directory - userdir <- renv_bootstrap_user_dir() - paste(userdir, "sandbox", prefix, sep = "/") - -} - -renv_paths_sandbox_win32 <- function(project = NULL) { - - # NOTE: We previously used the R temporary directory here, but - # a number of users reported issues with the base R packages being - # deleted by over-aggressive temporary directory cleaners. - # - # https://github.com/rstudio/renv/issues/835 # construct a platform prefix - hash <- substring(renv_hash_text(R()), 1L, 8L) - prefix <- paste(renv_platform_prefix(), hash, sep = "/") + 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(root, prefix, sep = "/")) + return(paste(c(root, prefix), collapse = "/")) # otherwise, build path in user data directory userdir <- renv_bootstrap_user_dir() - paste(userdir, "sandbox", prefix, sep = "/") + paste(c(userdir, "sandbox", prefix), collapse = "/") } @@ -19496,20 +19569,17 @@ renv_paths_root <- function(...) { # nocov start renv_paths_root_default <- function() { - (the$root <- the$root %||% { + 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) - checking <- checking() - - # compute the root directory - if (checking) + if (checking()) renv_paths_root_default_tempdir() else renv_paths_root_default_impl() - }) + } } @@ -20017,10 +20087,15 @@ renv_ppm_platform_impl <- function(file = "/etc/os-release") { 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), - grepl("\\bsuse\\b", id) ~ renv_ppm_platform_suse(properties) + 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) ) } @@ -20052,11 +20127,33 @@ renv_ppm_platform_rhel <- function(properties) { id <- properties$VERSION_ID if (is.null(id)) return(NULL) + rhel_version <- ifelse(numeric_version(id) < "9", "centos", "rhel") - paste0("centos", substring(id, 1L, 1L)) + paste0(rhel_version, substring(id, 1L, 1L)) + +} + +renv_ppm_platform_rocky <- function(properties) { + + id <- properties$VERSION_ID + if (is.null(id)) + return(NULL) + rhel_version <- ifelse(numeric_version(id) < "9", "centos", "rhel") + + paste0(rhel_version, substring(id, 1L, 1L)) } +renv_ppm_platform_alma <- function(properties) { + + id <- properties$VERSION_ID + if (is.null(id)) + return(NULL) + rhel_version <- ifelse(numeric_version(id) < "9", "centos", "rhel") + + paste0(rhel_version, substring(id, 1L, 1L)) + +} renv_ppm_platform_suse <- function(properties) { @@ -20065,7 +20162,41 @@ renv_ppm_platform_suse <- function(properties) { return(NULL) parts <- strsplit(id, ".", fixed = TRUE)[[1L]] - paste0("opensuse", parts[[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) } @@ -22549,7 +22680,6 @@ renv_rehash_cache <- function(cache, prompt, action, label) { n <- length(targets) fmt <- "Successfully re-cached %s." writef(fmt, nplural("package", n)) - renv_cache_clean_empty() TRUE @@ -24317,6 +24447,11 @@ restore <- function(project = NULL, # 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")) @@ -25977,7 +26112,7 @@ renv_robocopy_move <- function(source, target) { #' in the lockfile will be, ensuring that (e.g.) CRAN packages are #' re-installed from the same CRAN mirror. #' -#' Use `repos = getOptions(repos)` to override with the repositories set +#' 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. #' @@ -26384,6 +26519,11 @@ renv_sandbox_init <- function() { 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 @@ -26542,9 +26682,13 @@ renv_sandbox_generate <- function(sandbox) { }) # 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 (lock) { dlog("sandbox", "locking sandbox") @@ -26581,18 +26725,24 @@ renv_sandbox_task <- function(...) { if (!renv_sandbox_activated()) return() + # allow opt-out if necessary enabled <- getOption("renv.sandbox.task", default = TRUE) if (!enabled) return() - # make sure the sandbox exists + # 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) { @@ -27117,6 +27267,7 @@ renv_scope_tempfile <- function(pattern = "renv-tempfile-", 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) @@ -28188,14 +28339,17 @@ the$auto_snapshot_hash <- TRUE #' * `"implict"`, (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. +#' * `"custom"` uses a custom filter. #' #' See **Snapshot type** 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. @@ -28218,6 +28372,8 @@ the$auto_snapshot_hash <- TRUE #' @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 @@ -28228,6 +28384,7 @@ 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, @@ -28276,7 +28433,8 @@ snapshot <- function(project = NULL, packages = packages, exclude = exclude, prompt = prompt, - force = force + force = force, + dev = dev ) if (is.null(lockfile)) @@ -29389,7 +29547,7 @@ the$status_running <- FALSE #' #' # Missing packages #' -#' `status()` first checks that all packages used by the project are installed. +#' `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 @@ -29457,6 +29615,8 @@ the$status_running <- FALSE #' 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: #' @@ -29472,7 +29632,8 @@ status <- function(project = NULL, library = NULL, lockfile = NULL, sources = TRUE, - cache = FALSE) + cache = FALSE, + dev = FALSE) { renv_scope_error_handler() renv_dots_check(...) @@ -29500,7 +29661,7 @@ status <- function(project = NULL, lockpath <- lockfile %||% renv_paths_lockfile(project = project) # get all dependencies, including transitive - dependencies <- renv_snapshot_dependencies(project, dev = FALSE) + 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)) @@ -31786,6 +31947,7 @@ proceed <- function(default = TRUE) { } menu <- function(choices, title, default = 1L) { + testing <- getOption("renv.menu.choice", integer()) if (length(testing)) { selected <- testing[[1]] @@ -31797,31 +31959,28 @@ menu <- function(choices, title, default = 1L) { } if (!is.null(selected)) { - writef(c( - title, - "", - paste0(seq_along(choices), ": ", choices), - "", - paste0("Selection: ", 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()) { - writef(c("Not interactive. Will:", choices[[default]])) - return(default) + 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) { - "cancel" - } else { - names(choices)[idx] - } + + if (idx == 0L) + return("cancel") + + names(choices)[idx] + } # nocov end @@ -31864,7 +32023,9 @@ read <- function(file) { } plural <- function(word, n) { - if (n == 1) word else paste(word, "s", sep = "") + suffixes <- c("", "s") + indices <- as.integer(n != 1L) + 1L + paste0(word, suffixes[indices]) } nplural <- function(word, n) { @@ -32835,17 +32996,20 @@ renv_watchdog_start_impl <- function() { stdout <- stderr <- if (truthy(debugging)) "" else FALSE # launch the watchdog - system2( - command = R(), - args = c("--vanilla", "-s", "-f", renv_shell_path(script)), - stdout = stdout, - stderr = stderr, - wait = FALSE - ) + 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 = 10)) + 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) @@ -33076,6 +33240,10 @@ renv_yaml_load <- function(text) { 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 <- system.file(package = "renv", "help/renv.rdb") @@ -33104,8 +33272,21 @@ renv_zzz_load <- function() { # make sure renv (and packages using renv!!!) use tempdir for storage # when running tests, or R CMD check if (checking() || testing()) { - Sys.setenv(RENV_PATHS_ROOT = tempfile("renv-root-")) + + # set root directory + root <- Sys.getenv("RENV_PATHS_ROOT", unset = tempfile("renv-root-")) + Sys.setenv(RENV_PATHS_ROOT = root) + + # 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 options(renv.sandbox.locking_enabled = FALSE) + } renv_metadata_init() diff --git a/man/figures/lifecycle-archived.svg b/man/figures/lifecycle-archived.svg index 48f72a6f..745ab0c7 100644 --- a/man/figures/lifecycle-archived.svg +++ b/man/figures/lifecycle-archived.svg @@ -1 +1,21 @@ - lifecyclelifecyclearchivedarchived \ No newline at end of file + + lifecycle: archived + + + + + + + + + + + + + + + lifecycle + + archived + + diff --git a/man/figures/lifecycle-defunct.svg b/man/figures/lifecycle-defunct.svg index 01452e5f..d5c9559e 100644 --- a/man/figures/lifecycle-defunct.svg +++ b/man/figures/lifecycle-defunct.svg @@ -1 +1,21 @@ -lifecyclelifecycledefunctdefunct \ No newline at end of file + + lifecycle: defunct + + + + + + + + + + + + + + + lifecycle + + defunct + + diff --git a/man/figures/lifecycle-deprecated.svg b/man/figures/lifecycle-deprecated.svg index 4baaee01..b61c57c3 100644 --- a/man/figures/lifecycle-deprecated.svg +++ b/man/figures/lifecycle-deprecated.svg @@ -1 +1,21 @@ -lifecyclelifecycledeprecateddeprecated \ No newline at end of file + + lifecycle: deprecated + + + + + + + + + + + + + + + lifecycle + + deprecated + + diff --git a/man/figures/lifecycle-experimental.svg b/man/figures/lifecycle-experimental.svg index d1d060e9..5d88fc2c 100644 --- a/man/figures/lifecycle-experimental.svg +++ b/man/figures/lifecycle-experimental.svg @@ -1 +1,21 @@ -lifecyclelifecycleexperimentalexperimental \ No newline at end of file + + lifecycle: experimental + + + + + + + + + + + + + + + lifecycle + + experimental + + diff --git a/man/figures/lifecycle-maturing.svg b/man/figures/lifecycle-maturing.svg index df713101..897370ec 100644 --- a/man/figures/lifecycle-maturing.svg +++ b/man/figures/lifecycle-maturing.svg @@ -1 +1,21 @@ -lifecyclelifecyclematuringmaturing \ No newline at end of file + + lifecycle: maturing + + + + + + + + + + + + + + + lifecycle + + maturing + + diff --git a/man/figures/lifecycle-questioning.svg b/man/figures/lifecycle-questioning.svg index 08ee0c90..7c1721d0 100644 --- a/man/figures/lifecycle-questioning.svg +++ b/man/figures/lifecycle-questioning.svg @@ -1 +1,21 @@ -lifecyclelifecyclequestioningquestioning \ No newline at end of file + + lifecycle: questioning + + + + + + + + + + + + + + + lifecycle + + questioning + + diff --git a/man/figures/lifecycle-soft-deprecated.svg b/man/figures/lifecycle-soft-deprecated.svg new file mode 100644 index 00000000..9c166ff3 --- /dev/null +++ b/man/figures/lifecycle-soft-deprecated.svg @@ -0,0 +1,21 @@ + + lifecycle: soft-deprecated + + + + + + + + + + + + + + + lifecycle + + soft-deprecated + + diff --git a/man/figures/lifecycle-stable.svg b/man/figures/lifecycle-stable.svg index e015dc81..9bf21e76 100644 --- a/man/figures/lifecycle-stable.svg +++ b/man/figures/lifecycle-stable.svg @@ -1 +1,29 @@ -lifecyclelifecyclestablestable \ No newline at end of file + + lifecycle: stable + + + + + + + + + + + + + + + + lifecycle + + + + stable + + + diff --git a/man/figures/lifecycle-superseded.svg b/man/figures/lifecycle-superseded.svg index 75f24f55..db8d757f 100644 --- a/man/figures/lifecycle-superseded.svg +++ b/man/figures/lifecycle-superseded.svg @@ -1 +1,21 @@ - lifecyclelifecyclesupersededsuperseded \ No newline at end of file + + lifecycle: superseded + + + + + + + + + + + + + + + lifecycle + + superseded + + diff --git a/man/figures/logo.png b/man/figures/logo.png index 26c14e21..60f6b393 100644 Binary files a/man/figures/logo.png and b/man/figures/logo.png differ