├── DESCRIPTION ├── LICENSE ├── LICENSE.md ├── NAMESPACE ├── NEWS.md ├── R ├── alias.R ├── capshot.R ├── compare.R ├── create.R ├── delete.R ├── detect_dependencies.R ├── locate_capsule.R ├── mirror.R ├── recreate.R ├── repl.R ├── reproduce.R ├── run.R └── utils.R ├── README.md ├── man ├── any_local_behind_lockfile.Rd ├── capshot.Rd ├── compare_lockfile.Rd ├── create.Rd ├── delete.Rd ├── delete_local_lib.Rd ├── delete_lockfile.Rd ├── detect_dependencies.Rd ├── dev_mirror_lockfile.Rd ├── get_behind.Rd ├── recreate.Rd ├── repl.Rd ├── reproduce_lib.Rd ├── run.Rd ├── run_rscript.Rd ├── run_session.Rd └── whinge.Rd └── tests ├── testthat.R └── testthat ├── __renv.R ├── __testrpkg.R ├── _snaps └── compare.md ├── renv_cran.lock ├── renv_github.lock ├── renv_github_cran_ver.lock ├── renv_pak_rspm.lock ├── renv_rspm.lock ├── setup.R ├── test-capshot.R ├── test-compare.R ├── test-create-run.R ├── test-sha-is-version.R └── testrpkg.lock /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: capsule 2 | Title: A streamlined inversion of `renv` 3 | Version: 0.4.3 4 | Authors@R: 5 | c(person(given = "Miles", 6 | family = "McBain", 7 | role = c("aut", "cre"), 8 | email = "miles.mcbain@gmail.com", 9 | comment = c(ORCID = "0000-0003-2865-2548")), 10 | person(given = "Garrick", 11 | family = "Aden-Buie", 12 | role = c("aut"), 13 | email = "garrick@adenbuie.com")) 14 | Description: A `capsule` is a stable local package library that you consciously choose to execute code within. Think of it as representing 'production', while your normal interactive R session represents 'development'. 15 | License: MIT + file LICNSE 16 | Encoding: UTF-8 17 | LazyData: true 18 | Imports: 19 | callr, 20 | renv (>= 0.7.0.125), 21 | withr, 22 | using (>= 0.4.0), 23 | fs, 24 | jsonlite, 25 | stats, 26 | utils 27 | Remotes: 28 | anthonynorth/using 29 | RoxygenNote: 7.3.1 30 | Roxygen: list(markdown = TRUE) 31 | Suggests: 32 | testthat (>= 3.0.0) 33 | Config/testthat/edition: 3 34 | Copyright: 2022 Miles McBain, unless noted. Some MIT licensed code taken from RStudio bears Copyright in comments. 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | YEAR: 2019 2 | COPYRIGHT HOLDER: Miles McBain 3 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Copyright (c) 2019 Miles McBain 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | export(any_local_behind_lockfile) 4 | export(capshot) 5 | export(capshot_str) 6 | export(compare_capsule_to_lockfile) 7 | export(compare_local_to_lockfile) 8 | export(create) 9 | export(delete) 10 | export(delete_local_lib) 11 | export(delete_lockfile) 12 | export(detect_dependencies) 13 | export(dev_mirror_lockfile) 14 | export(get_capsule_behind_lockfile) 15 | export(get_local_behind_lockfile) 16 | export(recreate) 17 | export(repl) 18 | export(reproduce_lib) 19 | export(run) 20 | export(run_callr) 21 | export(run_rscript) 22 | export(run_session) 23 | export(whinge) 24 | -------------------------------------------------------------------------------- /NEWS.md: -------------------------------------------------------------------------------- 1 | # Version 0.4.3 2 | 3 | * Scraped off a lot of barnacles that had accumulated over recent years. 4 | * `capshot()` now fires off warnings when encountering packages with an unknown source. 5 | * Better support for multi-lockfile projects (e.g. targets multi-plans) with lockfile_path arg available on nearly all user facing functions now. 6 | 7 | 8 | # Version 0.4.2 9 | 10 | * New `run_rscript()` can run R scripts in the capsule via `callr::rscript()` (@gadenbuie #27) 11 | * `run_callr()` exposes the `show` option with default to `TRUE`. 12 | * `detect_depencies()` is now exported 13 | 14 | # Version 0.4.1 15 | 16 | * remove `capshot` dependency on `renv:::renv_alias`. 17 | * When looking for the sitaution where packages with the same version have different SHAs, only real shas are considered. Not the fake version SHAs inserted by `{pak}` et. al. 18 | -------------------------------------------------------------------------------- /R/alias.R: -------------------------------------------------------------------------------- 1 | # Code in this file taken from {renv} with MIT-style license: 2 | 3 | # Copyright 2021 RStudio, PBC 4 | 5 | # 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: 6 | 7 | # aliases used primarily for nicer / normalized text output 8 | `_renv_aliases` <- list( 9 | bioconductor = "Bioconductor", 10 | bitbucket = "Bitbucket", 11 | cellar = "Cellar", 12 | cran = "CRAN", 13 | git2r = "Git", 14 | github = "GitHub", 15 | gitlab = "GitLab", 16 | local = "Local", 17 | repository = "Repository", 18 | standard = "Repository", 19 | url = "URL", 20 | xgit = "Git" 21 | ) 22 | 23 | renv_alias <- function(text) { 24 | `_renv_aliases`[[text]] %||% text 25 | } 26 | -------------------------------------------------------------------------------- /R/capshot.R: -------------------------------------------------------------------------------- 1 | globalVariables("BASE_PACKAGES") 2 | 3 | BASE_PACKAGES <- 4 | c( 5 | "base", 6 | "compiler", 7 | "datasets", 8 | "grDevices", 9 | "graphics", 10 | "grid", 11 | "methods", 12 | "parallel", 13 | "splines", 14 | "stats", 15 | "stats4", 16 | "tcltk", 17 | "tools", 18 | "translations", 19 | "utils" 20 | ) 21 | #' Quickly generate an renv compliant lock file 22 | #' 23 | #' These functions generate json lockfiles that can be restored from using 24 | #' `capsule` or `renv`. 25 | #' 26 | #' Unlike [capsule::create()] this function does not populate a local library. 27 | #' It writes a lock file using dependencies found in files in `dep_source_paths`. 28 | #' Package dependency information is mined from DESCRIPTION files using the 29 | #' current [.libPaths()]. 30 | #' 31 | #' These functions do not use `{renv}` machinery and so may produce 32 | #' different results. They have been re-implmented for speed, so that they can 33 | #' be integrated into automated pipelines that build projects or documents. 34 | #' 35 | #' @param dep_source_paths files to scan for project dependencies to write to the lock file. 36 | #' @param lockfile_path output path for the lock file. 37 | #' @param minify a boolean value indicicating if lockfile JSON should have whitespace removed to shrink footprint. 38 | #' 39 | #' @return `lockfile_path`. Writes lockfile as a side effect. 40 | #' @export 41 | capshot <- function( 42 | dep_source_paths = "./packages.R", 43 | lockfile_path = "./renv.lock", 44 | minify = FALSE 45 | ) { 46 | 47 | lockfile_json <- capshot_str(dep_source_paths, minify) 48 | writeLines(lockfile_json, lockfile_path) 49 | invisible(lockfile_path) 50 | } 51 | 52 | 53 | #' @describeIn capshot a variation that returns lockfile json as a character vector for further use. 54 | #' @export 55 | capshot_str <- function(dep_source_paths = "./packages.R", minify = FALSE) { 56 | generate_lockfile_json( 57 | get_project_deps(detect_dependencies(dep_source_paths)), 58 | minify = minify 59 | ) 60 | } 61 | 62 | get_project_deps <- function(declared_deps) { 63 | pkg_dcfs <- 64 | lapply( 65 | Sys.glob(file.path(.libPaths(), "*", "DESCRIPTION")), 66 | read.dcf 67 | ) 68 | 69 | pkg_names <- unlist(lapply(pkg_dcfs, function(x) x[, "Package"])) 70 | pkg_dcfs <- pkg_dcfs[!duplicated(pkg_names)] 71 | pkg_names <- pkg_names[!duplicated(pkg_names)] 72 | project_dep_dcfs <- get_project_dcfs(declared_deps, pkg_names, pkg_dcfs) 73 | project_dep_dcfs 74 | } 75 | 76 | get_project_dcfs <- function(declared_deps, pkg_names, pkg_dcfs) { 77 | pkg_deps <- stats::setNames(lapply(pkg_dcfs, get_deps), pkg_names) 78 | project_deps <- stats::setNames(as.list(logical(length(pkg_names))), pkg_names) 79 | current_deps <- declared_deps 80 | 81 | while (length(current_deps) > 0) { 82 | project_deps[current_deps] <- TRUE 83 | new_current_deps <- setdiff( 84 | unlist(pkg_deps[current_deps], use.names = FALSE), 85 | names(Filter(isTRUE, project_deps)) 86 | ) 87 | current_deps <- new_current_deps 88 | } 89 | missing <- setdiff(names(project_deps), unlist(pkg_names)) 90 | if (length(missing)) stop("Cannot complete capshot(). Project dependencies not installed locally: ", 91 | paste(missing, collapse = ", "), "\n", 92 | ) 93 | stats::setNames(pkg_dcfs, pkg_names)[unlist(project_deps)] 94 | } 95 | 96 | parse_pkg_deps <- function(dep_string) { 97 | deps <- regmatches( 98 | dep_string, 99 | gregexpr(pattern = "[A-Za-z][A-Za-z0-9.]+", dep_string) 100 | )[[1]] 101 | 102 | setdiff(deps, "R") 103 | } 104 | 105 | get_deps <- function( 106 | dcf_data, 107 | fields = c("Imports", "Depends", "LinkingTo") 108 | ) { 109 | 110 | present_fields <- intersect(fields, colnames(dcf_data)) 111 | parse_pkg_deps( 112 | paste0(dcf_data[, present_fields], collapse = " ") 113 | ) 114 | } 115 | 116 | 117 | 118 | generate_lockfile_json <- function(project_dep_dcfs, minify = FALSE) { 119 | is_base <- names(project_dep_dcfs) %in% BASE_PACKAGES 120 | project_dep_dcfs <- project_dep_dcfs[!is_base] 121 | pkg_renv_entries <- lapply(project_dep_dcfs, get_renv_fields) 122 | r_renv_entry <- get_renv_r_entry() 123 | jsonlite::toJSON( 124 | list( 125 | R = r_renv_entry, 126 | Packages = pkg_renv_entries 127 | ), 128 | pretty = !minify, 129 | auto_unbox = TRUE 130 | ) 131 | } 132 | 133 | # This implementation tries to follow logic in 134 | # renv:::renv_snapshot_description_source 135 | # https://github.com/rstudio/renv/blob/b314054506b37924cde6fbcd46078f8e57d94dd4/R/snapshot.R 136 | # renv:::renv_snapshot_description_source is distributed with an MIT style license: 137 | 138 | # Copyright 2021 RStudio, PBC 139 | 140 | # 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: 141 | 142 | # 143 | get_renv_fields <- function(dcf_record) { 144 | dcf_record_df <- as.data.frame(dcf_record) 145 | if (!is.null(dcf_record_df$RemoteType)) { 146 | dcf_record_df$Source <- renv_alias(dcf_record_df$RemoteType) 147 | } else if (!is.null(dcf_record_df$Repository)) { 148 | if (identical(dcf_record_df$Repository, "Local")) { 149 | dcf_record_df$Source <- "Local" 150 | } 151 | else { 152 | dcf_record_df$Source <- "Repository" 153 | } 154 | } else if (!is.null(dcf_record_df$biocViews)) { 155 | dcf_record_df$Source <- "BioConductor" 156 | } else { 157 | dcf_record_df$Source <- "unknown" 158 | warning( 159 | paste0("{", dcf_record_df$Package,"}", " has unknown source. Installed via {devtools} locally?. Install from remote to document source in lockfile."), 160 | call. = FALSE 161 | ) 162 | } 163 | 164 | renv_fields <- c( 165 | "Source", 166 | "Package", 167 | "Version", 168 | "Repository", 169 | "OS_type", 170 | colnames(dcf_record_df)[grepl("^Remote(?!s$)", colnames(dcf_record_df), perl = TRUE)] 171 | ) 172 | as.list(dcf_record_df[, intersect(renv_fields, colnames(dcf_record_df))]) 173 | 174 | } 175 | 176 | get_renv_r_entry <- function() { 177 | list( 178 | Version = paste0(R.Version()[c("major", "minor")], collapse = "."), 179 | Repositories = get_repos() 180 | ) 181 | } 182 | 183 | get_repos <- function() { 184 | repos <- getOption("repos") 185 | # if you have none, default one 186 | repos[repos == "@CRAN@"] <- "https://cloud.r-project.org" 187 | mapply( 188 | function(repo, reponame) list(Name = reponame, URL = repo), 189 | repos, 190 | names(repos), 191 | USE.NAMES = FALSE, 192 | SIMPLIFY = FALSE 193 | ) 194 | } 195 | 196 | 197 | # Test code 198 | function() { 199 | lockfile <- 200 | capshot("../coolburn_dashboard/packages.R", lockfile_path = "renv.lock", minify = TRUE) 201 | lockfile 202 | 203 | dummy <- c("Remotes", "RemoteSha", "RemoteUsername", "RemotePkgRef") 204 | grepl("^Remote(?!s$)", dummy, perl = TRUE) 205 | } 206 | 207 | -------------------------------------------------------------------------------- /R/compare.R: -------------------------------------------------------------------------------- 1 | #' get packckes behind lockfile 2 | #' 3 | #' return information on packages in your main R library (`.libPaths()`) or capsule library (`./renv`) that are behind the 4 | #' lockfile versions (at `lockfile_path`). 5 | #' 6 | #' if `dep_source_paths` is supplied only dependencies declared in these files are returned. 7 | #' 8 | #' Information is returned about packages that are behind in your development 9 | #' environment, so you can update them to the capsule versions if you wish. 10 | #' 11 | #' A warning is thrown in the case that pacakges have the same version but 12 | #' different remote SHA. E.g. A package in one library is from GitHub and in 13 | #' the other library is from CRAN. Or Both packages are from GitHub, have the 14 | #' same version but different SHAs. 15 | #' 16 | #' @param dep_source_paths a character vector of file paths to extract 17 | #' package dependencies from. If NULL (default) the whole local library is compared. 18 | #' @param lockfile_path a length one character vector path of the lockfile for 19 | # the capsule. 20 | #' @param library_path the source libary to compare versions from. 21 | #' 22 | #' @return a summary dataframe of package version differences. 23 | #' @examples 24 | #' \dontrun{ 25 | #' get_local_behind_capsule( 26 | #' dep_source_paths = "./packages.R", 27 | #' lockfile_path = "./renv.lock" 28 | #' ) 29 | #' } 30 | #' @export 31 | #' @family comparisons 32 | #' @rdname get_behind 33 | get_local_behind_lockfile <- function( 34 | lockfile_path = "./renv.lock", 35 | dep_source_paths = NULL, 36 | library_path = .libPaths() 37 | ) { 38 | get_pkg_behind_lockfile(lockfile_path, dep_source_paths, library_path) 39 | } 40 | 41 | #' @family comparisons 42 | #' @describeIn get_behind get packages in the renv library that are behind the lockfile 43 | #' @export 44 | get_capsule_behind_lockfile <- function( 45 | lockfile_path = "./renv.lock", 46 | dep_source_paths = NULL 47 | ) { 48 | get_pkg_behind_lockfile( 49 | lockfile_path, 50 | dep_source_paths, 51 | library_path = locate_capsule()) 52 | } 53 | 54 | get_pkg_behind_lockfile <- function( 55 | lockfile_path = "./renv.lock", 56 | dep_source_paths = NULL, 57 | library_path = .libPaths() 58 | ) { 59 | assert_files_exist(lockfile_path) 60 | package_data <- compare_lib_lockfile(lockfile_path, library_path) 61 | 62 | if (!is.null(dep_source_paths)) { 63 | assert_files_exist(dep_source_paths) 64 | declared_dependencies <- 65 | detect_dependencies(dep_source_paths) 66 | package_data <- 67 | package_data[package_data$name %in% declared_dependencies, ] 68 | } 69 | 70 | behind <- 71 | .mapply( 72 | dots = package_data, 73 | MoreArgs = NULL, 74 | FUN = function(...) { 75 | data_row <- list(...) 76 | version_comp <- utils::compareVersion( 77 | data_row$version_lock, 78 | data_row$version_lib 79 | ) 80 | 81 | # catch edge case with git remotes not updating version and 82 | # warn if the versions are equal but: 83 | # * one has a remote sha and one does not - 84 | # i.e. one is from CRAN and one is from GitHub 85 | # * both have remote shas and they are not equal 86 | if (version_comp == 0 && 87 | ( 88 | (!is.na(data_row$remote_sha_lock)) || 89 | (!is.na(data_row$remote_sha_lib)) 90 | ) && 91 | !isTRUE(data_row$remote_sha_lock == data_row$remote_sha_lib)) { 92 | warning( 93 | "Packages have equal versions but different", 94 | " remote SHAs: ", 95 | data_row$name, 96 | call. = FALSE 97 | ) 98 | } 99 | 100 | if (version_comp == 1) TRUE else FALSE 101 | } 102 | ) 103 | 104 | package_data$behind <- unlist(behind) 105 | 106 | package_data[package_data$behind, ] 107 | } 108 | 109 | #' check if any local packages are behind lockfile 110 | #' 111 | #' A wrapper for [get_local_behind_lockfile] that returns TRUE if any 112 | #' dependencies found in `dep_source_paths` are behind the lockfile version in 113 | #' `lockfile_path` 114 | #' 115 | #' @inheritParams get_local_behind_lockfile 116 | #' 117 | #' @return TRUE if dev packages are behind lockfile, FALSE otherwise. 118 | #' @family comparisons 119 | #' @export 120 | any_local_behind_lockfile <- function( 121 | lockfile_path = "./renv.lock", 122 | dep_source_paths = NULL 123 | ) { 124 | nrow(get_local_behind_lockfile(lockfile_path, dep_source_paths)) > 0 125 | } 126 | 127 | assert_not_behind_lockfile <- function( 128 | lockfile_path = "./renv.lock", 129 | dep_source_paths = NULL, 130 | stop_on_behind = FALSE 131 | ) { 132 | if (!file.exists(lockfile_path)) { 133 | warning("No renv.lock found in this project.") 134 | return(FALSE) 135 | } 136 | packages_behind_lockfile <- get_pkg_behind_lockfile(lockfile_path, dep_source_paths) 137 | if (nrow(packages_behind_lockfile) == 0) { 138 | message("No installed packages are behind renv.lock") 139 | } else { 140 | action_fun <- if (stop_on_behind) stop else warning 141 | action_fun( 142 | "Found packages with versions behind renv.lock: ", 143 | paste0(packages_behind_lockfile$name, collapse = ", ") 144 | ) 145 | } 146 | 147 | } 148 | 149 | 150 | #' compare the local R library with the lockfile 151 | #' 152 | #' Get a summary dataframe comparing package versions in the lockfile with 153 | #' versions in the local R library (.libPaths()) or capsule library (./renv). 154 | #' 155 | #' @inheritParams get_local_behind_lockfile 156 | #' @return a summary dataframe of version differences 157 | #' 158 | #' @export 159 | #' @family comparisons 160 | #' @rdname compare_lockfile 161 | compare_local_to_lockfile <- function(lockfile_path = "./renv.lock") { 162 | compare_lib_lockfile( 163 | lockfile_path, 164 | library_path = .libPaths()) 165 | } 166 | 167 | #' @export 168 | #' @family comparisons 169 | #' @describeIn compare_lockfile compares the renv libray to the lockfile 170 | compare_capsule_to_lockfile <- function(lockfile_path = "./renv.lock") { 171 | compare_lib_lockfile( 172 | lockfile_path, 173 | library_path = locate_capsule() 174 | ) 175 | 176 | } 177 | 178 | #' @param library_path a character vector of library paths to search 179 | compare_lib_lockfile <- function( 180 | lockfile_path = "./renv.lock", 181 | library_path = .libPaths() 182 | ) { 183 | lockfile_deps <- get_lockfile_deps(lockfile_path) 184 | 185 | local_deps <- get_library_deps(lockfile_deps$name, library_path) 186 | 187 | merge(lockfile_deps, local_deps, by = "name", suffixes = c("_lock", "_lib")) 188 | } 189 | 190 | 191 | get_lockfile_deps <- function(lockfile_path) { 192 | lockfile <- jsonlite::read_json(lockfile_path) 193 | 194 | lockfile_deps <- lapply_df( 195 | lockfile$Packages[], 196 | dependency_data_frame 197 | ) 198 | 199 | lockfile_deps 200 | } 201 | 202 | #' @param library_path a character vector of library paths to search 203 | get_library_deps <- function(dep_list, library_path = NULL) { 204 | local_deps <- 205 | lapply_df( 206 | dep_list, 207 | function(dep_name) { 208 | tryCatch( 209 | { 210 | lib_data <- as.data.frame(read.dcf(file.path( 211 | find.package(dep_name, lib.loc = library_path), 212 | "DESCRIPTION" 213 | ))) 214 | dependency_data_frame( 215 | lib_data 216 | ) 217 | }, 218 | error = function(e) { 219 | dependency_data_frame( 220 | list( 221 | Package = dep_name 222 | ) 223 | ) 224 | } 225 | ) 226 | } 227 | ) 228 | } 229 | 230 | dependency_data_frame <- function(dep_data) { 231 | 232 | data.frame( 233 | name = dep_data$Package %||% NA, 234 | version = dep_data$Version %||% NA, 235 | repository = dep_data$Repository %||% 236 | dep_data$RemoteHost %||% NA, 237 | remote_sha = ifelse(is_real_sha(dep_data$RemoteSha), dep_data$RemoteSha, NA), 238 | remote_repo = dep_data$RemoteRepo %||% NA, 239 | remote_username = dep_data$RemoteUsername %||% NA 240 | ) 241 | 242 | } 243 | -------------------------------------------------------------------------------- /R/create.R: -------------------------------------------------------------------------------- 1 | ##' Create a capsule library context to run code in 2 | ##' 3 | ##' Dependencies to be encapsulated are detected from files you nominate in 4 | ##' `dep_source_paths`. Good practice would be to have a single dependencies R 5 | ##' file that contains all library() calls - hence this makes an explicit 6 | ##' assertion of your dependencies. This way spurious usages of pkg:: for 7 | ##' packages not stated as dependencies will cause errors that can be caught. 8 | ##' 9 | ##' @title create 10 | ##' @param dep_source_paths files to find package dependencies in. 11 | ##' @param lockfile_path The path to the lockfile to produce capsule library for. Default is `./renv.lock`. 12 | ##' @return nothing. Creates a capsule as a side effect. 13 | ##' @author Miles McBain 14 | ##' @export 15 | create <- function( 16 | dep_source_paths = "./packages.R", 17 | lockfile_path = "./renv.lock" 18 | ) { 19 | 20 | if (file.exists(lockfile_path)) { 21 | warning("Found an existing lockfile, ", 22 | lockfile_path, 23 | ", that will be ovewritten.") 24 | } 25 | capshot( 26 | dep_source_paths = dep_source_paths, 27 | lockfile_path = lockfile_path 28 | ) 29 | reproduce_lib() 30 | } 31 | -------------------------------------------------------------------------------- /R/delete.R: -------------------------------------------------------------------------------- 1 | ##' Delete the capsule's local library 2 | ##' 3 | ##' This helper is provided to help you recover from mistakes or test building 4 | ##' the library from a lockfile you have generated. 5 | ##' 6 | ##' @title delete_local_lib 7 | ##' @return nothing 8 | ##' @seealso [delete()] 9 | ##' @author Miles McBain 10 | ##' @export 11 | delete_local_lib <- function() { 12 | 13 | unlink("./renv", 14 | recursive = TRUE) 15 | 16 | } 17 | ##' Delete the capule's lockfile 18 | ##' 19 | ##' This helper is provided to help you recover from mistakes or test extracting 20 | ##' dependencies. 21 | ##' 22 | ##' @title delete_lockfile 23 | ##' @return nothing 24 | ##' @export 25 | ##' @author Miles McBain 26 | delete_lockfile <- function() { 27 | 28 | unlink("./renv.lock") 29 | 30 | } 31 | ##' Delete the capsule 32 | ##' 33 | ##' Removes the lockfile and library, in the case that you made a mistake or no 34 | ##' longer want to use capsule. 35 | ##' 36 | ##' @title delete 37 | ##' @return nothing 38 | ##' @author Miles McBain 39 | ##' @export 40 | delete <- function() { 41 | 42 | delete_local_lib() 43 | delete_lockfile() 44 | 45 | } 46 | -------------------------------------------------------------------------------- /R/detect_dependencies.R: -------------------------------------------------------------------------------- 1 | #' Detect dependencies in nominated R or Rmd files. 2 | #' 3 | #' Get the names of R packages referred to in `file_path`. `file_path` can be a 4 | #' vector of paths if you need, although I advise keeping dependency calls in a 5 | #' single file for R projects. 6 | #' 7 | #' This is a thin wrapper around `[renv::dependencies()]` that includes support 8 | #' for the `[using::pkg()]` style specification via `[using::detect_dependencies()]` 9 | #' @param file_path the file(s) to detect dependencies in. 10 | #' @return a character vector of package names 11 | #' @export 12 | detect_dependencies <- function(file_path) { 13 | 14 | renv_deps <- 15 | renv::dependencies(file_path, progress = FALSE) 16 | 17 | using_deps <- 18 | lapply_df(file_path, using::detect_dependencies) 19 | 20 | unique(c(renv_deps$Package, 21 | using_deps$package)) 22 | 23 | } 24 | -------------------------------------------------------------------------------- /R/locate_capsule.R: -------------------------------------------------------------------------------- 1 | locate_capsule <- function() { 2 | 3 | renv_libraries <- 4 | list.files(pattern = "^.renv", recursive = TRUE, include.dirs = TRUE, all.files = TRUE) 5 | 6 | if (length(renv_libraries) == 0) { 7 | stop("could not find a capsule local library (looking for .renv inside)") 8 | 9 | } 10 | 11 | if (length(renv_libraries) > 1) { 12 | stop( 13 | "found multiple renv local libraries ", 14 | paste0(renv_libaries, collapse = ", "), 15 | "comparing", 16 | renv_libraries[[1]] 17 | ) 18 | } 19 | 20 | path_parts <- fs::path_split(renv_libraries[[1]])[[1]] 21 | fs::path_join(head(path_parts, -1)) # remove .renv 22 | } 23 | 24 | capsule_exists <- function() { 25 | library_locations <- 26 | list.files(pattern = "^.renv", recursive = TRUE, include.dirs = TRUE, all.files = TRUE) 27 | 28 | length(library_locations) > 0 29 | } 30 | -------------------------------------------------------------------------------- /R/mirror.R: -------------------------------------------------------------------------------- 1 | #' Mirror lockfile in local library 2 | #' 3 | #' Install packages contained in the lockfile that are either missing from the local library or at a lower version number. 4 | #' 5 | #' Packages are installed at the lockfile version. Packages in the local library that are ahead of the the local library are not touched. 6 | #' 7 | #' So this function ensures that the local development environment is **at least** at the lockfile version of all packages, not **equal to**. 8 | #' 9 | #' To find differences between the local library and the lockfile versions use [compare_local_to_lockfile()]. 10 | #' 11 | #' @param lockfile_path lockfile to be compared to local environment. 12 | #' @param dep_source_paths R files to search for dependencies, the set of packages to be updated is limited to these dependencies (and their dependencies). 13 | #' @param library_path the source libary to compare versions from, and install into. 14 | #' @param prompt ask for confirmation after displaying list to be installed and before isntalling? 15 | #' 16 | #' @return names of the packages updated or to be updated (if install did not proceed) invisibly. 17 | #' @export 18 | dev_mirror_lockfile <- function( 19 | lockfile_path = "./renv.lock", 20 | dep_source_paths = NULL, 21 | library_path = .libPaths(), 22 | prompt = interactive() 23 | ) { 24 | lockfile_deps <- get_pkg_behind_lockfile(lockfile_path, dep_source_paths) 25 | 26 | packages_to_update <- lockfile_deps$name 27 | if (length(packages_to_update) == 0) { 28 | cat("No packages to update.\n") 29 | return(invisible(character(0))) 30 | } 31 | 32 | cat( 33 | length(packages_to_update), 34 | "packages to be updated to lockfile versions:\n" 35 | ) 36 | cat( 37 | paste( 38 | format(lockfile_deps$name), 39 | format(lockfile_deps$version_lib), 40 | " -> ", 41 | format(lockfile_deps$version_lock) 42 | ), 43 | sep = "\n" 44 | ) 45 | proceed <- utils::menu(choices = c("Yes", "No"), title = "Proceed with installation?") 46 | 47 | if (proceed == 2) return(invisible(packages_to_update)) 48 | renv::restore( 49 | packages = packages_to_update, 50 | prompt = FALSE, 51 | library = library_path, 52 | lockfile = lockfile_path 53 | ) 54 | invisible(packages_to_update) 55 | } 56 | 57 | #' Complain if the local R library has packages that are behind the lockfile versions 58 | #' 59 | #' Useful for keeping teams loosely in sync on package versions. A warning can 60 | #' be tolerated until updating at a convenient time. For example if 61 | #' placed in the packages.R file of a `{tflow}` project. 62 | #' 63 | #' The message is hardcoded, but the whinge_fun that takes the message is customisable. 64 | #' 65 | #' @param whinge_fun the function to use to have a whinge about packages, e.g. message, warning, stop, etc. 66 | #' @param lockfile_path the path to the project lockfile 67 | #' @param dep_source_paths to search for dependencies to comparage versions for. E.g. "packages.R" 68 | #' If NULL the whole local library is compared 69 | #' @return output of whinge_fun, most likely nothing. 70 | #' @export 71 | whinge <- function(whinge_fun = warning, lockfile_path = "./renv.lock", dep_source_paths = NULL) { 72 | if (any_local_behind_lockfile(lockfile_path, dep_source_paths)) { 73 | whinge_fun("[{capsule} whinge] Your R library packages are behind the lockfile.", 74 | " Use capsule::dev_mirror_lockfile to upgrade.") 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /R/recreate.R: -------------------------------------------------------------------------------- 1 | ##' Recreate a capsule with new dependencies 2 | ##' 3 | ##' After some development work has been completed, Use this function to update the 4 | ##' capsule environment to match the dependency versions in your development 5 | ##' environment. 6 | ##' 7 | ##' Similarly to `create()`, you are expected to supply a vector of files in 8 | ##' your project to extract dependencies from. Things work best when this is a 9 | ##' single file containing only dependency related code. 10 | ##' 11 | ##' @title recreate 12 | ##' @param dep_source_paths a character vector of project source files to 13 | ##' extract dependencies from. 14 | ##' @return nothing. The capsule is regenerated as a side effect. 15 | ##' @author Miles McBain 16 | ##' @seealso [create()] 17 | ##' @export 18 | recreate <- function(dep_source_paths = "./packages.R") { 19 | 20 | delete() 21 | create(dep_source_paths = dep_source_paths) 22 | 23 | } 24 | -------------------------------------------------------------------------------- /R/repl.R: -------------------------------------------------------------------------------- 1 | ##' Open a REPL within the capsule 2 | ##' 3 | ##' Uses an experimental feature from `callr` to attach a new process repl to 4 | ##' your current interactive session. That REPL evaluates code within the 5 | ##' context of your capsule. 6 | ##' 7 | ##' To exit the process send the use the interrupt signal in the REPL e.g. 8 | ##' Control-C, or, ess-interrupt, or the 'stop' button in rstudio. 9 | ##' 10 | ##' Depending on your R editor, overtaking your REPL with a new process may 11 | ##' cause strang behaviour, like the loss of autocompletions. 12 | ##' 13 | ##' @title repl 14 | ##' @return nothing. 15 | ##' @author Miles McBain 16 | ##' @export 17 | repl <- function() { 18 | 19 | session_options <- callr::r_session_options() 20 | session_options$libpath <- c(renv::paths$library(), system_libraries()) 21 | capsule <- callr::r_session$new(options = session_options, wait = TRUE) 22 | capsule$attach() 23 | 24 | } 25 | -------------------------------------------------------------------------------- /R/reproduce.R: -------------------------------------------------------------------------------- 1 | ##' Reproduce the capsule library from the lockfile 2 | ##' 3 | ##' If you have cloned a project that contains a lockfile you can actually just 4 | ##' use `run()` to execute commands in the capsule and have the library built 5 | ##' automatically. If that is not convenient, this will explicitly create the 6 | ##' capsule library from the lockfile. 7 | ##' 8 | ##' @title reproduce_lib 9 | ##' @return nothing. 10 | ##' @author Miles McBain 11 | ##' @param lockfile_path The path to the lockfile to produce library for. Default is `./renv.lock`. 12 | ##' @export 13 | reproduce_lib <- function(lockfile_path = "./renv.lock") { 14 | 15 | if (capsule_exists()) stop("[capsule] I found a capsule library. Use delete_local_lib() to remove it and try again") 16 | 17 | callr::r(function(){ 18 | renv::init(bare = TRUE) 19 | renv::deactivate() 20 | }) 21 | delete_unneeded() 22 | renv::restore(project = getwd(), 23 | library = renv::paths$library(), 24 | lockfile = lockfile_path, 25 | confirm = FALSE) 26 | 27 | } 28 | 29 | -------------------------------------------------------------------------------- /R/run.R: -------------------------------------------------------------------------------- 1 | ##' run function in a new process in the capsule 2 | ##' 3 | ##' Execute the supplied function in the context of the capsule library using 4 | ##' `callr::r`. This ensures code is run in a new R process that will not be 5 | ##' contaminated by the state of the interactive development environment. 6 | ##' 7 | ##' @section Lockfile: 8 | ##' At a minimum, an `renv` lockfile must be present in the current working 9 | ##' directory. The capsule library will be generated from the lockfile if it 10 | ##' does not exist. Use `create()` to make the lockfile. 11 | ##' 12 | ##' @section Details: 13 | ##' `run` is a more convenient interface to `run_callr`, which inserts the 14 | ##' `code` argument into 15 | ##' the body of a function, to be run in a child R process. The code is passed 16 | ##' through to the function body using non-standard evaluation. If edge cases 17 | ##' arise due to non-standard evaluation, prefer `run_callr`. 18 | ##' 19 | ##' @rdname run 20 | ##' @title run functions in the capsule 21 | ##' @param func a function to run in the capsule context, as per the 22 | ##' [callr::r()] interface. 23 | ##' @param code the body of function to be run in the 24 | ##' capsule context. See Details. 25 | ##' @param ... additional arguments passed to `callr::r()` 26 | ##' @param lockfile_path the path to lockfile for the capsule library. Default is `./renv.lock`. 27 | ##' @param show Whether to show standard output in console. 28 | ##' @return output of `func` 29 | ##' @author Miles McBain 30 | ##' @seealso [callr::r()] for detailed calling semantics, [create()] to make the 31 | ##' lockfile. [run()] for a lighter weight alternative. 32 | ##' @export 33 | ##' @examples 34 | ##' \dontrun{ 35 | ##' run_callr(function() {library(tidyverse)}) 36 | ##' run(library(tidyverse)) 37 | ##' By default rmarkdown::render looks into the .GlobalEnv: 38 | ##' run_session(rmarkdown::render("./analysis.Rmd")) 39 | ##' } 40 | run_callr <- function(func, show = TRUE, ..., lockfile_path = "./renv.lock") { 41 | 42 | reproduce_lib_if_not_present(lockfile_path) 43 | callr::r( 44 | func = func, 45 | libpath = c(renv::paths$library()), 46 | show = show, 47 | ... 48 | ) 49 | 50 | } 51 | 52 | #' Run an R script in a new process in the capsule 53 | #' 54 | #' Execute the supplied R script in the context of the capsule library using 55 | #' [callr::rscript()]. This ensures the script is executed in a new R process 56 | #' that will not be contaminated by the state of the interactive development 57 | #' environment and will use the R packages and versions in the capsule. 58 | #' 59 | #' @seealso [run_callr()] for more details relevant to `run_rscript()`, 60 | #' [callr::r()] for detailed calling semantics, [create()] to make the 61 | #' lockfile. [run()] for a lighter weight alternative. 62 | #' 63 | #' @param path The path to the R script 64 | #' @param show Whether to show standard output in console. 65 | #' @param lockfile_path the path to lockfile for the capsule library. Default is `./renv.lock`. 66 | #' @inheritParams callr::rscript 67 | #' @inheritDotParams callr::rscript 68 | #' 69 | #' @return Invisibly returns the result of [callr::rscript()] 70 | #' 71 | #' @export 72 | run_rscript <- function(path, ..., lockfile_path = "./renv.lock", show = TRUE) { 73 | reproduce_lib_if_not_present() 74 | callr::rscript( 75 | path, 76 | libpath = c(renv::paths$library()), 77 | show = show, 78 | ... 79 | ) 80 | } 81 | 82 | 83 | #' @rdname run 84 | #' @export 85 | run <- function(code, lockfile_path = "./renv.lock") { 86 | 87 | arg <- substitute(code) 88 | run_fn <- bquote(function() { 89 | .(arg) 90 | }) 91 | run_callr(eval(run_fn), lockfile_path = lockfile_path) 92 | 93 | } 94 | 95 | ##' run code in the context of the capsule in the current R session 96 | ##' 97 | ##' Execute the supplied function in the context of the capsule library, by 98 | ##' changing the R library paths it searches. 99 | ##' 100 | ##' In almost all cases, run or run_callr which do effectively the same thing, 101 | ##' are preferred. This is because the `code` argument can cause packages 102 | ##' to be attached, and thus not read from the capsule library. 103 | ##' 104 | ##' For example if `code` was `drake::r_make()` this would cause `drake`, to 105 | ## be attached from the main R library, not the capsule, which could cause 106 | ##' compatibility issues. 107 | ##' 108 | ##' Use this function when you have R code that modifies the .GlobalEnv, and 109 | ##' you want to inspect it at the end, or you want to actively debug with #' 110 | ##' browser() or recover(). Even then it may be preferrable to use 111 | ##' capsule::repl() to do debugging. 112 | ##' 113 | ##' @inheritSection run_callr Lockfile 114 | ##' 115 | ##' @title run_session 116 | ##' @param code an expression to run in the context of the capsule library. 117 | ##' @param lockfile_path the path to lockfile for the capsule library. Default is `./renv.lock`. 118 | ##' @return output of `code` 119 | ##' @author Miles McBain 120 | ##' @seealso [create()] to make the lockfile. [run_callr()] and [run()] for safer versions. 121 | ##' @export 122 | ##' @examples 123 | ##' \dontrun{ 124 | ##' run(library()) 125 | ##' run(search()) 126 | ##' capsule::run({ 127 | ##' search() 128 | ##' message("hello") 129 | ##' }) 130 | ##' } 131 | run_session <- function(code, lockfile_path = "./renv.lock") { 132 | 133 | reproduce_lib_if_not_present(lockfile_path) 134 | withr::with_libpaths( 135 | new = renv::paths$library(), 136 | code = code 137 | ) 138 | 139 | } 140 | 141 | reproduce_lib_if_not_present <- function(lockfile_path) { 142 | 143 | if (!dir.exists(locate_capsule())) reproduce_lib(lockfile_path) 144 | 145 | } 146 | -------------------------------------------------------------------------------- /R/utils.R: -------------------------------------------------------------------------------- 1 | system_libraries <- function() { 2 | 3 | Filter( 4 | function(x) fs::path_has_parent(x, Sys.getenv("R_HOME")), 5 | .libPaths() 6 | ) 7 | 8 | } 9 | 10 | delete_unneeded <- function() { 11 | 12 | unlink("./.Rbuildignore") 13 | unlink("./renv/activate.R") 14 | unlink("./renv/settings.dcf") 15 | 16 | } 17 | 18 | `%||%` <- function(lhs, rhs) { 19 | if (is.null(lhs)) rhs else lhs 20 | } 21 | 22 | assert_files_exist <- function(...) { 23 | files <- unlist(list(...)) 24 | missing_files <- !file.exists(files) 25 | if (any(missing_files)) { 26 | stop( 27 | "Required files are missing: ", 28 | paste0(files[missing_files], collapse = ", ") 29 | ) 30 | } 31 | } 32 | 33 | lapply_df <- function(vec, fn, ...) { 34 | do.call( 35 | rbind, 36 | lapply(vec, fn, ...) 37 | ) 38 | } 39 | 40 | # detect the case where some package install tools store the version number in the remoteSha field. 41 | is_real_sha <- function(sha) { 42 | if (is.null(sha) || is.na(sha) || length(sha) == 0) return(FALSE) 43 | !grepl("\\.|-", sha) 44 | } 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # capsule 3 | 4 | [![capsule status badge](https://milesmcbain.r-universe.dev/badges/capsule)](https://milesmcbain.r-universe.dev) 5 | 6 | # Installation 7 | 8 | ## R Universe 9 | ``` {r} 10 | install.packages( 11 | "capsule", 12 | repos = c(mm = "https://milesmcbain.r-universe.dev", getOption("repos"))) 13 | ``` 14 | 15 | # Overview 16 | 17 | A `capsule` is a stable project-specific R package library that you consciously 18 | choose to execute code within. Think of it as representing 'production', while 19 | your normal interactive R session represents 'development'. 20 | 21 | You develop interactively in a dynamic package environment. You run code for 22 | real in a well-defined static capsule. Periodically you'll want to have 23 | your development environment reflected in the capsule as new stuff is 24 | integrated. 25 | 26 | When sharing with others, a capsule is the fallback that ensures your code can 27 | always be run, no matter what issues appear in your collaborator's development 28 | environment. 29 | 30 | # Usage 31 | 32 | There are 3 functions you need to know about to use `capsule`: `capsule::create()`, `capsule::run()`, `capsule::recreate()`. 33 | 34 | ## `create()` a capsule for my pipeline 35 | 36 | ```r 37 | > capsule::create("./packages.R") 38 | Finding R package dependencies ... Done! 39 | * Discovering package dependencies ... Done! 40 | * Copying packages into the cache ... [132/132] Done! 41 | The following package(s) will be updated in the lockfile: 42 | 43 | # CRAN =============================== 44 | - anytime [* -> 0.3.6] 45 | - askpass [* -> 1.1] 46 | ...TRUNCATED... 47 | - BH [* -> 1.69.0-1] 48 | - spatial [* -> 7.3-11] 49 | - survival [* -> 2.44-1.1] 50 | 51 | # GitHub ============================= 52 | - geojsonsf [* -> SymbolixAU/geojsonsf] 53 | - h3jsr [* -> obrl-soil/h3jsr] 54 | - jsonify [* -> SymbolixAU/jsonify] 55 | - qfes [* -> milesmcbain/qfes] 56 | - renv [* -> rstudio/renv] 57 | 58 | * Lockfile written to 'c:/repos/capsule/renv.lock'. 59 | ``` 60 | 61 | You supply a vector of file paths to extract dependencies from. The default is 62 | `"./packages.R"`. These dependencies are copied from your regular (dev) library 63 | to your local capsule. 64 | 65 | Notice how this is easier when you keep your library calls all in one place? :wink: 66 | 67 | You'll notice some things created in your project folder. Assuming you have the 68 | project under version control... you definitely want to commit the `./renv.lock` 69 | file. This will allow someone else to `run()` code in the capsule context. 70 | 71 | ## `run()` code in the capsule 72 | 73 | Render a document in the capsule: 74 | 75 | ```r 76 | capsule::run(rmarkdown::render("doc/analysis.Rmd")) 77 | ``` 78 | 79 | Or run your `{targets}` plan in the capsule: 80 | 81 | ```r 82 | capsule::run(targets::tar_make()) 83 | ``` 84 | 85 | So what about code that you've just been handed? It has a `renv.lock` but no 86 | local library? How do you build the library to run the code? You don't! `run()` 87 | will check to see if a local library exists, and build it if required. (You can do this manually with `reproduce_lib()`, if that feels better before calling `run()`). 88 | 89 | ## `recreate()` the capsule 90 | 91 | You've done some development work, updated a few dependencies, and the output 92 | has tested successfully. You can make the capsule reflect the project dependencies 93 | installed in your dev environment using `recreate()`. 94 | 95 | ## Other Useful Stuff 96 | 97 | ### Debugging in the capsule with a REPL 98 | 99 | Try `capsule::repl()` to attach a REPL for a new R process in the context of the 100 | capsule. This is handy for interactive work like debugging. The tradeoff here is 101 | that depending what editor you use strange behaviour may be induced by the outer 102 | REPL being overtaken. In ESS I lose my autocompletions. 103 | 104 | ### Automating lockfile creation with `capshot()` 105 | 106 | `capshot()` is designed to create a lockfile for your 107 | project very quickly so that it can be integrated into your build or rendering 108 | pipeline. On my [`{tflow}`](https://github.com/milesmcbain/tflow) projects laden with 109 | dependencies it typically takes 1-2 seconds to detect the dependencies and 110 | write the lockfile. 111 | 112 | Unlike `create()` it does not populate a local library automatically. See: 113 | 114 | * `capsule::capshot()` - fast lockfile creation 115 | * `capsule::capshot_str()` - quickly generate the lockfile json for further processing, e.g. embedding in a document. 116 | 117 | ### Helpers 118 | 119 | * `capsule::delete()` - remove the capsule (local library and lockfile). 120 | * `capsule::delete_local_lib()` - remove the local library. 121 | * `capsule::delete_lockfile()` - remove the lockfile. 122 | * `capsule::reproduce_lib()` - build the local library from the lockfile. 123 | 124 | ### It's an `renv` in the end 125 | 126 | A capsule is an `renv`. The full power of `renv` can always be used to 127 | manipulate the lockfile and library if you wish. 128 | -------------------------------------------------------------------------------- /man/any_local_behind_lockfile.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/compare.R 3 | \name{any_local_behind_lockfile} 4 | \alias{any_local_behind_lockfile} 5 | \title{check if any local packages are behind lockfile} 6 | \usage{ 7 | any_local_behind_lockfile( 8 | lockfile_path = "./renv.lock", 9 | dep_source_paths = NULL 10 | ) 11 | } 12 | \arguments{ 13 | \item{lockfile_path}{a length one character vector path of the lockfile for} 14 | 15 | \item{dep_source_paths}{a character vector of file paths to extract 16 | package dependencies from. If NULL (default) the whole local library is compared.} 17 | } 18 | \value{ 19 | TRUE if dev packages are behind lockfile, FALSE otherwise. 20 | } 21 | \description{ 22 | A wrapper for \link{get_local_behind_lockfile} that returns TRUE if any 23 | dependencies found in \code{dep_source_paths} are behind the lockfile version in 24 | \code{lockfile_path} 25 | } 26 | \seealso{ 27 | Other comparisons: 28 | \code{\link{compare_local_to_lockfile}()}, 29 | \code{\link{get_local_behind_lockfile}()} 30 | } 31 | \concept{comparisons} 32 | -------------------------------------------------------------------------------- /man/capshot.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/capshot.R 3 | \name{capshot} 4 | \alias{capshot} 5 | \alias{capshot_str} 6 | \title{Quickly generate an renv compliant lock file} 7 | \usage{ 8 | capshot( 9 | dep_source_paths = "./packages.R", 10 | lockfile_path = "./renv.lock", 11 | minify = FALSE 12 | ) 13 | 14 | capshot_str(dep_source_paths = "./packages.R", minify = FALSE) 15 | } 16 | \arguments{ 17 | \item{dep_source_paths}{files to scan for project dependencies to write to the lock file.} 18 | 19 | \item{lockfile_path}{output path for the lock file.} 20 | 21 | \item{minify}{a boolean value indicicating if lockfile JSON should have whitespace removed to shrink footprint.} 22 | } 23 | \value{ 24 | \code{lockfile_path}. Writes lockfile as a side effect. 25 | } 26 | \description{ 27 | These functions generate json lockfiles that can be restored from using 28 | \code{capsule} or \code{renv}. 29 | } 30 | \details{ 31 | Unlike \code{\link[=create]{create()}} this function does not populate a local library. 32 | It writes a lock file using dependencies found in files in \code{dep_source_paths}. 33 | Package dependency information is mined from DESCRIPTION files using the 34 | current \code{\link[=.libPaths]{.libPaths()}}. 35 | 36 | These functions do not use \code{{renv}} machinery and so may produce 37 | different results. They have been re-implmented for speed, so that they can 38 | be integrated into automated pipelines that build projects or documents. 39 | } 40 | \section{Functions}{ 41 | \itemize{ 42 | \item \code{capshot_str()}: a variation that returns lockfile json as a character vector for further use. 43 | 44 | }} 45 | -------------------------------------------------------------------------------- /man/compare_lockfile.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/compare.R 3 | \name{compare_local_to_lockfile} 4 | \alias{compare_local_to_lockfile} 5 | \alias{compare_capsule_to_lockfile} 6 | \title{compare the local R library with the lockfile} 7 | \usage{ 8 | compare_local_to_lockfile(lockfile_path = "./renv.lock") 9 | 10 | compare_capsule_to_lockfile(lockfile_path = "./renv.lock") 11 | } 12 | \arguments{ 13 | \item{lockfile_path}{a length one character vector path of the lockfile for} 14 | } 15 | \value{ 16 | a summary dataframe of version differences 17 | } 18 | \description{ 19 | Get a summary dataframe comparing package versions in the lockfile with 20 | versions in the local R library (.libPaths()) or capsule library (./renv). 21 | } 22 | \section{Functions}{ 23 | \itemize{ 24 | \item \code{compare_capsule_to_lockfile()}: compares the renv libray to the lockfile 25 | 26 | }} 27 | \seealso{ 28 | Other comparisons: 29 | \code{\link{any_local_behind_lockfile}()}, 30 | \code{\link{get_local_behind_lockfile}()} 31 | 32 | Other comparisons: 33 | \code{\link{any_local_behind_lockfile}()}, 34 | \code{\link{get_local_behind_lockfile}()} 35 | } 36 | \concept{comparisons} 37 | -------------------------------------------------------------------------------- /man/create.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/create.R 3 | \name{create} 4 | \alias{create} 5 | \title{create} 6 | \usage{ 7 | create(dep_source_paths = "./packages.R", lockfile_path = "./renv.lock") 8 | } 9 | \arguments{ 10 | \item{dep_source_paths}{files to find package dependencies in.} 11 | 12 | \item{lockfile_path}{The path to the lockfile to produce capsule library for. Default is \code{./renv.lock}.} 13 | } 14 | \value{ 15 | nothing. Creates a capsule as a side effect. 16 | } 17 | \description{ 18 | Create a capsule library context to run code in 19 | } 20 | \details{ 21 | Dependencies to be encapsulated are detected from files you nominate in 22 | \code{dep_source_paths}. Good practice would be to have a single dependencies R 23 | file that contains all library() calls - hence this makes an explicit 24 | assertion of your dependencies. This way spurious usages of pkg:: for 25 | packages not stated as dependencies will cause errors that can be caught. 26 | } 27 | \author{ 28 | Miles McBain 29 | } 30 | -------------------------------------------------------------------------------- /man/delete.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/delete.R 3 | \name{delete} 4 | \alias{delete} 5 | \title{delete} 6 | \usage{ 7 | delete() 8 | } 9 | \value{ 10 | nothing 11 | } 12 | \description{ 13 | Delete the capsule 14 | } 15 | \details{ 16 | Removes the lockfile and library, in the case that you made a mistake or no 17 | longer want to use capsule. 18 | } 19 | \author{ 20 | Miles McBain 21 | } 22 | -------------------------------------------------------------------------------- /man/delete_local_lib.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/delete.R 3 | \name{delete_local_lib} 4 | \alias{delete_local_lib} 5 | \title{delete_local_lib} 6 | \usage{ 7 | delete_local_lib() 8 | } 9 | \value{ 10 | nothing 11 | } 12 | \description{ 13 | Delete the capsule's local library 14 | } 15 | \details{ 16 | This helper is provided to help you recover from mistakes or test building 17 | the library from a lockfile you have generated. 18 | } 19 | \seealso{ 20 | \code{\link[=delete]{delete()}} 21 | } 22 | \author{ 23 | Miles McBain 24 | } 25 | -------------------------------------------------------------------------------- /man/delete_lockfile.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/delete.R 3 | \name{delete_lockfile} 4 | \alias{delete_lockfile} 5 | \title{delete_lockfile} 6 | \usage{ 7 | delete_lockfile() 8 | } 9 | \value{ 10 | nothing 11 | } 12 | \description{ 13 | Delete the capule's lockfile 14 | } 15 | \details{ 16 | This helper is provided to help you recover from mistakes or test extracting 17 | dependencies. 18 | } 19 | \author{ 20 | Miles McBain 21 | } 22 | -------------------------------------------------------------------------------- /man/detect_dependencies.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/detect_dependencies.R 3 | \name{detect_dependencies} 4 | \alias{detect_dependencies} 5 | \title{Detect dependencies in nominated R or Rmd files.} 6 | \usage{ 7 | detect_dependencies(file_path) 8 | } 9 | \arguments{ 10 | \item{file_path}{the file(s) to detect dependencies in.} 11 | } 12 | \value{ 13 | a character vector of package names 14 | } 15 | \description{ 16 | Get the names of R packages referred to in \code{file_path}. \code{file_path} can be a 17 | vector of paths if you need, although I advise keeping dependency calls in a 18 | single file for R projects. 19 | } 20 | \details{ 21 | This is a thin wrapper around \verb{[renv::dependencies()]} that includes support 22 | for the \verb{[using::pkg()]} style specification via \verb{[using::detect_dependencies()]} 23 | } 24 | -------------------------------------------------------------------------------- /man/dev_mirror_lockfile.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/mirror.R 3 | \name{dev_mirror_lockfile} 4 | \alias{dev_mirror_lockfile} 5 | \title{Mirror lockfile in local library} 6 | \usage{ 7 | dev_mirror_lockfile( 8 | lockfile_path = "./renv.lock", 9 | dep_source_paths = NULL, 10 | library_path = .libPaths(), 11 | prompt = interactive() 12 | ) 13 | } 14 | \arguments{ 15 | \item{lockfile_path}{lockfile to be compared to local environment.} 16 | 17 | \item{dep_source_paths}{R files to search for dependencies, the set of packages to be updated is limited to these dependencies (and their dependencies).} 18 | 19 | \item{library_path}{the source libary to compare versions from, and install into.} 20 | 21 | \item{prompt}{ask for confirmation after displaying list to be installed and before isntalling?} 22 | } 23 | \value{ 24 | names of the packages updated or to be updated (if install did not proceed) invisibly. 25 | } 26 | \description{ 27 | Install packages contained in the lockfile that are either missing from the local library or at a lower version number. 28 | } 29 | \details{ 30 | Packages are installed at the lockfile version. Packages in the local library that are ahead of the the local library are not touched. 31 | 32 | So this function ensures that the local development environment is \strong{at least} at the lockfile version of all packages, not \strong{equal to}. 33 | 34 | To find differences between the local library and the lockfile versions use \code{\link[=compare_local_to_lockfile]{compare_local_to_lockfile()}}. 35 | } 36 | -------------------------------------------------------------------------------- /man/get_behind.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/compare.R 3 | \name{get_local_behind_lockfile} 4 | \alias{get_local_behind_lockfile} 5 | \alias{get_capsule_behind_lockfile} 6 | \title{get packckes behind lockfile} 7 | \usage{ 8 | get_local_behind_lockfile( 9 | lockfile_path = "./renv.lock", 10 | dep_source_paths = NULL, 11 | library_path = .libPaths() 12 | ) 13 | 14 | get_capsule_behind_lockfile( 15 | lockfile_path = "./renv.lock", 16 | dep_source_paths = NULL 17 | ) 18 | } 19 | \arguments{ 20 | \item{lockfile_path}{a length one character vector path of the lockfile for} 21 | 22 | \item{dep_source_paths}{a character vector of file paths to extract 23 | package dependencies from. If NULL (default) the whole local library is compared.} 24 | 25 | \item{library_path}{the source libary to compare versions from.} 26 | } 27 | \value{ 28 | a summary dataframe of package version differences. 29 | } 30 | \description{ 31 | return information on packages in your main R library (\code{.libPaths()}) or capsule library (\code{./renv}) that are behind the 32 | lockfile versions (at \code{lockfile_path}). 33 | } 34 | \details{ 35 | if \code{dep_source_paths} is supplied only dependencies declared in these files are returned. 36 | 37 | Information is returned about packages that are behind in your development 38 | environment, so you can update them to the capsule versions if you wish. 39 | 40 | A warning is thrown in the case that pacakges have the same version but 41 | different remote SHA. E.g. A package in one library is from GitHub and in 42 | the other library is from CRAN. Or Both packages are from GitHub, have the 43 | same version but different SHAs. 44 | } 45 | \section{Functions}{ 46 | \itemize{ 47 | \item \code{get_capsule_behind_lockfile()}: get packages in the renv library that are behind the lockfile 48 | 49 | }} 50 | \examples{ 51 | \dontrun{ 52 | get_local_behind_capsule( 53 | dep_source_paths = "./packages.R", 54 | lockfile_path = "./renv.lock" 55 | ) 56 | } 57 | } 58 | \seealso{ 59 | Other comparisons: 60 | \code{\link{any_local_behind_lockfile}()}, 61 | \code{\link{compare_local_to_lockfile}()} 62 | 63 | Other comparisons: 64 | \code{\link{any_local_behind_lockfile}()}, 65 | \code{\link{compare_local_to_lockfile}()} 66 | } 67 | \concept{comparisons} 68 | -------------------------------------------------------------------------------- /man/recreate.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/recreate.R 3 | \name{recreate} 4 | \alias{recreate} 5 | \title{recreate} 6 | \usage{ 7 | recreate(dep_source_paths = "./packages.R") 8 | } 9 | \arguments{ 10 | \item{dep_source_paths}{a character vector of project source files to 11 | extract dependencies from.} 12 | } 13 | \value{ 14 | nothing. The capsule is regenerated as a side effect. 15 | } 16 | \description{ 17 | Recreate a capsule with new dependencies 18 | } 19 | \details{ 20 | After some development work has been completed, Use this function to update the 21 | capsule environment to match the dependency versions in your development 22 | environment. 23 | 24 | Similarly to \code{create()}, you are expected to supply a vector of files in 25 | your project to extract dependencies from. Things work best when this is a 26 | single file containing only dependency related code. 27 | } 28 | \seealso{ 29 | \code{\link[=create]{create()}} 30 | } 31 | \author{ 32 | Miles McBain 33 | } 34 | -------------------------------------------------------------------------------- /man/repl.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/repl.R 3 | \name{repl} 4 | \alias{repl} 5 | \title{repl} 6 | \usage{ 7 | repl() 8 | } 9 | \value{ 10 | nothing. 11 | } 12 | \description{ 13 | Open a REPL within the capsule 14 | } 15 | \details{ 16 | Uses an experimental feature from \code{callr} to attach a new process repl to 17 | your current interactive session. That REPL evaluates code within the 18 | context of your capsule. 19 | 20 | To exit the process send the use the interrupt signal in the REPL e.g. 21 | Control-C, or, ess-interrupt, or the 'stop' button in rstudio. 22 | 23 | Depending on your R editor, overtaking your REPL with a new process may 24 | cause strang behaviour, like the loss of autocompletions. 25 | } 26 | \author{ 27 | Miles McBain 28 | } 29 | -------------------------------------------------------------------------------- /man/reproduce_lib.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/reproduce.R 3 | \name{reproduce_lib} 4 | \alias{reproduce_lib} 5 | \title{reproduce_lib} 6 | \usage{ 7 | reproduce_lib(lockfile_path = "./renv.lock") 8 | } 9 | \arguments{ 10 | \item{lockfile_path}{The path to the lockfile to produce library for. Default is \code{./renv.lock}.} 11 | } 12 | \value{ 13 | nothing. 14 | } 15 | \description{ 16 | Reproduce the capsule library from the lockfile 17 | } 18 | \details{ 19 | If you have cloned a project that contains a lockfile you can actually just 20 | use \code{run()} to execute commands in the capsule and have the library built 21 | automatically. If that is not convenient, this will explicitly create the 22 | capsule library from the lockfile. 23 | } 24 | \author{ 25 | Miles McBain 26 | } 27 | -------------------------------------------------------------------------------- /man/run.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/run.R 3 | \name{run_callr} 4 | \alias{run_callr} 5 | \alias{run} 6 | \title{run functions in the capsule} 7 | \usage{ 8 | run_callr(func, show = TRUE, ..., lockfile_path = "./renv.lock") 9 | 10 | run(code, lockfile_path = "./renv.lock") 11 | } 12 | \arguments{ 13 | \item{func}{a function to run in the capsule context, as per the 14 | \code{\link[callr:r]{callr::r()}} interface.} 15 | 16 | \item{show}{Whether to show standard output in console.} 17 | 18 | \item{...}{additional arguments passed to \code{callr::r()}} 19 | 20 | \item{lockfile_path}{the path to lockfile for the capsule library. Default is \code{./renv.lock}.} 21 | 22 | \item{code}{the body of function to be run in the 23 | capsule context. See Details.} 24 | } 25 | \value{ 26 | output of \code{func} 27 | } 28 | \description{ 29 | run function in a new process in the capsule 30 | } 31 | \details{ 32 | Execute the supplied function in the context of the capsule library using 33 | \code{callr::r}. This ensures code is run in a new R process that will not be 34 | contaminated by the state of the interactive development environment. 35 | } 36 | \section{Lockfile}{ 37 | 38 | At a minimum, an \code{renv} lockfile must be present in the current working 39 | directory. The capsule library will be generated from the lockfile if it 40 | does not exist. Use \code{create()} to make the lockfile. 41 | } 42 | 43 | \section{Details}{ 44 | 45 | \code{run} is a more convenient interface to \code{run_callr}, which inserts the 46 | \code{code} argument into 47 | the body of a function, to be run in a child R process. The code is passed 48 | through to the function body using non-standard evaluation. If edge cases 49 | arise due to non-standard evaluation, prefer \code{run_callr}. 50 | } 51 | 52 | \examples{ 53 | \dontrun{ 54 | run_callr(function() {library(tidyverse)}) 55 | run(library(tidyverse)) 56 | By default rmarkdown::render looks into the .GlobalEnv: 57 | run_session(rmarkdown::render("./analysis.Rmd")) 58 | } 59 | } 60 | \seealso{ 61 | \code{\link[callr:r]{callr::r()}} for detailed calling semantics, \code{\link[=create]{create()}} to make the 62 | lockfile. \code{\link[=run]{run()}} for a lighter weight alternative. 63 | } 64 | \author{ 65 | Miles McBain 66 | } 67 | -------------------------------------------------------------------------------- /man/run_rscript.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/run.R 3 | \name{run_rscript} 4 | \alias{run_rscript} 5 | \title{Run an R script in a new process in the capsule} 6 | \usage{ 7 | run_rscript(path, ..., lockfile_path = "./renv.lock", show = TRUE) 8 | } 9 | \arguments{ 10 | \item{path}{The path to the R script} 11 | 12 | \item{...}{ 13 | Arguments passed on to \code{\link[callr:rscript]{callr::rscript}} 14 | \describe{ 15 | \item{\code{script}}{Path of the script to run.} 16 | \item{\code{cmdargs}}{Command line arguments.} 17 | \item{\code{libpath}}{The library path.} 18 | \item{\code{repos}}{The \code{repos} option. If \code{NULL}, then no 19 | \code{repos} option is set. This options is only used if 20 | \code{user_profile} or \code{system_profile} is set \code{FALSE}, 21 | as it is set using the system or the user profile.} 22 | \item{\code{stdout}}{Optionally a file name to send the standard output to.} 23 | \item{\code{stderr}}{Optionally a file name to send the standard error to. 24 | It may be the same as \code{stdout}, in which case standard error is 25 | redirected to standard output. It can also be the special string 26 | \code{"2>&1"}, in which case standard error will be redirected to standard 27 | output.} 28 | \item{\code{poll_connection}}{Whether to have a control connection to 29 | the process. This is used to transmit messages from the subprocess 30 | to the parent.} 31 | \item{\code{echo}}{Whether to echo the complete command run by \code{rcmd}.} 32 | \item{\code{callback}}{A function to call for each line of the standard 33 | output and standard error from the child process. It works together 34 | with the \code{show} option; i.e. if \code{show = TRUE}, and a 35 | callback is provided, then the output is shown of the screen, and the 36 | callback is also called.} 37 | \item{\code{block_callback}}{A function to call for each block of the standard 38 | output and standard error. This callback is not line oriented, i.e. 39 | multiple lines or half a line can be passed to the callback.} 40 | \item{\code{spinner}}{Whether to show a calming spinner on the screen while 41 | the child R session is running. By default it is shown if 42 | \code{show = TRUE} and the R session is interactive.} 43 | \item{\code{system_profile}}{Whether to use the system profile file.} 44 | \item{\code{user_profile}}{Whether to use the user's profile file. 45 | If this is \code{"project"}, then only the profile from the working 46 | directory is used, but the \code{R_PROFILE_USER} environment variable 47 | and the user level profile are not. See also "Security considerations" 48 | below.} 49 | \item{\code{env}}{Environment variables to set for the child process.} 50 | \item{\code{timeout}}{Timeout for the function call to finish. It can be a 51 | \link[base:difftime]{base::difftime} object, or a real number, meaning seconds. 52 | If the process does not finish before the timeout period expires, 53 | then a \code{system_command_timeout_error} error is thrown. \code{Inf} 54 | means no timeout.} 55 | \item{\code{wd}}{Working directory to use for running the command. Defaults 56 | to the current working directory.} 57 | \item{\code{fail_on_status}}{Whether to throw an R error if the command returns 58 | with a non-zero status code. By default no error is thrown.} 59 | \item{\code{color}}{Whether to use terminal colors in the child process, 60 | assuming they are active in the parent process.} 61 | }} 62 | 63 | \item{lockfile_path}{the path to lockfile for the capsule library. Default is \code{./renv.lock}.} 64 | 65 | \item{show}{Whether to show standard output in console.} 66 | } 67 | \value{ 68 | Invisibly returns the result of \code{\link[callr:rscript]{callr::rscript()}} 69 | } 70 | \description{ 71 | Execute the supplied R script in the context of the capsule library using 72 | \code{\link[callr:rscript]{callr::rscript()}}. This ensures the script is executed in a new R process 73 | that will not be contaminated by the state of the interactive development 74 | environment and will use the R packages and versions in the capsule. 75 | } 76 | \seealso{ 77 | \code{\link[=run_callr]{run_callr()}} for more details relevant to \code{run_rscript()}, 78 | \code{\link[callr:r]{callr::r()}} for detailed calling semantics, \code{\link[=create]{create()}} to make the 79 | lockfile. \code{\link[=run]{run()}} for a lighter weight alternative. 80 | } 81 | -------------------------------------------------------------------------------- /man/run_session.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/run.R 3 | \name{run_session} 4 | \alias{run_session} 5 | \title{run_session} 6 | \usage{ 7 | run_session(code, lockfile_path = "./renv.lock") 8 | } 9 | \arguments{ 10 | \item{code}{an expression to run in the context of the capsule library.} 11 | 12 | \item{lockfile_path}{the path to lockfile for the capsule library. Default is \code{./renv.lock}.} 13 | } 14 | \value{ 15 | output of \code{code} 16 | } 17 | \description{ 18 | run code in the context of the capsule in the current R session 19 | } 20 | \details{ 21 | Execute the supplied function in the context of the capsule library, by 22 | changing the R library paths it searches. 23 | 24 | In almost all cases, run or run_callr which do effectively the same thing, 25 | are preferred. This is because the \code{code} argument can cause packages 26 | to be attached, and thus not read from the capsule library. 27 | 28 | For example if \code{code} was \code{drake::r_make()} this would cause \code{drake}, to 29 | compatibility issues. 30 | 31 | Use this function when you have R code that modifies the .GlobalEnv, and 32 | you want to inspect it at the end, or you want to actively debug with #' 33 | browser() or recover(). Even then it may be preferrable to use 34 | capsule::repl() to do debugging. 35 | } 36 | \section{Lockfile}{ 37 | 38 | At a minimum, an \code{renv} lockfile must be present in the current working 39 | directory. The capsule library will be generated from the lockfile if it 40 | does not exist. Use \code{create()} to make the lockfile. 41 | } 42 | 43 | \examples{ 44 | \dontrun{ 45 | run(library()) 46 | run(search()) 47 | capsule::run({ 48 | search() 49 | message("hello") 50 | }) 51 | } 52 | } 53 | \seealso{ 54 | \code{\link[=create]{create()}} to make the lockfile. \code{\link[=run_callr]{run_callr()}} and \code{\link[=run]{run()}} for safer versions. 55 | } 56 | \author{ 57 | Miles McBain 58 | } 59 | -------------------------------------------------------------------------------- /man/whinge.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/mirror.R 3 | \name{whinge} 4 | \alias{whinge} 5 | \title{Complain if the local R library has packages that are behind the lockfile versions} 6 | \usage{ 7 | whinge( 8 | whinge_fun = warning, 9 | lockfile_path = "./renv.lock", 10 | dep_source_paths = NULL 11 | ) 12 | } 13 | \arguments{ 14 | \item{whinge_fun}{the function to use to have a whinge about packages, e.g. message, warning, stop, etc.} 15 | 16 | \item{lockfile_path}{the path to the project lockfile} 17 | 18 | \item{dep_source_paths}{to search for dependencies to comparage versions for. E.g. "packages.R" 19 | If NULL the whole local library is compared} 20 | } 21 | \value{ 22 | output of whinge_fun, most likely nothing. 23 | } 24 | \description{ 25 | Useful for keeping teams loosely in sync on package versions. A warning can 26 | be tolerated until updating at a convenient time. For example if 27 | placed in the packages.R file of a \code{{tflow}} project. 28 | } 29 | \details{ 30 | The message is hardcoded, but the whinge_fun that takes the message is customisable. 31 | } 32 | -------------------------------------------------------------------------------- /tests/testthat.R: -------------------------------------------------------------------------------- 1 | library(testthat) 2 | library(capsule) 3 | 4 | test_check("capsule") 5 | -------------------------------------------------------------------------------- /tests/testthat/__renv.R: -------------------------------------------------------------------------------- 1 | library(renv) 2 | -------------------------------------------------------------------------------- /tests/testthat/__testrpkg.R: -------------------------------------------------------------------------------- 1 | library(testrpkg) 2 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/compare.md: -------------------------------------------------------------------------------- 1 | # lockfile/library comparisons work 2 | 3 | Code 4 | compare_capsule_to_lockfile("renv_cran.lock") 5 | Output 6 | data.frame [1, 11] 7 | name chr renv 8 | version_lock chr 0.15.5 9 | repository_lock chr CRAN 10 | remote_sha_lock lgl NA 11 | remote_repo_lock lgl NA 12 | remote_username_lock lgl NA 13 | version_lib chr 0.15.5 14 | repository_lib chr CRAN 15 | remote_sha_lib lgl NA 16 | remote_repo_lib lgl NA 17 | remote_username_lib lgl NA 18 | 19 | --- 20 | 21 | Code 22 | compare_capsule_to_lockfile("renv_pak_rspm.lock") 23 | Output 24 | data.frame [1, 11] 25 | name chr renv 26 | version_lock chr 0.15.5 27 | repository_lock chr RSPM 28 | remote_sha_lock lgl NA 29 | remote_repo_lock chr https://packagemanager.rstudio.com~ 30 | remote_username_lock lgl NA 31 | version_lib chr 0.15.5 32 | repository_lib chr CRAN 33 | remote_sha_lib lgl NA 34 | remote_repo_lib lgl NA 35 | remote_username_lib lgl NA 36 | 37 | -------------------------------------------------------------------------------- /tests/testthat/renv_cran.lock: -------------------------------------------------------------------------------- 1 | { 2 | "R": { 3 | "Version": "4.2.0", 4 | "Repositories": [ 5 | { 6 | "Name": "CRAN", 7 | "URL": "https://cran.rstudio.com" 8 | } 9 | ] 10 | }, 11 | "Packages": { 12 | "renv": { 13 | "Source": "Repository", 14 | "Package": "renv", 15 | "Version": "0.15.5", 16 | "Repository": "CRAN" 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tests/testthat/renv_github.lock: -------------------------------------------------------------------------------- 1 | { 2 | "R": { 3 | "Version": "4.2.0", 4 | "Repositories": [ 5 | { 6 | "Name": "RSPM", 7 | "URL": "https://packagemanager.rstudio.com/all/__linux__/focal/latest" 8 | }, 9 | { 10 | "Name": "CRAN", 11 | "URL": "https://cloud.r-project.org" 12 | } 13 | ] 14 | }, 15 | "Packages": { 16 | "renv": { 17 | "Source": "GitHub", 18 | "Package": "renv", 19 | "Version": "0.15.5-37", 20 | "RemoteType": "github", 21 | "RemoteHost": "api.github.com", 22 | "RemoteRepo": "renv", 23 | "RemoteUsername": "rstudio", 24 | "RemotePkgRef": "rstudio/renv", 25 | "RemoteRef": "HEAD", 26 | "RemoteSha": "71fc8098b6967fbd28a4ff61a6b2dbd15efd241f" 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/testthat/renv_github_cran_ver.lock: -------------------------------------------------------------------------------- 1 | { 2 | "R": { 3 | "Version": "4.2.0", 4 | "Repositories": [ 5 | { 6 | "Name": "RSPM", 7 | "URL": "https://packagemanager.rstudio.com/all/__linux__/focal/latest" 8 | }, 9 | { 10 | "Name": "CRAN", 11 | "URL": "https://cloud.r-project.org" 12 | } 13 | ] 14 | }, 15 | "Packages": { 16 | "renv": { 17 | "Source": "GitHub", 18 | "Package": "renv", 19 | "Version": "0.15.5", 20 | "RemoteType": "github", 21 | "RemoteHost": "api.github.com", 22 | "RemoteRepo": "renv", 23 | "RemoteUsername": "rstudio", 24 | "RemotePkgRef": "rstudio/renv", 25 | "RemoteRef": "HEAD", 26 | "RemoteSha": "085673d756bf39960d4c5cd5a0634a3d5e4e925b" 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/testthat/renv_pak_rspm.lock: -------------------------------------------------------------------------------- 1 | { 2 | "R": { 3 | "Version": "4.2.0", 4 | "Repositories": [ 5 | { 6 | "Name": "RSPM", 7 | "URL": "https://packagemanager.rstudio.com/all/__linux__/focal/latest" 8 | }, 9 | { 10 | "Name": "CRAN", 11 | "URL": "https://cloud.r-project.org" 12 | } 13 | ] 14 | }, 15 | "Packages": { 16 | "renv": { 17 | "Source": "Repository", 18 | "Package": "renv", 19 | "Version": "0.15.5", 20 | "Repository": "RSPM", 21 | "RemoteType": "standard", 22 | "RemotePkgRef": "renv", 23 | "RemoteRef": "renv", 24 | "RemoteRepos": "https://packagemanager.rstudio.com/all/__linux__/focal/latest", 25 | "RemotePkgPlatform": "source", 26 | "RemoteSha": "0.15.5" 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/testthat/renv_rspm.lock: -------------------------------------------------------------------------------- 1 | { 2 | "R": { 3 | "Version": "4.2.0", 4 | "Repositories": [ 5 | { 6 | "Name": "RENV", 7 | "URL": "file:///home/ubuntu/R/x86_64-pc-linux-gnu-library/4.2/renv/repos" 8 | }, 9 | { 10 | "Name": "RSPM", 11 | "URL": "https://packagemanager.rstudio.com/all/__linux__/focal/latest" 12 | }, 13 | { 14 | "Name": "CRAN", 15 | "URL": "https://cloud.r-project.org" 16 | } 17 | ] 18 | }, 19 | "Packages": { 20 | "renv": { 21 | "Source": "Repository", 22 | "Package": "renv", 23 | "Version": "0.15.4", 24 | "Repository": "RSPM" 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /tests/testthat/setup.R: -------------------------------------------------------------------------------- 1 | temp_dir_root <- tempdir() 2 | temp_lib_path <- file.path(temp_dir_root, "capsule_lib") 3 | 4 | # create test library 5 | dir.create(temp_lib_path) 6 | 7 | get_test_libpaths <- function() { 8 | c( 9 | temp_lib_path, 10 | .libPaths() 11 | ) 12 | } 13 | 14 | # Set CRAN 15 | old_repos <- getOption("repos") 16 | options(repos = c(CRAN = "https://cloud.r-project.org")) 17 | withr::defer(options(repos = old_repos), teardown_env()) 18 | cleanup <- function() { 19 | unlink(temp_lib_path, recursive = TRUE) 20 | } 21 | # burn it all down 22 | withr::defer(cleanup(), teardown_env()) 23 | 24 | # populate test library 25 | withr::with_libpaths( 26 | new = temp_lib_path, 27 | code = { 28 | withr::with_options( 29 | list( 30 | repos = c(mm = "https://milesmcbain.r-universe.dev", CRAN = "https://cloud.r-project.org/") 31 | ), 32 | # ideally would use pak here so package cache gets used on test runs 33 | # but pack can't install using a library it itself it not in. 34 | # TODO Could we create a lockfile to restore to the temp library? 35 | renv::restore( 36 | lockfile = testthat::test_path("testrpkg.lock"), 37 | library = temp_lib_path, 38 | confirm = FALSE 39 | ) 40 | ) 41 | } 42 | ) 43 | 44 | -------------------------------------------------------------------------------- /tests/testthat/test-capshot.R: -------------------------------------------------------------------------------- 1 | # test utils 2 | sort_by_name <- function(named) { 3 | named[sort(names(named))] 4 | } 5 | 6 | differences_in <- function(a, b, diffs) { 7 | length(setdiff(setdiff(a, b), diffs)) == 0 8 | } 9 | 10 | test_that("test capshot", { 11 | 12 | withr::with_libpaths( 13 | new = get_test_libpaths(), 14 | code = { 15 | 16 | capsule_lock <- tempfile(fileext = ".lock") 17 | renv_lock <- tempfile(fileext = ".lock") 18 | 19 | capshot( 20 | testthat::test_path("__testrpkg.R"), 21 | capsule_lock 22 | ) 23 | renv::snapshot( 24 | testthat::test_path("__testrpkg.R"), 25 | lockfile = renv_lock, 26 | packages = names( 27 | detect_dependencies(testthat::test_path("__testrpkg.R")) 28 | ), 29 | prompt = FALSE 30 | ) 31 | capsule_json <- jsonlite::fromJSON(capsule_lock) 32 | renv_json <- jsonlite::fromJSON(renv_lock) 33 | 34 | # renv adds itself to the lockfile. Capsule doesn't need to do that. 35 | renv_json$Packages$renv <- NULL 36 | 37 | # need to put packages in same order 38 | capsule_json$Packages <- sort_by_name(capsule_json$Packages) 39 | renv_json$Packages <- sort_by_name(capsule_json$Packages) 40 | 41 | # again ignoring renv in the lock, are the packages the same 42 | expect_equal( 43 | setdiff(names(renv_json$Packages), "renv"), 44 | names(capsule_json$Packages) 45 | ) 46 | 47 | comparison_packages <- names(capsule_json$Packages) 48 | 49 | # Does the data for each package have the same values. 50 | # Order doesn't matter. 51 | package_data_similar <- 52 | lapply(comparison_packages, function(package_name) { 53 | capsule_package_data <- capsule_json$Packages[[package_name]] 54 | renv_package_data <- renv_json$Packages[[package_name]] 55 | 56 | capsule_package_data_names <- names( 57 | capsule_package_data 58 | ) 59 | 60 | differences_in( 61 | names(renv_package_data), 62 | names(capsule_package_data), 63 | c("Hash", "Requirements") 64 | ) && 65 | all( 66 | unlist(capsule_package_data[capsule_package_data_names]) == 67 | unlist(renv_package_data[capsule_package_data_names]) 68 | ) 69 | }) 70 | 71 | expect_true(all(unlist(package_data_similar))) 72 | withr::defer(unlink(capsule_lock), teardown_env()) 73 | withr::defer(unlink(renv_lock), teardown_env()) 74 | 75 | }) 76 | }) 77 | -------------------------------------------------------------------------------- /tests/testthat/test-compare.R: -------------------------------------------------------------------------------- 1 | test_that("lockfile/library comparisons work", { 2 | 3 | 4 | temp_dir <- tempdir() 5 | working_dir_path <- file.path(temp_dir, "compare") 6 | dir.create(working_dir_path) 7 | withr::defer(unlink(working_dir_path, recursive = TRUE), teardown_env()) 8 | 9 | file.copy(test_path("renv_cran.lock"), working_dir_path) 10 | file.copy(test_path("renv_github_cran_ver.lock"), working_dir_path) 11 | file.copy(test_path("renv_pak_rspm.lock"), working_dir_path) 12 | 13 | withr::with_dir( 14 | working_dir_path, 15 | { 16 | # setup a fake R library 17 | reproduce_lib( 18 | lockfile_path = "renv_cran.lock" 19 | ) 20 | 21 | withr::with_libpaths( 22 | renv::paths$library(), 23 | action = "prefix", 24 | code = { 25 | # local library and lockfile match 26 | expect_snapshot(compare_capsule_to_lockfile("renv_cran.lock")) 27 | expect_false(any_local_behind_lockfile("renv_cran.lock")) 28 | expect_null(whinge(lockfile_path = "renv_cran.lock")) 29 | 30 | 31 | # local has CRAN, lockfile has GitHub with same version 32 | result <- NULL 33 | expect_warning( 34 | result <- any_local_behind_lockfile("renv_github_cran_ver.lock"), 35 | "Packages have equal versions but different remote SHAs: renv" 36 | ) 37 | expect_false(result) 38 | expect_warning( 39 | whinge(lockfile_path = "renv_github_cran_ver.lock"), 40 | "Packages have equal versions but different remote SHAs: renv" 41 | ) 42 | 43 | # local has CRAN, lockfile has pak-installed RSPM 44 | expect_snapshot(compare_capsule_to_lockfile("renv_pak_rspm.lock")) 45 | expect_false(any_local_behind_lockfile("renv_pak_rspm.lock")) 46 | expect_null(whinge(lockfile_path = "renv_pak_rspm.lock")) 47 | } 48 | ) 49 | delete_local_lib() 50 | } 51 | ) 52 | 53 | 54 | }) 55 | -------------------------------------------------------------------------------- /tests/testthat/test-create-run.R: -------------------------------------------------------------------------------- 1 | test_that("create and run works", { 2 | 3 | withr::with_libpaths( 4 | new = get_test_libpaths(), 5 | code = { 6 | temp_dir <- tempdir() 7 | working_dir_path <- file.path(temp_dir, "capsule") 8 | 9 | dir.create(working_dir_path) 10 | withr::defer(unlink(working_dir_path, recursive = TRUE), teardown_env()) 11 | file.copy( 12 | testthat::test_path("__testrpkg.R"), 13 | file.path(working_dir_path, "__testrpkg.R") 14 | ) 15 | 16 | # create a capsule library 17 | withr::with_dir( 18 | working_dir_path, 19 | capsule::create( 20 | dep_source_paths = "__testrpkg.R" 21 | ) 22 | ) 23 | 24 | expect_true( 25 | file.exists( 26 | file.path(working_dir_path, "renv.lock") 27 | ) 28 | ) 29 | 30 | lib_name <- 31 | list.files( 32 | file.path(working_dir_path, "renv", "library"), 33 | pattern = "^.renv", 34 | recursive = TRUE, 35 | include.dirs = TRUE, 36 | all.files = TRUE, 37 | full.names = TRUE 38 | ) %>% 39 | fs::path_split() %>% 40 | unlist() %>% 41 | utils::head(-1) %>% # drop .renv 42 | fs::path_join() %>% 43 | as.character() 44 | 45 | libraries <- list.files( 46 | lib_name 47 | ) 48 | 49 | lockfile_json <- jsonlite::fromJSON( 50 | file.path(working_dir_path, "renv.lock") 51 | ) 52 | 53 | # renv installs itself by default, so other than this, 54 | # there should be no differences 55 | expect_equal( 56 | setdiff( 57 | libraries, 58 | names(lockfile_json$Packages) 59 | ), 60 | "renv" 61 | ) 62 | 63 | # check they all have a DESCRIPTION 64 | expect_true( 65 | all(vapply( 66 | file.path(lib_name, libraries, "DESCRIPTION"), 67 | file.exists, 68 | logical(1) 69 | )) 70 | ) 71 | 72 | # now let's check the lib paths using capsule::run() 73 | capsule_lib_paths <- withr::with_dir( 74 | working_dir_path, 75 | capsule::run(.libPaths()) 76 | ) 77 | 78 | # The priority library is the same 79 | expect_equal( 80 | capsule_lib_paths[[1]], 81 | lib_name 82 | ) 83 | } 84 | ) 85 | 86 | }) 87 | -------------------------------------------------------------------------------- /tests/testthat/test-sha-is-version.R: -------------------------------------------------------------------------------- 1 | test_that("is_real_sha works", { 2 | 3 | real_sha <- "7ef575ab812a189d2869e21bd11ac8f7ecd6bc81" 4 | fake_sha <- "1.2.3" 5 | 6 | expect_false(is_real_sha(fake_sha)) 7 | expect_true(is_real_sha(real_sha)) 8 | expect_false(is_real_sha(NA)) 9 | expect_false(is_real_sha(NULL)) 10 | expect_false(is_real_sha(character(0))) 11 | 12 | }) 13 | -------------------------------------------------------------------------------- /tests/testthat/testrpkg.lock: -------------------------------------------------------------------------------- 1 | { 2 | "R": { 3 | "Version": "4.4.1", 4 | "Repositories": [ 5 | { 6 | "Name": "CRAN", 7 | "URL": "https://mirror.aarnet.edu.au/pub/CRAN" 8 | } 9 | ] 10 | }, 11 | "Packages": { 12 | "bit64": { 13 | "Source": "Repository", 14 | "Package": "bit64", 15 | "Version": "4.6.0-1", 16 | "Repository": "CRAN" 17 | }, 18 | "bit": { 19 | "Source": "Repository", 20 | "Package": "bit", 21 | "Version": "4.5.0.1", 22 | "Repository": "CRAN" 23 | }, 24 | "cli": { 25 | "Source": "Repository", 26 | "Package": "cli", 27 | "Version": "3.6.4", 28 | "Repository": "CRAN" 29 | }, 30 | "clipr": { 31 | "Source": "Repository", 32 | "Package": "clipr", 33 | "Version": "0.8.0", 34 | "Repository": "CRAN", 35 | "RemoteType": "standard", 36 | "RemotePkgRef": "clipr", 37 | "RemoteRef": "clipr", 38 | "RemoteRepos": "https://mirror.aarnet.edu.au/pub/CRAN", 39 | "RemotePkgPlatform": "source", 40 | "RemoteSha": "0.8.0" 41 | }, 42 | "cpp11": { 43 | "Source": "Repository", 44 | "Package": "cpp11", 45 | "Version": "0.5.2", 46 | "Repository": "CRAN" 47 | }, 48 | "crayon": { 49 | "Source": "Repository", 50 | "Package": "crayon", 51 | "Version": "1.5.3", 52 | "Repository": "CRAN", 53 | "RemoteType": "standard", 54 | "RemotePkgRef": "crayon", 55 | "RemoteRef": "crayon", 56 | "RemoteRepos": "https://mirror.aarnet.edu.au/pub/CRAN", 57 | "RemotePkgPlatform": "source", 58 | "RemoteSha": "1.5.3" 59 | }, 60 | "fansi": { 61 | "Source": "Repository", 62 | "Package": "fansi", 63 | "Version": "1.0.6", 64 | "Repository": "CRAN", 65 | "RemoteType": "standard", 66 | "RemotePkgRef": "fansi", 67 | "RemoteRef": "fansi", 68 | "RemoteRepos": "https://mirror.aarnet.edu.au/pub/CRAN", 69 | "RemotePkgPlatform": "source", 70 | "RemoteSha": "1.0.6" 71 | }, 72 | "glue": { 73 | "Source": "Repository", 74 | "Package": "glue", 75 | "Version": "1.8.0", 76 | "Repository": "CRAN", 77 | "RemoteType": "standard", 78 | "RemotePkgRef": "glue", 79 | "RemoteRef": "glue", 80 | "RemoteRepos": "https://mirror.aarnet.edu.au/pub/CRAN", 81 | "RemotePkgPlatform": "source", 82 | "RemoteSha": "1.8.0" 83 | }, 84 | "hms": { 85 | "Source": "Repository", 86 | "Package": "hms", 87 | "Version": "1.1.3", 88 | "Repository": "CRAN", 89 | "RemoteType": "standard", 90 | "RemotePkgRef": "hms", 91 | "RemoteRef": "hms", 92 | "RemoteRepos": "https://mirror.aarnet.edu.au/pub/CRAN", 93 | "RemotePkgPlatform": "source", 94 | "RemoteSha": "1.1.3" 95 | }, 96 | "lifecycle": { 97 | "Source": "Repository", 98 | "Package": "lifecycle", 99 | "Version": "1.0.4", 100 | "Repository": "CRAN", 101 | "RemoteType": "standard", 102 | "RemotePkgRef": "lifecycle", 103 | "RemoteRef": "lifecycle", 104 | "RemoteRepos": "https://mirror.aarnet.edu.au/pub/CRAN", 105 | "RemotePkgPlatform": "source", 106 | "RemoteSha": "1.0.4" 107 | }, 108 | "magrittr": { 109 | "Source": "Repository", 110 | "Package": "magrittr", 111 | "Version": "2.0.3", 112 | "Repository": "CRAN", 113 | "RemoteType": "standard", 114 | "RemotePkgRef": "magrittr", 115 | "RemoteRef": "magrittr", 116 | "RemoteRepos": "https://mirror.aarnet.edu.au/pub/CRAN", 117 | "RemotePkgPlatform": "source", 118 | "RemoteSha": "2.0.3" 119 | }, 120 | "pillar": { 121 | "Source": "Repository", 122 | "Package": "pillar", 123 | "Version": "1.10.1", 124 | "Repository": "CRAN" 125 | }, 126 | "pkgconfig": { 127 | "Source": "Repository", 128 | "Package": "pkgconfig", 129 | "Version": "2.0.3", 130 | "Repository": "CRAN", 131 | "RemoteType": "standard", 132 | "RemotePkgRef": "pkgconfig", 133 | "RemoteRef": "pkgconfig", 134 | "RemoteRepos": "https://mirror.aarnet.edu.au/pub/CRAN", 135 | "RemotePkgPlatform": "source", 136 | "RemoteSha": "2.0.3" 137 | }, 138 | "prettyunits": { 139 | "Source": "Repository", 140 | "Package": "prettyunits", 141 | "Version": "1.2.0", 142 | "Repository": "CRAN", 143 | "RemoteType": "standard", 144 | "RemotePkgRef": "prettyunits", 145 | "RemoteRef": "prettyunits", 146 | "RemoteRepos": "https://mirror.aarnet.edu.au/pub/CRAN", 147 | "RemotePkgPlatform": "source", 148 | "RemoteSha": "1.2.0" 149 | }, 150 | "progress": { 151 | "Source": "Repository", 152 | "Package": "progress", 153 | "Version": "1.2.3", 154 | "Repository": "CRAN", 155 | "RemoteType": "standard", 156 | "RemotePkgRef": "progress", 157 | "RemoteRef": "progress", 158 | "RemoteRepos": "https://mirror.aarnet.edu.au/pub/CRAN", 159 | "RemotePkgPlatform": "source", 160 | "RemoteSha": "1.2.3" 161 | }, 162 | "R6": { 163 | "Source": "Repository", 164 | "Package": "R6", 165 | "Version": "2.6.1", 166 | "Repository": "CRAN" 167 | }, 168 | "readr": { 169 | "Source": "Repository", 170 | "Package": "readr", 171 | "Version": "2.1.5", 172 | "Repository": "CRAN", 173 | "RemoteType": "standard", 174 | "RemotePkgRef": "readr", 175 | "RemoteRef": "readr", 176 | "RemoteRepos": "https://mirror.aarnet.edu.au/pub/CRAN", 177 | "RemotePkgPlatform": "source", 178 | "RemoteSha": "2.1.5" 179 | }, 180 | "rlang": { 181 | "Source": "Repository", 182 | "Package": "rlang", 183 | "Version": "1.1.5", 184 | "Repository": "CRAN" 185 | }, 186 | "rstudioapi": { 187 | "Source": "Repository", 188 | "Package": "rstudioapi", 189 | "Version": "0.17.1", 190 | "Repository": "CRAN", 191 | "RemoteType": "standard", 192 | "RemotePkgRef": "rstudioapi", 193 | "RemoteRef": "rstudioapi", 194 | "RemoteRepos": "https://mirror.aarnet.edu.au/pub/CRAN", 195 | "RemoteReposName": "CRAN", 196 | "RemotePkgPlatform": "source", 197 | "RemoteSha": "0.17.1" 198 | }, 199 | "testrpkg2": { 200 | "Source": "GitHub", 201 | "Package": "testrpkg2", 202 | "Version": "1.0.0", 203 | "RemoteType": "github", 204 | "RemoteHost": "api.github.com", 205 | "RemoteRepo": "testrpkg2", 206 | "RemoteUsername": "milesmcbain", 207 | "RemoteRef": "HEAD", 208 | "RemoteSha": "ec0d23a8dc1307aaf64034d17ac0ba3021be2df3" 209 | }, 210 | "testrpkg": { 211 | "Source": "GitHub", 212 | "Package": "testrpkg", 213 | "Version": "1.0.1", 214 | "RemoteType": "github", 215 | "RemoteHost": "api.github.com", 216 | "RemoteRepo": "testrpkg", 217 | "RemoteUsername": "milesmcbain", 218 | "RemoteRef": "HEAD", 219 | "RemoteSha": "1768f22be009ae0411abcdfe732d17d874002530" 220 | }, 221 | "tibble": { 222 | "Source": "Repository", 223 | "Package": "tibble", 224 | "Version": "3.2.1", 225 | "Repository": "CRAN", 226 | "RemoteType": "standard", 227 | "RemotePkgRef": "tibble", 228 | "RemoteRef": "tibble", 229 | "RemoteRepos": "https://mirror.aarnet.edu.au/pub/CRAN", 230 | "RemotePkgPlatform": "source", 231 | "RemoteSha": "3.2.1" 232 | }, 233 | "tidyselect": { 234 | "Source": "Repository", 235 | "Package": "tidyselect", 236 | "Version": "1.2.1", 237 | "Repository": "CRAN", 238 | "RemoteType": "standard", 239 | "RemotePkgRef": "tidyselect", 240 | "RemoteRef": "tidyselect", 241 | "RemoteRepos": "https://mirror.aarnet.edu.au/pub/CRAN", 242 | "RemotePkgPlatform": "source", 243 | "RemoteSha": "1.2.1" 244 | }, 245 | "tzdb": { 246 | "Source": "Repository", 247 | "Package": "tzdb", 248 | "Version": "0.4.0", 249 | "Repository": "CRAN", 250 | "RemoteType": "standard", 251 | "RemotePkgRef": "tzdb", 252 | "RemoteRef": "tzdb", 253 | "RemoteRepos": "https://mirror.aarnet.edu.au/pub/CRAN", 254 | "RemotePkgPlatform": "source", 255 | "RemoteSha": "0.4.0" 256 | }, 257 | "utf8": { 258 | "Source": "Repository", 259 | "Package": "utf8", 260 | "Version": "1.2.4", 261 | "Repository": "CRAN", 262 | "RemoteType": "standard", 263 | "RemotePkgRef": "utf8", 264 | "RemoteRef": "utf8", 265 | "RemoteRepos": "https://mirror.aarnet.edu.au/pub/CRAN", 266 | "RemotePkgPlatform": "source", 267 | "RemoteSha": "1.2.4" 268 | }, 269 | "vctrs": { 270 | "Source": "Repository", 271 | "Package": "vctrs", 272 | "Version": "0.6.5", 273 | "Repository": "CRAN", 274 | "RemoteType": "standard", 275 | "RemotePkgRef": "vctrs", 276 | "RemoteRef": "vctrs", 277 | "RemoteRepos": "https://mirror.aarnet.edu.au/pub/CRAN", 278 | "RemotePkgPlatform": "source", 279 | "RemoteSha": "0.6.5" 280 | }, 281 | "vroom": { 282 | "Source": "Repository", 283 | "Package": "vroom", 284 | "Version": "1.6.5", 285 | "Repository": "CRAN", 286 | "RemoteType": "standard", 287 | "RemotePkgRef": "vroom", 288 | "RemoteRef": "vroom", 289 | "RemoteRepos": "https://mirror.aarnet.edu.au/pub/CRAN", 290 | "RemotePkgPlatform": "source", 291 | "RemoteSha": "1.6.5" 292 | }, 293 | "withr": { 294 | "Source": "Repository", 295 | "Package": "withr", 296 | "Version": "3.0.2", 297 | "Repository": "CRAN", 298 | "RemoteType": "standard", 299 | "RemotePkgRef": "withr", 300 | "RemoteRef": "withr", 301 | "RemoteRepos": "https://mirror.aarnet.edu.au/pub/CRAN", 302 | "RemotePkgPlatform": "source", 303 | "RemoteSha": "3.0.2" 304 | } 305 | } 306 | } 307 | --------------------------------------------------------------------------------