├── .Rprofile ├── .gitignore ├── Dockerfile ├── LICENSE ├── R ├── download_data.R ├── ingest_data.R ├── setup_duckdb.R └── transform_data.R ├── README.md ├── _targets.R ├── renv.lock ├── renv ├── .gitignore ├── activate.R └── settings.json ├── run.R └── tests └── testthat.R /.Rprofile: -------------------------------------------------------------------------------- 1 | source("renv/activate.R") 2 | 3 | options(repos = c(P3M = "https://packagemanager.posit.co/cran/latest")) 4 | 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.code-workspace 2 | _targets/ 3 | data/ 4 | 5 | !.gitkeep -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Stage 1: Build base image and install dependencies 2 | FROM rocker/r-ver:4.4.1 AS base 3 | 4 | # Set environment variables 5 | ENV WORKDIR=/project 6 | ENV RENV_VERSION=1.0.7 7 | ENV RENV_PATHS_LIBRARY=${WORKDIR}/renv/library 8 | ENV RENV_PATHS_CACHE=${WORKDIR}/renv/.cache 9 | 10 | # Install system dependencies 11 | RUN apt-get update && apt-get install -y \ 12 | cmake \ 13 | libcurl4-openssl-dev \ 14 | libmbedtls-dev \ 15 | libssl-dev \ 16 | libxml2-dev \ 17 | tree \ 18 | xz-utils \ 19 | && rm -rf /var/lib/apt/lists/* 20 | 21 | # Set the working directory 22 | WORKDIR ${WORKDIR} 23 | 24 | FROM base as builder 25 | 26 | # Install specific version of renv 27 | RUN R -e "install.packages('renv', repos='https://cloud.r-project.org', type='source', version='${RENV_VERSION}')" 28 | 29 | # Copy renv files 30 | # https://rstudio.github.io/renv/articles/docker.html 31 | COPY renv.lock renv.lock 32 | COPY .Rprofile .Rprofile 33 | RUN mkdir -p renv 34 | COPY renv/activate.R renv/activate.R 35 | COPY renv/settings.json renv/settings.json 36 | 37 | # Change default location of cache to project folder 38 | RUN mkdir ${RENV_PATHS_CACHE} 39 | RUN mkdir ${RENV_PATHS_LIBRARY} 40 | 41 | # Restore R packages using renv 42 | RUN R -e "renv::restore()" 43 | 44 | # Stage 2: final image with application code 45 | FROM base as runner 46 | 47 | # Set the working directory 48 | WORKDIR ${WORKDIR} 49 | 50 | # Copy files from builder stage 51 | COPY --from=builder ${WORKDIR} . 52 | 53 | # Copy project files 54 | COPY _targets.R _targets.R 55 | COPY R R 56 | COPY run.R run.R 57 | 58 | RUN mkdir -p data 59 | 60 | # Mount the _targets directory as a volume 61 | VOLUME ${WORKDIR}/_targets 62 | 63 | # Restore R packages from the builder stage's cache 64 | # TODO renv would install again within the runner stage? 65 | # RUN R -e "renv::restore()" 66 | 67 | # Command to run the targets pipeline 68 | CMD ["Rscript", "--verbose", "run.R"] 69 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Philip Orlando 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 | -------------------------------------------------------------------------------- /R/download_data.R: -------------------------------------------------------------------------------- 1 | get_data_url <- function() { 2 | url <- "https://gis.dhcs.ca.gov/api/download/v1/items/d2e10fb206454d88813a45e0a42a1ea4/csv?layers=0" 3 | return(url) 4 | } 5 | 6 | 7 | get_url_modified_time <- function(url) { 8 | response <- httr2::request(url) |> 9 | httr2::req_method(method = "HEAD") |> 10 | httr2::req_perform() |> 11 | httr2::resp_check_status() 12 | 13 | url_modified_time <- response |> 14 | httr2::resp_header(header = "Last-Modified") |> 15 | lubridate::parse_date_time(orders = "a, d b Y H:M:S", tz = "GMT") 16 | 17 | return(url_modified_time) 18 | } 19 | 20 | 21 | download_data <- function(url, url_modified_time) { 22 | destfile <- "data/Medi-Cal_Managed_Care_Provider_Listing.csv" 23 | 24 | response <- httr2::request(url) |> 25 | httr2::req_timeout(300) |> 26 | httr2::req_progress() |> 27 | httr2::req_perform(destfile) |> 28 | httr2::resp_check_status() 29 | 30 | return(destfile) 31 | } 32 | -------------------------------------------------------------------------------- /R/ingest_data.R: -------------------------------------------------------------------------------- 1 | ingest_data <- function(source_file, duckdb_file) { 2 | table_name <- "medi_cal_managed_care_providers" 3 | con <- DBI::dbConnect(duckdb::duckdb(), dbdir = duckdb_file) 4 | on.exit(DBI::dbDisconnect(con)) 5 | 6 | # duckdb read_csv() error: CSV options could not be auto-detected. Consider setting parser options manually. 7 | df <- readr::read_csv( 8 | source_file, 9 | col_types = readr::cols( 10 | .default = readr::col_character() 11 | ), 12 | ) |> 13 | janitor::clean_names() 14 | 15 | DBI::dbWriteTable(con, table_name, df, overwrite = TRUE) 16 | 17 | hash <- dplyr::tbl(con, table_name) |> 18 | dplyr::collect() |> 19 | digest::digest() 20 | 21 | return(hash) 22 | } 23 | -------------------------------------------------------------------------------- /R/setup_duckdb.R: -------------------------------------------------------------------------------- 1 | setup_duckdb <- function() { 2 | duckdb_file <- "data/database.duckdb" 3 | 4 | if (!file.exists(duckdb_file)) { 5 | con <- DBI::dbConnect(duckdb::duckdb(), dbdir = duckdb_file) 6 | DBI::dbDisconnect(con, shutdown = TRUE) 7 | } 8 | 9 | return(duckdb_file) 10 | } 11 | -------------------------------------------------------------------------------- /R/transform_data.R: -------------------------------------------------------------------------------- 1 | transform_data <- function(ingest_hash, duckdb_file) { 2 | table_name <- "telehealth_by_taxonomy" 3 | 4 | con <- DBI::dbConnect(duckdb::duckdb(), dbdir = duckdb_file) 5 | on.exit(DBI::dbDisconnect(con)) 6 | 7 | df <- dplyr::tbl(con, "medi_cal_managed_care_providers") |> 8 | dplyr::filter( 9 | telehealth %in% c("B", "O"), 10 | !mcna_provider_type %in% c("Other") 11 | ) |> 12 | dplyr::group_by( 13 | taxonomy, 14 | mcna_provider_type 15 | ) |> 16 | dplyr::count(name = "n_telehealth") |> 17 | dplyr::arrange(desc(n_telehealth)) |> 18 | dplyr::collect() 19 | 20 | DBI::dbWriteTable(con, table_name, df, overwrite = TRUE) 21 | 22 | hash <- dplyr::tbl(con, table_name) |> 23 | dplyr::collect() |> 24 | digest::digest() 25 | 26 | return(hash) 27 | } 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Portable Data pipelines with Docker, DuckDB, and R 2 | 3 | This project showcases the integration of Docker, DuckDB, and R to create efficient and portable data pipelines. By using Docker, we ensure a consistent environment across different machines, while DuckDB provides fast, in-process SQL analytics. The R programming language, along with the {duckdb}, {dbplyr}, and {targets} packages, is used to orchestrate and run the data processing tasks. The {renv} package is used alongside Docker to manage R package dependencies, ensuring reproducibility. 4 | 5 | ## Getting Started 6 | 7 | To get started with this project, clone the repository and navigate to the directory: 8 | 9 | ```bash 10 | git clone https://github.com/philiporlando/docker-duckdb-r.git 11 | cd docker-duckdb-r 12 | ``` 13 | 14 | ## Building the Docker Image 15 | 16 | Build the Docker image using the following command. This will set up the necessary R environment, install all dependencies, and prepare the DuckDB database for use. The initial build may take a few minutes to complete. 17 | 18 | ```bash 19 | docker build . 20 | ``` 21 | 22 | ## Running the Container 23 | 24 | To run the Docker container and launch the `{targets}` pipeline use: 25 | 26 | ```bash 27 | docker run 28 | ``` 29 | 30 | ## Repository Structure 31 | 32 | - `Dockerfile`: Defines the Docker image and specifies how the R environment is built. 33 | - `R/`: Contains R scripts with function definitions used by `{targets}`. 34 | - `_targets.R`: The target script file that defines the pipeline. See [The {targets} R package user manual](https://books.ropensci.org/targets/) for more details. 35 | - `data/`: Any source data and the DuckDB database file are stored here. 36 | - `tests/`: TBD test suite built around `{testthat}`. 37 | -------------------------------------------------------------------------------- /_targets.R: -------------------------------------------------------------------------------- 1 | library(targets) 2 | library(qs) 3 | 4 | 5 | targets::tar_option_set( 6 | packages = c("tibble"), 7 | format = "qs" 8 | ) 9 | 10 | targets::tar_source() 11 | 12 | list( 13 | tar_target( 14 | name = data_url, 15 | command = get_data_url() 16 | ), 17 | tar_target( 18 | name = url_modified_time, 19 | command = get_url_modified_time(data_url) 20 | ), 21 | tar_target( 22 | name = raw_data_file, 23 | command = download_data(data_url, url_modified_time), 24 | format = "file", 25 | ), 26 | tar_target( 27 | name = duckdb_file, 28 | command = setup_duckdb() 29 | ), 30 | tar_target( 31 | name = ingested_data, 32 | command = ingest_data( 33 | raw_data_file, 34 | duckdb_file 35 | ) 36 | ), 37 | tar_target( 38 | name = transformed_data, 39 | command = transform_data( 40 | ingested_data, 41 | duckdb_file 42 | ) 43 | ) 44 | ) 45 | -------------------------------------------------------------------------------- /renv.lock: -------------------------------------------------------------------------------- 1 | { 2 | "R": { 3 | "Version": "4.4.1", 4 | "Repositories": [ 5 | { 6 | "Name": "CRAN", 7 | "URL": "https://cloud.r-project.org" 8 | } 9 | ] 10 | }, 11 | "Packages": { 12 | "BH": { 13 | "Package": "BH", 14 | "Version": "1.84.0-0", 15 | "Source": "Repository", 16 | "Repository": "CRAN", 17 | "Hash": "a8235afbcd6316e6e91433ea47661013" 18 | }, 19 | "DBI": { 20 | "Package": "DBI", 21 | "Version": "1.2.3", 22 | "Source": "Repository", 23 | "Repository": "CRAN", 24 | "Requirements": [ 25 | "R", 26 | "methods" 27 | ], 28 | "Hash": "065ae649b05f1ff66bb0c793107508f5" 29 | }, 30 | "Matrix": { 31 | "Package": "Matrix", 32 | "Version": "1.7-0", 33 | "Source": "Repository", 34 | "Repository": "CRAN", 35 | "Requirements": [ 36 | "R", 37 | "grDevices", 38 | "graphics", 39 | "grid", 40 | "lattice", 41 | "methods", 42 | "stats", 43 | "utils" 44 | ], 45 | "Hash": "1920b2f11133b12350024297d8a4ff4a" 46 | }, 47 | "R6": { 48 | "Package": "R6", 49 | "Version": "2.5.1", 50 | "Source": "Repository", 51 | "Repository": "RSPM", 52 | "Requirements": [ 53 | "R" 54 | ], 55 | "Hash": "470851b6d5d0ac559e9d01bb352b4021" 56 | }, 57 | "RApiSerialize": { 58 | "Package": "RApiSerialize", 59 | "Version": "0.1.3", 60 | "Source": "Repository", 61 | "Repository": "CRAN", 62 | "Hash": "12cbd319d03b55edaa195695a155bb30" 63 | }, 64 | "Rcpp": { 65 | "Package": "Rcpp", 66 | "Version": "1.0.13", 67 | "Source": "Repository", 68 | "Repository": "CRAN", 69 | "Requirements": [ 70 | "methods", 71 | "utils" 72 | ], 73 | "Hash": "f27411eb6d9c3dada5edd444b8416675" 74 | }, 75 | "RcppParallel": { 76 | "Package": "RcppParallel", 77 | "Version": "5.1.8", 78 | "Source": "Repository", 79 | "Repository": "CRAN", 80 | "Requirements": [ 81 | "R" 82 | ], 83 | "Hash": "21c1466a17de6b1f5f4bc0569868775e" 84 | }, 85 | "askpass": { 86 | "Package": "askpass", 87 | "Version": "1.2.0", 88 | "Source": "Repository", 89 | "Repository": "RSPM", 90 | "Requirements": [ 91 | "sys" 92 | ], 93 | "Hash": "cad6cf7f1d5f6e906700b9d3e718c796" 94 | }, 95 | "backports": { 96 | "Package": "backports", 97 | "Version": "1.5.0", 98 | "Source": "Repository", 99 | "Repository": "CRAN", 100 | "Requirements": [ 101 | "R" 102 | ], 103 | "Hash": "e1e1b9d75c37401117b636b7ae50827a" 104 | }, 105 | "base64url": { 106 | "Package": "base64url", 107 | "Version": "1.4", 108 | "Source": "Repository", 109 | "Repository": "CRAN", 110 | "Requirements": [ 111 | "backports" 112 | ], 113 | "Hash": "0c54cf3a08cc0e550fbd64ad33166143" 114 | }, 115 | "bit": { 116 | "Package": "bit", 117 | "Version": "4.0.5", 118 | "Source": "Repository", 119 | "Repository": "CRAN", 120 | "Requirements": [ 121 | "R" 122 | ], 123 | "Hash": "d242abec29412ce988848d0294b208fd" 124 | }, 125 | "bit64": { 126 | "Package": "bit64", 127 | "Version": "4.0.5", 128 | "Source": "Repository", 129 | "Repository": "CRAN", 130 | "Requirements": [ 131 | "R", 132 | "bit", 133 | "methods", 134 | "stats", 135 | "utils" 136 | ], 137 | "Hash": "9fe98599ca456d6552421db0d6772d8f" 138 | }, 139 | "brio": { 140 | "Package": "brio", 141 | "Version": "1.1.5", 142 | "Source": "Repository", 143 | "Repository": "CRAN", 144 | "Requirements": [ 145 | "R" 146 | ], 147 | "Hash": "c1ee497a6d999947c2c224ae46799b1a" 148 | }, 149 | "callr": { 150 | "Package": "callr", 151 | "Version": "3.7.6", 152 | "Source": "Repository", 153 | "Repository": "CRAN", 154 | "Requirements": [ 155 | "R", 156 | "R6", 157 | "processx", 158 | "utils" 159 | ], 160 | "Hash": "d7e13f49c19103ece9e58ad2d83a7354" 161 | }, 162 | "cli": { 163 | "Package": "cli", 164 | "Version": "3.6.3", 165 | "Source": "Repository", 166 | "Repository": "CRAN", 167 | "Requirements": [ 168 | "R", 169 | "utils" 170 | ], 171 | "Hash": "b21916dd77a27642b447374a5d30ecf3" 172 | }, 173 | "clipr": { 174 | "Package": "clipr", 175 | "Version": "0.8.0", 176 | "Source": "Repository", 177 | "Repository": "CRAN", 178 | "Requirements": [ 179 | "utils" 180 | ], 181 | "Hash": "3f038e5ac7f41d4ac41ce658c85e3042" 182 | }, 183 | "codetools": { 184 | "Package": "codetools", 185 | "Version": "0.2-20", 186 | "Source": "Repository", 187 | "Repository": "CRAN", 188 | "Requirements": [ 189 | "R" 190 | ], 191 | "Hash": "61e097f35917d342622f21cdc79c256e" 192 | }, 193 | "cpp11": { 194 | "Package": "cpp11", 195 | "Version": "0.4.7", 196 | "Source": "Repository", 197 | "Repository": "RSPM", 198 | "Requirements": [ 199 | "R" 200 | ], 201 | "Hash": "5a295d7d963cc5035284dcdbaf334f4e" 202 | }, 203 | "crayon": { 204 | "Package": "crayon", 205 | "Version": "1.5.3", 206 | "Source": "Repository", 207 | "Repository": "CRAN", 208 | "Requirements": [ 209 | "grDevices", 210 | "methods", 211 | "utils" 212 | ], 213 | "Hash": "859d96e65ef198fd43e82b9628d593ef" 214 | }, 215 | "curl": { 216 | "Package": "curl", 217 | "Version": "5.2.1", 218 | "Source": "Repository", 219 | "Repository": "RSPM", 220 | "Requirements": [ 221 | "R" 222 | ], 223 | "Hash": "411ca2c03b1ce5f548345d2fc2685f7a" 224 | }, 225 | "data.table": { 226 | "Package": "data.table", 227 | "Version": "1.15.4", 228 | "Source": "Repository", 229 | "Repository": "CRAN", 230 | "Requirements": [ 231 | "R", 232 | "methods" 233 | ], 234 | "Hash": "8ee9ac56ef633d0c7cab8b2ca87d683e" 235 | }, 236 | "desc": { 237 | "Package": "desc", 238 | "Version": "1.4.3", 239 | "Source": "Repository", 240 | "Repository": "CRAN", 241 | "Requirements": [ 242 | "R", 243 | "R6", 244 | "cli", 245 | "utils" 246 | ], 247 | "Hash": "99b79fcbd6c4d1ce087f5c5c758b384f" 248 | }, 249 | "diffobj": { 250 | "Package": "diffobj", 251 | "Version": "0.3.5", 252 | "Source": "Repository", 253 | "Repository": "CRAN", 254 | "Requirements": [ 255 | "R", 256 | "crayon", 257 | "methods", 258 | "stats", 259 | "tools", 260 | "utils" 261 | ], 262 | "Hash": "bcaa8b95f8d7d01a5dedfd959ce88ab8" 263 | }, 264 | "digest": { 265 | "Package": "digest", 266 | "Version": "0.6.36", 267 | "Source": "Repository", 268 | "Repository": "CRAN", 269 | "Requirements": [ 270 | "R", 271 | "utils" 272 | ], 273 | "Hash": "fd6824ad91ede64151e93af67df6376b" 274 | }, 275 | "dplyr": { 276 | "Package": "dplyr", 277 | "Version": "1.1.4", 278 | "Source": "Repository", 279 | "Repository": "CRAN", 280 | "Requirements": [ 281 | "R", 282 | "R6", 283 | "cli", 284 | "generics", 285 | "glue", 286 | "lifecycle", 287 | "magrittr", 288 | "methods", 289 | "pillar", 290 | "rlang", 291 | "tibble", 292 | "tidyselect", 293 | "utils", 294 | "vctrs" 295 | ], 296 | "Hash": "fedd9d00c2944ff00a0e2696ccf048ec" 297 | }, 298 | "duckdb": { 299 | "Package": "duckdb", 300 | "Version": "1.0.0-2", 301 | "Source": "Repository", 302 | "Repository": "CRAN", 303 | "Requirements": [ 304 | "DBI", 305 | "R", 306 | "methods", 307 | "utils" 308 | ], 309 | "Hash": "c68785a280aa69dbe449b3cf98fa3dd1" 310 | }, 311 | "evaluate": { 312 | "Package": "evaluate", 313 | "Version": "0.24.0", 314 | "Source": "Repository", 315 | "Repository": "CRAN", 316 | "Requirements": [ 317 | "R", 318 | "methods" 319 | ], 320 | "Hash": "a1066cbc05caee9a4bf6d90f194ff4da" 321 | }, 322 | "fansi": { 323 | "Package": "fansi", 324 | "Version": "1.0.6", 325 | "Source": "Repository", 326 | "Repository": "RSPM", 327 | "Requirements": [ 328 | "R", 329 | "grDevices", 330 | "utils" 331 | ], 332 | "Hash": "962174cf2aeb5b9eea581522286a911f" 333 | }, 334 | "fs": { 335 | "Package": "fs", 336 | "Version": "1.6.4", 337 | "Source": "Repository", 338 | "Repository": "CRAN", 339 | "Requirements": [ 340 | "R", 341 | "methods" 342 | ], 343 | "Hash": "15aeb8c27f5ea5161f9f6a641fafd93a" 344 | }, 345 | "generics": { 346 | "Package": "generics", 347 | "Version": "0.1.3", 348 | "Source": "Repository", 349 | "Repository": "CRAN", 350 | "Requirements": [ 351 | "R", 352 | "methods" 353 | ], 354 | "Hash": "15e9634c0fcd294799e9b2e929ed1b86" 355 | }, 356 | "glue": { 357 | "Package": "glue", 358 | "Version": "1.7.0", 359 | "Source": "Repository", 360 | "Repository": "RSPM", 361 | "Requirements": [ 362 | "R", 363 | "methods" 364 | ], 365 | "Hash": "e0b3a53876554bd45879e596cdb10a52" 366 | }, 367 | "highr": { 368 | "Package": "highr", 369 | "Version": "0.11", 370 | "Source": "Repository", 371 | "Repository": "CRAN", 372 | "Requirements": [ 373 | "R", 374 | "xfun" 375 | ], 376 | "Hash": "d65ba49117ca223614f71b60d85b8ab7" 377 | }, 378 | "hms": { 379 | "Package": "hms", 380 | "Version": "1.1.3", 381 | "Source": "Repository", 382 | "Repository": "RSPM", 383 | "Requirements": [ 384 | "lifecycle", 385 | "methods", 386 | "pkgconfig", 387 | "rlang", 388 | "vctrs" 389 | ], 390 | "Hash": "b59377caa7ed00fa41808342002138f9" 391 | }, 392 | "httr2": { 393 | "Package": "httr2", 394 | "Version": "1.0.2", 395 | "Source": "Repository", 396 | "Repository": "CRAN", 397 | "Requirements": [ 398 | "R", 399 | "R6", 400 | "cli", 401 | "curl", 402 | "glue", 403 | "lifecycle", 404 | "magrittr", 405 | "openssl", 406 | "rappdirs", 407 | "rlang", 408 | "vctrs", 409 | "withr" 410 | ], 411 | "Hash": "320c8fe23fcb25a6690ef7bdb6a3a705" 412 | }, 413 | "igraph": { 414 | "Package": "igraph", 415 | "Version": "2.0.3", 416 | "Source": "Repository", 417 | "Repository": "CRAN", 418 | "Requirements": [ 419 | "Matrix", 420 | "R", 421 | "cli", 422 | "cpp11", 423 | "grDevices", 424 | "graphics", 425 | "lifecycle", 426 | "magrittr", 427 | "methods", 428 | "pkgconfig", 429 | "rlang", 430 | "stats", 431 | "utils", 432 | "vctrs" 433 | ], 434 | "Hash": "c3b7d801d722e26e4cd888e042bf9af5" 435 | }, 436 | "janitor": { 437 | "Package": "janitor", 438 | "Version": "2.2.0", 439 | "Source": "Repository", 440 | "Repository": "RSPM", 441 | "Requirements": [ 442 | "R", 443 | "dplyr", 444 | "hms", 445 | "lifecycle", 446 | "lubridate", 447 | "magrittr", 448 | "purrr", 449 | "rlang", 450 | "snakecase", 451 | "stringi", 452 | "stringr", 453 | "tidyr", 454 | "tidyselect" 455 | ], 456 | "Hash": "5baae149f1082f466df9d1442ba7aa65" 457 | }, 458 | "jsonlite": { 459 | "Package": "jsonlite", 460 | "Version": "1.8.8", 461 | "Source": "Repository", 462 | "Repository": "RSPM", 463 | "Requirements": [ 464 | "methods" 465 | ], 466 | "Hash": "e1b9c55281c5adc4dd113652d9e26768" 467 | }, 468 | "knitr": { 469 | "Package": "knitr", 470 | "Version": "1.48", 471 | "Source": "Repository", 472 | "Repository": "CRAN", 473 | "Requirements": [ 474 | "R", 475 | "evaluate", 476 | "highr", 477 | "methods", 478 | "tools", 479 | "xfun", 480 | "yaml" 481 | ], 482 | "Hash": "acf380f300c721da9fde7df115a5f86f" 483 | }, 484 | "lattice": { 485 | "Package": "lattice", 486 | "Version": "0.22-6", 487 | "Source": "Repository", 488 | "Repository": "CRAN", 489 | "Requirements": [ 490 | "R", 491 | "grDevices", 492 | "graphics", 493 | "grid", 494 | "stats", 495 | "utils" 496 | ], 497 | "Hash": "cc5ac1ba4c238c7ca9fa6a87ca11a7e2" 498 | }, 499 | "lifecycle": { 500 | "Package": "lifecycle", 501 | "Version": "1.0.4", 502 | "Source": "Repository", 503 | "Repository": "RSPM", 504 | "Requirements": [ 505 | "R", 506 | "cli", 507 | "glue", 508 | "rlang" 509 | ], 510 | "Hash": "b8552d117e1b808b09a832f589b79035" 511 | }, 512 | "lubridate": { 513 | "Package": "lubridate", 514 | "Version": "1.9.3", 515 | "Source": "Repository", 516 | "Repository": "RSPM", 517 | "Requirements": [ 518 | "R", 519 | "generics", 520 | "methods", 521 | "timechange" 522 | ], 523 | "Hash": "680ad542fbcf801442c83a6ac5a2126c" 524 | }, 525 | "magrittr": { 526 | "Package": "magrittr", 527 | "Version": "2.0.3", 528 | "Source": "Repository", 529 | "Repository": "RSPM", 530 | "Requirements": [ 531 | "R" 532 | ], 533 | "Hash": "7ce2733a9826b3aeb1775d56fd305472" 534 | }, 535 | "openssl": { 536 | "Package": "openssl", 537 | "Version": "2.2.0", 538 | "Source": "Repository", 539 | "Repository": "RSPM", 540 | "Requirements": [ 541 | "askpass" 542 | ], 543 | "Hash": "2bcca3848e4734eb3b16103bc9aa4b8e" 544 | }, 545 | "pillar": { 546 | "Package": "pillar", 547 | "Version": "1.9.0", 548 | "Source": "Repository", 549 | "Repository": "RSPM", 550 | "Requirements": [ 551 | "cli", 552 | "fansi", 553 | "glue", 554 | "lifecycle", 555 | "rlang", 556 | "utf8", 557 | "utils", 558 | "vctrs" 559 | ], 560 | "Hash": "15da5a8412f317beeee6175fbc76f4bb" 561 | }, 562 | "pkgbuild": { 563 | "Package": "pkgbuild", 564 | "Version": "1.4.4", 565 | "Source": "Repository", 566 | "Repository": "CRAN", 567 | "Requirements": [ 568 | "R", 569 | "R6", 570 | "callr", 571 | "cli", 572 | "desc", 573 | "processx" 574 | ], 575 | "Hash": "a29e8e134a460a01e0ca67a4763c595b" 576 | }, 577 | "pkgconfig": { 578 | "Package": "pkgconfig", 579 | "Version": "2.0.3", 580 | "Source": "Repository", 581 | "Repository": "RSPM", 582 | "Requirements": [ 583 | "utils" 584 | ], 585 | "Hash": "01f28d4278f15c76cddbea05899c5d6f" 586 | }, 587 | "pkgload": { 588 | "Package": "pkgload", 589 | "Version": "1.4.0", 590 | "Source": "Repository", 591 | "Repository": "CRAN", 592 | "Requirements": [ 593 | "R", 594 | "cli", 595 | "desc", 596 | "fs", 597 | "glue", 598 | "lifecycle", 599 | "methods", 600 | "pkgbuild", 601 | "processx", 602 | "rlang", 603 | "rprojroot", 604 | "utils", 605 | "withr" 606 | ], 607 | "Hash": "2ec30ffbeec83da57655b850cf2d3e0e" 608 | }, 609 | "praise": { 610 | "Package": "praise", 611 | "Version": "1.0.0", 612 | "Source": "Repository", 613 | "Repository": "CRAN", 614 | "Hash": "a555924add98c99d2f411e37e7d25e9f" 615 | }, 616 | "prettyunits": { 617 | "Package": "prettyunits", 618 | "Version": "1.2.0", 619 | "Source": "Repository", 620 | "Repository": "RSPM", 621 | "Requirements": [ 622 | "R" 623 | ], 624 | "Hash": "6b01fc98b1e86c4f705ce9dcfd2f57c7" 625 | }, 626 | "processx": { 627 | "Package": "processx", 628 | "Version": "3.8.4", 629 | "Source": "Repository", 630 | "Repository": "CRAN", 631 | "Requirements": [ 632 | "R", 633 | "R6", 634 | "ps", 635 | "utils" 636 | ], 637 | "Hash": "0c90a7d71988856bad2a2a45dd871bb9" 638 | }, 639 | "progress": { 640 | "Package": "progress", 641 | "Version": "1.2.3", 642 | "Source": "Repository", 643 | "Repository": "RSPM", 644 | "Requirements": [ 645 | "R", 646 | "R6", 647 | "crayon", 648 | "hms", 649 | "prettyunits" 650 | ], 651 | "Hash": "f4625e061cb2865f111b47ff163a5ca6" 652 | }, 653 | "ps": { 654 | "Package": "ps", 655 | "Version": "1.7.7", 656 | "Source": "Repository", 657 | "Repository": "CRAN", 658 | "Requirements": [ 659 | "R", 660 | "utils" 661 | ], 662 | "Hash": "878b467580097e9c383acbb16adab57a" 663 | }, 664 | "purrr": { 665 | "Package": "purrr", 666 | "Version": "1.0.2", 667 | "Source": "Repository", 668 | "Repository": "RSPM", 669 | "Requirements": [ 670 | "R", 671 | "cli", 672 | "lifecycle", 673 | "magrittr", 674 | "rlang", 675 | "vctrs" 676 | ], 677 | "Hash": "1cba04a4e9414bdefc9dcaa99649a8dc" 678 | }, 679 | "qs": { 680 | "Package": "qs", 681 | "Version": "0.26.3", 682 | "Source": "Repository", 683 | "Repository": "CRAN", 684 | "Requirements": [ 685 | "BH", 686 | "R", 687 | "RApiSerialize", 688 | "Rcpp", 689 | "stringfish" 690 | ], 691 | "Hash": "e372cf3f70e3c7a768ec5ea14f0f3edd" 692 | }, 693 | "rappdirs": { 694 | "Package": "rappdirs", 695 | "Version": "0.3.3", 696 | "Source": "Repository", 697 | "Repository": "CRAN", 698 | "Requirements": [ 699 | "R" 700 | ], 701 | "Hash": "5e3c5dc0b071b21fa128676560dbe94d" 702 | }, 703 | "readr": { 704 | "Package": "readr", 705 | "Version": "2.1.5", 706 | "Source": "Repository", 707 | "Repository": "CRAN", 708 | "Requirements": [ 709 | "R", 710 | "R6", 711 | "cli", 712 | "clipr", 713 | "cpp11", 714 | "crayon", 715 | "hms", 716 | "lifecycle", 717 | "methods", 718 | "rlang", 719 | "tibble", 720 | "tzdb", 721 | "utils", 722 | "vroom" 723 | ], 724 | "Hash": "9de96463d2117f6ac49980577939dfb3" 725 | }, 726 | "rematch2": { 727 | "Package": "rematch2", 728 | "Version": "2.1.2", 729 | "Source": "Repository", 730 | "Repository": "CRAN", 731 | "Requirements": [ 732 | "tibble" 733 | ], 734 | "Hash": "76c9e04c712a05848ae7a23d2f170a40" 735 | }, 736 | "renv": { 737 | "Package": "renv", 738 | "Version": "1.0.7", 739 | "Source": "Repository", 740 | "Repository": "CRAN", 741 | "Requirements": [ 742 | "utils" 743 | ], 744 | "Hash": "397b7b2a265bc5a7a06852524dabae20" 745 | }, 746 | "rlang": { 747 | "Package": "rlang", 748 | "Version": "1.1.4", 749 | "Source": "Repository", 750 | "Repository": "RSPM", 751 | "Requirements": [ 752 | "R", 753 | "utils" 754 | ], 755 | "Hash": "3eec01f8b1dee337674b2e34ab1f9bc1" 756 | }, 757 | "rprojroot": { 758 | "Package": "rprojroot", 759 | "Version": "2.0.4", 760 | "Source": "Repository", 761 | "Repository": "CRAN", 762 | "Requirements": [ 763 | "R" 764 | ], 765 | "Hash": "4c8415e0ec1e29f3f4f6fc108bef0144" 766 | }, 767 | "secretbase": { 768 | "Package": "secretbase", 769 | "Version": "1.0.1", 770 | "Source": "Repository", 771 | "Repository": "CRAN", 772 | "Requirements": [ 773 | "R" 774 | ], 775 | "Hash": "28495386ab3f1e83c237c6d519508fe7" 776 | }, 777 | "snakecase": { 778 | "Package": "snakecase", 779 | "Version": "0.11.1", 780 | "Source": "Repository", 781 | "Repository": "RSPM", 782 | "Requirements": [ 783 | "R", 784 | "stringi", 785 | "stringr" 786 | ], 787 | "Hash": "58767e44739b76965332e8a4fe3f91f1" 788 | }, 789 | "stringfish": { 790 | "Package": "stringfish", 791 | "Version": "0.16.0", 792 | "Source": "Repository", 793 | "Repository": "CRAN", 794 | "Requirements": [ 795 | "R", 796 | "Rcpp", 797 | "RcppParallel" 798 | ], 799 | "Hash": "b7eb79470319ae71d4b5ed9cd7bf7294" 800 | }, 801 | "stringi": { 802 | "Package": "stringi", 803 | "Version": "1.8.4", 804 | "Source": "Repository", 805 | "Repository": "RSPM", 806 | "Requirements": [ 807 | "R", 808 | "stats", 809 | "tools", 810 | "utils" 811 | ], 812 | "Hash": "39e1144fd75428983dc3f63aa53dfa91" 813 | }, 814 | "stringr": { 815 | "Package": "stringr", 816 | "Version": "1.5.1", 817 | "Source": "Repository", 818 | "Repository": "RSPM", 819 | "Requirements": [ 820 | "R", 821 | "cli", 822 | "glue", 823 | "lifecycle", 824 | "magrittr", 825 | "rlang", 826 | "stringi", 827 | "vctrs" 828 | ], 829 | "Hash": "960e2ae9e09656611e0b8214ad543207" 830 | }, 831 | "sys": { 832 | "Package": "sys", 833 | "Version": "3.4.2", 834 | "Source": "Repository", 835 | "Repository": "RSPM", 836 | "Hash": "3a1be13d68d47a8cd0bfd74739ca1555" 837 | }, 838 | "targets": { 839 | "Package": "targets", 840 | "Version": "1.7.1", 841 | "Source": "Repository", 842 | "Repository": "CRAN", 843 | "Requirements": [ 844 | "R", 845 | "R6", 846 | "base64url", 847 | "callr", 848 | "cli", 849 | "codetools", 850 | "data.table", 851 | "igraph", 852 | "knitr", 853 | "ps", 854 | "rlang", 855 | "secretbase", 856 | "stats", 857 | "tibble", 858 | "tidyselect", 859 | "tools", 860 | "utils", 861 | "vctrs", 862 | "yaml" 863 | ], 864 | "Hash": "d79b21ef513564351577d7064e1fc4dd" 865 | }, 866 | "testthat": { 867 | "Package": "testthat", 868 | "Version": "3.2.1.1", 869 | "Source": "Repository", 870 | "Repository": "CRAN", 871 | "Requirements": [ 872 | "R", 873 | "R6", 874 | "brio", 875 | "callr", 876 | "cli", 877 | "desc", 878 | "digest", 879 | "evaluate", 880 | "jsonlite", 881 | "lifecycle", 882 | "magrittr", 883 | "methods", 884 | "pkgload", 885 | "praise", 886 | "processx", 887 | "ps", 888 | "rlang", 889 | "utils", 890 | "waldo", 891 | "withr" 892 | ], 893 | "Hash": "3f6e7e5e2220856ff865e4834766bf2b" 894 | }, 895 | "tibble": { 896 | "Package": "tibble", 897 | "Version": "3.2.1", 898 | "Source": "Repository", 899 | "Repository": "RSPM", 900 | "Requirements": [ 901 | "R", 902 | "fansi", 903 | "lifecycle", 904 | "magrittr", 905 | "methods", 906 | "pillar", 907 | "pkgconfig", 908 | "rlang", 909 | "utils", 910 | "vctrs" 911 | ], 912 | "Hash": "a84e2cc86d07289b3b6f5069df7a004c" 913 | }, 914 | "tidyr": { 915 | "Package": "tidyr", 916 | "Version": "1.3.1", 917 | "Source": "Repository", 918 | "Repository": "RSPM", 919 | "Requirements": [ 920 | "R", 921 | "cli", 922 | "cpp11", 923 | "dplyr", 924 | "glue", 925 | "lifecycle", 926 | "magrittr", 927 | "purrr", 928 | "rlang", 929 | "stringr", 930 | "tibble", 931 | "tidyselect", 932 | "utils", 933 | "vctrs" 934 | ], 935 | "Hash": "915fb7ce036c22a6a33b5a8adb712eb1" 936 | }, 937 | "tidyselect": { 938 | "Package": "tidyselect", 939 | "Version": "1.2.1", 940 | "Source": "Repository", 941 | "Repository": "RSPM", 942 | "Requirements": [ 943 | "R", 944 | "cli", 945 | "glue", 946 | "lifecycle", 947 | "rlang", 948 | "vctrs", 949 | "withr" 950 | ], 951 | "Hash": "829f27b9c4919c16b593794a6344d6c0" 952 | }, 953 | "timechange": { 954 | "Package": "timechange", 955 | "Version": "0.3.0", 956 | "Source": "Repository", 957 | "Repository": "RSPM", 958 | "Requirements": [ 959 | "R", 960 | "cpp11" 961 | ], 962 | "Hash": "c5f3c201b931cd6474d17d8700ccb1c8" 963 | }, 964 | "tzdb": { 965 | "Package": "tzdb", 966 | "Version": "0.4.0", 967 | "Source": "Repository", 968 | "Repository": "CRAN", 969 | "Requirements": [ 970 | "R", 971 | "cpp11" 972 | ], 973 | "Hash": "f561504ec2897f4d46f0c7657e488ae1" 974 | }, 975 | "utf8": { 976 | "Package": "utf8", 977 | "Version": "1.2.4", 978 | "Source": "Repository", 979 | "Repository": "RSPM", 980 | "Requirements": [ 981 | "R" 982 | ], 983 | "Hash": "62b65c52671e6665f803ff02954446e9" 984 | }, 985 | "vctrs": { 986 | "Package": "vctrs", 987 | "Version": "0.6.5", 988 | "Source": "Repository", 989 | "Repository": "RSPM", 990 | "Requirements": [ 991 | "R", 992 | "cli", 993 | "glue", 994 | "lifecycle", 995 | "rlang" 996 | ], 997 | "Hash": "c03fa420630029418f7e6da3667aac4a" 998 | }, 999 | "vroom": { 1000 | "Package": "vroom", 1001 | "Version": "1.6.5", 1002 | "Source": "Repository", 1003 | "Repository": "CRAN", 1004 | "Requirements": [ 1005 | "R", 1006 | "bit64", 1007 | "cli", 1008 | "cpp11", 1009 | "crayon", 1010 | "glue", 1011 | "hms", 1012 | "lifecycle", 1013 | "methods", 1014 | "progress", 1015 | "rlang", 1016 | "stats", 1017 | "tibble", 1018 | "tidyselect", 1019 | "tzdb", 1020 | "vctrs", 1021 | "withr" 1022 | ], 1023 | "Hash": "390f9315bc0025be03012054103d227c" 1024 | }, 1025 | "waldo": { 1026 | "Package": "waldo", 1027 | "Version": "0.5.2", 1028 | "Source": "Repository", 1029 | "Repository": "CRAN", 1030 | "Requirements": [ 1031 | "R", 1032 | "cli", 1033 | "diffobj", 1034 | "fansi", 1035 | "glue", 1036 | "methods", 1037 | "rematch2", 1038 | "rlang", 1039 | "tibble" 1040 | ], 1041 | "Hash": "c7d3fd6d29ab077cbac8f0e2751449e6" 1042 | }, 1043 | "withr": { 1044 | "Package": "withr", 1045 | "Version": "3.0.1", 1046 | "Source": "Repository", 1047 | "Repository": "CRAN", 1048 | "Requirements": [ 1049 | "R", 1050 | "grDevices", 1051 | "graphics" 1052 | ], 1053 | "Hash": "07909200e8bbe90426fbfeb73e1e27aa" 1054 | }, 1055 | "xfun": { 1056 | "Package": "xfun", 1057 | "Version": "0.46", 1058 | "Source": "Repository", 1059 | "Repository": "CRAN", 1060 | "Requirements": [ 1061 | "grDevices", 1062 | "stats", 1063 | "tools" 1064 | ], 1065 | "Hash": "00ce32f398db0415dde61abfef11300c" 1066 | }, 1067 | "yaml": { 1068 | "Package": "yaml", 1069 | "Version": "2.3.10", 1070 | "Source": "Repository", 1071 | "Repository": "CRAN", 1072 | "Hash": "51dab85c6c98e50a18d7551e9d49f76c" 1073 | } 1074 | } 1075 | } 1076 | -------------------------------------------------------------------------------- /renv/.gitignore: -------------------------------------------------------------------------------- 1 | library/ 2 | local/ 3 | cellar/ 4 | lock/ 5 | python/ 6 | sandbox/ 7 | staging/ 8 | -------------------------------------------------------------------------------- /renv/activate.R: -------------------------------------------------------------------------------- 1 | 2 | local({ 3 | 4 | # the requested version of renv 5 | version <- "1.0.7" 6 | attr(version, "sha") <- NULL 7 | 8 | # the project directory 9 | project <- Sys.getenv("RENV_PROJECT") 10 | if (!nzchar(project)) 11 | project <- getwd() 12 | 13 | # use start-up diagnostics if enabled 14 | diagnostics <- Sys.getenv("RENV_STARTUP_DIAGNOSTICS", unset = "FALSE") 15 | if (diagnostics) { 16 | start <- Sys.time() 17 | profile <- tempfile("renv-startup-", fileext = ".Rprof") 18 | utils::Rprof(profile) 19 | on.exit({ 20 | utils::Rprof(NULL) 21 | elapsed <- signif(difftime(Sys.time(), start, units = "auto"), digits = 2L) 22 | writeLines(sprintf("- renv took %s to run the autoloader.", format(elapsed))) 23 | writeLines(sprintf("- Profile: %s", profile)) 24 | print(utils::summaryRprof(profile)) 25 | }, add = TRUE) 26 | } 27 | 28 | # figure out whether the autoloader is enabled 29 | enabled <- local({ 30 | 31 | # first, check config option 32 | override <- getOption("renv.config.autoloader.enabled") 33 | if (!is.null(override)) 34 | return(override) 35 | 36 | # if we're being run in a context where R_LIBS is already set, 37 | # don't load -- presumably we're being run as a sub-process and 38 | # the parent process has already set up library paths for us 39 | rcmd <- Sys.getenv("R_CMD", unset = NA) 40 | rlibs <- Sys.getenv("R_LIBS", unset = NA) 41 | if (!is.na(rlibs) && !is.na(rcmd)) 42 | return(FALSE) 43 | 44 | # next, check environment variables 45 | # TODO: prefer using the configuration one in the future 46 | envvars <- c( 47 | "RENV_CONFIG_AUTOLOADER_ENABLED", 48 | "RENV_AUTOLOADER_ENABLED", 49 | "RENV_ACTIVATE_PROJECT" 50 | ) 51 | 52 | for (envvar in envvars) { 53 | envval <- Sys.getenv(envvar, unset = NA) 54 | if (!is.na(envval)) 55 | return(tolower(envval) %in% c("true", "t", "1")) 56 | } 57 | 58 | # enable by default 59 | TRUE 60 | 61 | }) 62 | 63 | # bail if we're not enabled 64 | if (!enabled) { 65 | 66 | # if we're not enabled, we might still need to manually load 67 | # the user profile here 68 | profile <- Sys.getenv("R_PROFILE_USER", unset = "~/.Rprofile") 69 | if (file.exists(profile)) { 70 | cfg <- Sys.getenv("RENV_CONFIG_USER_PROFILE", unset = "TRUE") 71 | if (tolower(cfg) %in% c("true", "t", "1")) 72 | sys.source(profile, envir = globalenv()) 73 | } 74 | 75 | return(FALSE) 76 | 77 | } 78 | 79 | # avoid recursion 80 | if (identical(getOption("renv.autoloader.running"), TRUE)) { 81 | warning("ignoring recursive attempt to run renv autoloader") 82 | return(invisible(TRUE)) 83 | } 84 | 85 | # signal that we're loading renv during R startup 86 | options(renv.autoloader.running = TRUE) 87 | on.exit(options(renv.autoloader.running = NULL), add = TRUE) 88 | 89 | # signal that we've consented to use renv 90 | options(renv.consent = TRUE) 91 | 92 | # load the 'utils' package eagerly -- this ensures that renv shims, which 93 | # mask 'utils' packages, will come first on the search path 94 | library(utils, lib.loc = .Library) 95 | 96 | # unload renv if it's already been loaded 97 | if ("renv" %in% loadedNamespaces()) 98 | unloadNamespace("renv") 99 | 100 | # load bootstrap tools 101 | `%||%` <- function(x, y) { 102 | if (is.null(x)) y else x 103 | } 104 | 105 | catf <- function(fmt, ..., appendLF = TRUE) { 106 | 107 | quiet <- getOption("renv.bootstrap.quiet", default = FALSE) 108 | if (quiet) 109 | return(invisible()) 110 | 111 | msg <- sprintf(fmt, ...) 112 | cat(msg, file = stdout(), sep = if (appendLF) "\n" else "") 113 | 114 | invisible(msg) 115 | 116 | } 117 | 118 | header <- function(label, 119 | ..., 120 | prefix = "#", 121 | suffix = "-", 122 | n = min(getOption("width"), 78)) 123 | { 124 | label <- sprintf(label, ...) 125 | n <- max(n - nchar(label) - nchar(prefix) - 2L, 8L) 126 | if (n <= 0) 127 | return(paste(prefix, label)) 128 | 129 | tail <- paste(rep.int(suffix, n), collapse = "") 130 | paste0(prefix, " ", label, " ", tail) 131 | 132 | } 133 | 134 | heredoc <- function(text, leave = 0) { 135 | 136 | # remove leading, trailing whitespace 137 | trimmed <- gsub("^\\s*\\n|\\n\\s*$", "", text) 138 | 139 | # split into lines 140 | lines <- strsplit(trimmed, "\n", fixed = TRUE)[[1L]] 141 | 142 | # compute common indent 143 | indent <- regexpr("[^[:space:]]", lines) 144 | common <- min(setdiff(indent, -1L)) - leave 145 | paste(substring(lines, common), collapse = "\n") 146 | 147 | } 148 | 149 | startswith <- function(string, prefix) { 150 | substring(string, 1, nchar(prefix)) == prefix 151 | } 152 | 153 | bootstrap <- function(version, library) { 154 | 155 | friendly <- renv_bootstrap_version_friendly(version) 156 | section <- header(sprintf("Bootstrapping renv %s", friendly)) 157 | catf(section) 158 | 159 | # attempt to download renv 160 | catf("- Downloading renv ... ", appendLF = FALSE) 161 | withCallingHandlers( 162 | tarball <- renv_bootstrap_download(version), 163 | error = function(err) { 164 | catf("FAILED") 165 | stop("failed to download:\n", conditionMessage(err)) 166 | } 167 | ) 168 | catf("OK") 169 | on.exit(unlink(tarball), add = TRUE) 170 | 171 | # now attempt to install 172 | catf("- Installing renv ... ", appendLF = FALSE) 173 | withCallingHandlers( 174 | status <- renv_bootstrap_install(version, tarball, library), 175 | error = function(err) { 176 | catf("FAILED") 177 | stop("failed to install:\n", conditionMessage(err)) 178 | } 179 | ) 180 | catf("OK") 181 | 182 | # add empty line to break up bootstrapping from normal output 183 | catf("") 184 | 185 | return(invisible()) 186 | } 187 | 188 | renv_bootstrap_tests_running <- function() { 189 | getOption("renv.tests.running", default = FALSE) 190 | } 191 | 192 | renv_bootstrap_repos <- function() { 193 | 194 | # get CRAN repository 195 | cran <- getOption("renv.repos.cran", "https://cloud.r-project.org") 196 | 197 | # check for repos override 198 | repos <- Sys.getenv("RENV_CONFIG_REPOS_OVERRIDE", unset = NA) 199 | if (!is.na(repos)) { 200 | 201 | # check for RSPM; if set, use a fallback repository for renv 202 | rspm <- Sys.getenv("RSPM", unset = NA) 203 | if (identical(rspm, repos)) 204 | repos <- c(RSPM = rspm, CRAN = cran) 205 | 206 | return(repos) 207 | 208 | } 209 | 210 | # check for lockfile repositories 211 | repos <- tryCatch(renv_bootstrap_repos_lockfile(), error = identity) 212 | if (!inherits(repos, "error") && length(repos)) 213 | return(repos) 214 | 215 | # retrieve current repos 216 | repos <- getOption("repos") 217 | 218 | # ensure @CRAN@ entries are resolved 219 | repos[repos == "@CRAN@"] <- cran 220 | 221 | # add in renv.bootstrap.repos if set 222 | default <- c(FALLBACK = "https://cloud.r-project.org") 223 | extra <- getOption("renv.bootstrap.repos", default = default) 224 | repos <- c(repos, extra) 225 | 226 | # remove duplicates that might've snuck in 227 | dupes <- duplicated(repos) | duplicated(names(repos)) 228 | repos[!dupes] 229 | 230 | } 231 | 232 | renv_bootstrap_repos_lockfile <- function() { 233 | 234 | lockpath <- Sys.getenv("RENV_PATHS_LOCKFILE", unset = "renv.lock") 235 | if (!file.exists(lockpath)) 236 | return(NULL) 237 | 238 | lockfile <- tryCatch(renv_json_read(lockpath), error = identity) 239 | if (inherits(lockfile, "error")) { 240 | warning(lockfile) 241 | return(NULL) 242 | } 243 | 244 | repos <- lockfile$R$Repositories 245 | if (length(repos) == 0) 246 | return(NULL) 247 | 248 | keys <- vapply(repos, `[[`, "Name", FUN.VALUE = character(1)) 249 | vals <- vapply(repos, `[[`, "URL", FUN.VALUE = character(1)) 250 | names(vals) <- keys 251 | 252 | return(vals) 253 | 254 | } 255 | 256 | renv_bootstrap_download <- function(version) { 257 | 258 | sha <- attr(version, "sha", exact = TRUE) 259 | 260 | methods <- if (!is.null(sha)) { 261 | 262 | # attempting to bootstrap a development version of renv 263 | c( 264 | function() renv_bootstrap_download_tarball(sha), 265 | function() renv_bootstrap_download_github(sha) 266 | ) 267 | 268 | } else { 269 | 270 | # attempting to bootstrap a release version of renv 271 | c( 272 | function() renv_bootstrap_download_tarball(version), 273 | function() renv_bootstrap_download_cran_latest(version), 274 | function() renv_bootstrap_download_cran_archive(version) 275 | ) 276 | 277 | } 278 | 279 | for (method in methods) { 280 | path <- tryCatch(method(), error = identity) 281 | if (is.character(path) && file.exists(path)) 282 | return(path) 283 | } 284 | 285 | stop("All download methods failed") 286 | 287 | } 288 | 289 | renv_bootstrap_download_impl <- function(url, destfile) { 290 | 291 | mode <- "wb" 292 | 293 | # https://bugs.r-project.org/bugzilla/show_bug.cgi?id=17715 294 | fixup <- 295 | Sys.info()[["sysname"]] == "Windows" && 296 | substring(url, 1L, 5L) == "file:" 297 | 298 | if (fixup) 299 | mode <- "w+b" 300 | 301 | args <- list( 302 | url = url, 303 | destfile = destfile, 304 | mode = mode, 305 | quiet = TRUE 306 | ) 307 | 308 | if ("headers" %in% names(formals(utils::download.file))) 309 | args$headers <- renv_bootstrap_download_custom_headers(url) 310 | 311 | do.call(utils::download.file, args) 312 | 313 | } 314 | 315 | renv_bootstrap_download_custom_headers <- function(url) { 316 | 317 | headers <- getOption("renv.download.headers") 318 | if (is.null(headers)) 319 | return(character()) 320 | 321 | if (!is.function(headers)) 322 | stopf("'renv.download.headers' is not a function") 323 | 324 | headers <- headers(url) 325 | if (length(headers) == 0L) 326 | return(character()) 327 | 328 | if (is.list(headers)) 329 | headers <- unlist(headers, recursive = FALSE, use.names = TRUE) 330 | 331 | ok <- 332 | is.character(headers) && 333 | is.character(names(headers)) && 334 | all(nzchar(names(headers))) 335 | 336 | if (!ok) 337 | stop("invocation of 'renv.download.headers' did not return a named character vector") 338 | 339 | headers 340 | 341 | } 342 | 343 | renv_bootstrap_download_cran_latest <- function(version) { 344 | 345 | spec <- renv_bootstrap_download_cran_latest_find(version) 346 | type <- spec$type 347 | repos <- spec$repos 348 | 349 | baseurl <- utils::contrib.url(repos = repos, type = type) 350 | ext <- if (identical(type, "source")) 351 | ".tar.gz" 352 | else if (Sys.info()[["sysname"]] == "Windows") 353 | ".zip" 354 | else 355 | ".tgz" 356 | name <- sprintf("renv_%s%s", version, ext) 357 | url <- paste(baseurl, name, sep = "/") 358 | 359 | destfile <- file.path(tempdir(), name) 360 | status <- tryCatch( 361 | renv_bootstrap_download_impl(url, destfile), 362 | condition = identity 363 | ) 364 | 365 | if (inherits(status, "condition")) 366 | return(FALSE) 367 | 368 | # report success and return 369 | destfile 370 | 371 | } 372 | 373 | renv_bootstrap_download_cran_latest_find <- function(version) { 374 | 375 | # check whether binaries are supported on this system 376 | binary <- 377 | getOption("renv.bootstrap.binary", default = TRUE) && 378 | !identical(.Platform$pkgType, "source") && 379 | !identical(getOption("pkgType"), "source") && 380 | Sys.info()[["sysname"]] %in% c("Darwin", "Windows") 381 | 382 | types <- c(if (binary) "binary", "source") 383 | 384 | # iterate over types + repositories 385 | for (type in types) { 386 | for (repos in renv_bootstrap_repos()) { 387 | 388 | # retrieve package database 389 | db <- tryCatch( 390 | as.data.frame( 391 | utils::available.packages(type = type, repos = repos), 392 | stringsAsFactors = FALSE 393 | ), 394 | error = identity 395 | ) 396 | 397 | if (inherits(db, "error")) 398 | next 399 | 400 | # check for compatible entry 401 | entry <- db[db$Package %in% "renv" & db$Version %in% version, ] 402 | if (nrow(entry) == 0) 403 | next 404 | 405 | # found it; return spec to caller 406 | spec <- list(entry = entry, type = type, repos = repos) 407 | return(spec) 408 | 409 | } 410 | } 411 | 412 | # if we got here, we failed to find renv 413 | fmt <- "renv %s is not available from your declared package repositories" 414 | stop(sprintf(fmt, version)) 415 | 416 | } 417 | 418 | renv_bootstrap_download_cran_archive <- function(version) { 419 | 420 | name <- sprintf("renv_%s.tar.gz", version) 421 | repos <- renv_bootstrap_repos() 422 | urls <- file.path(repos, "src/contrib/Archive/renv", name) 423 | destfile <- file.path(tempdir(), name) 424 | 425 | for (url in urls) { 426 | 427 | status <- tryCatch( 428 | renv_bootstrap_download_impl(url, destfile), 429 | condition = identity 430 | ) 431 | 432 | if (identical(status, 0L)) 433 | return(destfile) 434 | 435 | } 436 | 437 | return(FALSE) 438 | 439 | } 440 | 441 | renv_bootstrap_download_tarball <- function(version) { 442 | 443 | # if the user has provided the path to a tarball via 444 | # an environment variable, then use it 445 | tarball <- Sys.getenv("RENV_BOOTSTRAP_TARBALL", unset = NA) 446 | if (is.na(tarball)) 447 | return() 448 | 449 | # allow directories 450 | if (dir.exists(tarball)) { 451 | name <- sprintf("renv_%s.tar.gz", version) 452 | tarball <- file.path(tarball, name) 453 | } 454 | 455 | # bail if it doesn't exist 456 | if (!file.exists(tarball)) { 457 | 458 | # let the user know we weren't able to honour their request 459 | fmt <- "- RENV_BOOTSTRAP_TARBALL is set (%s) but does not exist." 460 | msg <- sprintf(fmt, tarball) 461 | warning(msg) 462 | 463 | # bail 464 | return() 465 | 466 | } 467 | 468 | catf("- Using local tarball '%s'.", tarball) 469 | tarball 470 | 471 | } 472 | 473 | renv_bootstrap_download_github <- function(version) { 474 | 475 | enabled <- Sys.getenv("RENV_BOOTSTRAP_FROM_GITHUB", unset = "TRUE") 476 | if (!identical(enabled, "TRUE")) 477 | return(FALSE) 478 | 479 | # prepare download options 480 | pat <- Sys.getenv("GITHUB_PAT") 481 | if (nzchar(Sys.which("curl")) && nzchar(pat)) { 482 | fmt <- "--location --fail --header \"Authorization: token %s\"" 483 | extra <- sprintf(fmt, pat) 484 | saved <- options("download.file.method", "download.file.extra") 485 | options(download.file.method = "curl", download.file.extra = extra) 486 | on.exit(do.call(base::options, saved), add = TRUE) 487 | } else if (nzchar(Sys.which("wget")) && nzchar(pat)) { 488 | fmt <- "--header=\"Authorization: token %s\"" 489 | extra <- sprintf(fmt, pat) 490 | saved <- options("download.file.method", "download.file.extra") 491 | options(download.file.method = "wget", download.file.extra = extra) 492 | on.exit(do.call(base::options, saved), add = TRUE) 493 | } 494 | 495 | url <- file.path("https://api.github.com/repos/rstudio/renv/tarball", version) 496 | name <- sprintf("renv_%s.tar.gz", version) 497 | destfile <- file.path(tempdir(), name) 498 | 499 | status <- tryCatch( 500 | renv_bootstrap_download_impl(url, destfile), 501 | condition = identity 502 | ) 503 | 504 | if (!identical(status, 0L)) 505 | return(FALSE) 506 | 507 | renv_bootstrap_download_augment(destfile) 508 | 509 | return(destfile) 510 | 511 | } 512 | 513 | # Add Sha to DESCRIPTION. This is stop gap until #890, after which we 514 | # can use renv::install() to fully capture metadata. 515 | renv_bootstrap_download_augment <- function(destfile) { 516 | sha <- renv_bootstrap_git_extract_sha1_tar(destfile) 517 | if (is.null(sha)) { 518 | return() 519 | } 520 | 521 | # Untar 522 | tempdir <- tempfile("renv-github-") 523 | on.exit(unlink(tempdir, recursive = TRUE), add = TRUE) 524 | untar(destfile, exdir = tempdir) 525 | pkgdir <- dir(tempdir, full.names = TRUE)[[1]] 526 | 527 | # Modify description 528 | desc_path <- file.path(pkgdir, "DESCRIPTION") 529 | desc_lines <- readLines(desc_path) 530 | remotes_fields <- c( 531 | "RemoteType: github", 532 | "RemoteHost: api.github.com", 533 | "RemoteRepo: renv", 534 | "RemoteUsername: rstudio", 535 | "RemotePkgRef: rstudio/renv", 536 | paste("RemoteRef: ", sha), 537 | paste("RemoteSha: ", sha) 538 | ) 539 | writeLines(c(desc_lines[desc_lines != ""], remotes_fields), con = desc_path) 540 | 541 | # Re-tar 542 | local({ 543 | old <- setwd(tempdir) 544 | on.exit(setwd(old), add = TRUE) 545 | 546 | tar(destfile, compression = "gzip") 547 | }) 548 | invisible() 549 | } 550 | 551 | # Extract the commit hash from a git archive. Git archives include the SHA1 552 | # hash as the comment field of the tarball pax extended header 553 | # (see https://www.kernel.org/pub/software/scm/git/docs/git-archive.html) 554 | # For GitHub archives this should be the first header after the default one 555 | # (512 byte) header. 556 | renv_bootstrap_git_extract_sha1_tar <- function(bundle) { 557 | 558 | # open the bundle for reading 559 | # We use gzcon for everything because (from ?gzcon) 560 | # > Reading from a connection which does not supply a 'gzip' magic 561 | # > header is equivalent to reading from the original connection 562 | conn <- gzcon(file(bundle, open = "rb", raw = TRUE)) 563 | on.exit(close(conn)) 564 | 565 | # The default pax header is 512 bytes long and the first pax extended header 566 | # with the comment should be 51 bytes long 567 | # `52 comment=` (11 chars) + 40 byte SHA1 hash 568 | len <- 0x200 + 0x33 569 | res <- rawToChar(readBin(conn, "raw", n = len)[0x201:len]) 570 | 571 | if (grepl("^52 comment=", res)) { 572 | sub("52 comment=", "", res) 573 | } else { 574 | NULL 575 | } 576 | } 577 | 578 | renv_bootstrap_install <- function(version, tarball, library) { 579 | 580 | # attempt to install it into project library 581 | dir.create(library, showWarnings = FALSE, recursive = TRUE) 582 | output <- renv_bootstrap_install_impl(library, tarball) 583 | 584 | # check for successful install 585 | status <- attr(output, "status") 586 | if (is.null(status) || identical(status, 0L)) 587 | return(status) 588 | 589 | # an error occurred; report it 590 | header <- "installation of renv failed" 591 | lines <- paste(rep.int("=", nchar(header)), collapse = "") 592 | text <- paste(c(header, lines, output), collapse = "\n") 593 | stop(text) 594 | 595 | } 596 | 597 | renv_bootstrap_install_impl <- function(library, tarball) { 598 | 599 | # invoke using system2 so we can capture and report output 600 | bin <- R.home("bin") 601 | exe <- if (Sys.info()[["sysname"]] == "Windows") "R.exe" else "R" 602 | R <- file.path(bin, exe) 603 | 604 | args <- c( 605 | "--vanilla", "CMD", "INSTALL", "--no-multiarch", 606 | "-l", shQuote(path.expand(library)), 607 | shQuote(path.expand(tarball)) 608 | ) 609 | 610 | system2(R, args, stdout = TRUE, stderr = TRUE) 611 | 612 | } 613 | 614 | renv_bootstrap_platform_prefix <- function() { 615 | 616 | # construct version prefix 617 | version <- paste(R.version$major, R.version$minor, sep = ".") 618 | prefix <- paste("R", numeric_version(version)[1, 1:2], sep = "-") 619 | 620 | # include SVN revision for development versions of R 621 | # (to avoid sharing platform-specific artefacts with released versions of R) 622 | devel <- 623 | identical(R.version[["status"]], "Under development (unstable)") || 624 | identical(R.version[["nickname"]], "Unsuffered Consequences") 625 | 626 | if (devel) 627 | prefix <- paste(prefix, R.version[["svn rev"]], sep = "-r") 628 | 629 | # build list of path components 630 | components <- c(prefix, R.version$platform) 631 | 632 | # include prefix if provided by user 633 | prefix <- renv_bootstrap_platform_prefix_impl() 634 | if (!is.na(prefix) && nzchar(prefix)) 635 | components <- c(prefix, components) 636 | 637 | # build prefix 638 | paste(components, collapse = "/") 639 | 640 | } 641 | 642 | renv_bootstrap_platform_prefix_impl <- function() { 643 | 644 | # if an explicit prefix has been supplied, use it 645 | prefix <- Sys.getenv("RENV_PATHS_PREFIX", unset = NA) 646 | if (!is.na(prefix)) 647 | return(prefix) 648 | 649 | # if the user has requested an automatic prefix, generate it 650 | auto <- Sys.getenv("RENV_PATHS_PREFIX_AUTO", unset = NA) 651 | if (is.na(auto) && getRversion() >= "4.4.0") 652 | auto <- "TRUE" 653 | 654 | if (auto %in% c("TRUE", "True", "true", "1")) 655 | return(renv_bootstrap_platform_prefix_auto()) 656 | 657 | # empty string on failure 658 | "" 659 | 660 | } 661 | 662 | renv_bootstrap_platform_prefix_auto <- function() { 663 | 664 | prefix <- tryCatch(renv_bootstrap_platform_os(), error = identity) 665 | if (inherits(prefix, "error") || prefix %in% "unknown") { 666 | 667 | msg <- paste( 668 | "failed to infer current operating system", 669 | "please file a bug report at https://github.com/rstudio/renv/issues", 670 | sep = "; " 671 | ) 672 | 673 | warning(msg) 674 | 675 | } 676 | 677 | prefix 678 | 679 | } 680 | 681 | renv_bootstrap_platform_os <- function() { 682 | 683 | sysinfo <- Sys.info() 684 | sysname <- sysinfo[["sysname"]] 685 | 686 | # handle Windows + macOS up front 687 | if (sysname == "Windows") 688 | return("windows") 689 | else if (sysname == "Darwin") 690 | return("macos") 691 | 692 | # check for os-release files 693 | for (file in c("/etc/os-release", "/usr/lib/os-release")) 694 | if (file.exists(file)) 695 | return(renv_bootstrap_platform_os_via_os_release(file, sysinfo)) 696 | 697 | # check for redhat-release files 698 | if (file.exists("/etc/redhat-release")) 699 | return(renv_bootstrap_platform_os_via_redhat_release()) 700 | 701 | "unknown" 702 | 703 | } 704 | 705 | renv_bootstrap_platform_os_via_os_release <- function(file, sysinfo) { 706 | 707 | # read /etc/os-release 708 | release <- utils::read.table( 709 | file = file, 710 | sep = "=", 711 | quote = c("\"", "'"), 712 | col.names = c("Key", "Value"), 713 | comment.char = "#", 714 | stringsAsFactors = FALSE 715 | ) 716 | 717 | vars <- as.list(release$Value) 718 | names(vars) <- release$Key 719 | 720 | # get os name 721 | os <- tolower(sysinfo[["sysname"]]) 722 | 723 | # read id 724 | id <- "unknown" 725 | for (field in c("ID", "ID_LIKE")) { 726 | if (field %in% names(vars) && nzchar(vars[[field]])) { 727 | id <- vars[[field]] 728 | break 729 | } 730 | } 731 | 732 | # read version 733 | version <- "unknown" 734 | for (field in c("UBUNTU_CODENAME", "VERSION_CODENAME", "VERSION_ID", "BUILD_ID")) { 735 | if (field %in% names(vars) && nzchar(vars[[field]])) { 736 | version <- vars[[field]] 737 | break 738 | } 739 | } 740 | 741 | # join together 742 | paste(c(os, id, version), collapse = "-") 743 | 744 | } 745 | 746 | renv_bootstrap_platform_os_via_redhat_release <- function() { 747 | 748 | # read /etc/redhat-release 749 | contents <- readLines("/etc/redhat-release", warn = FALSE) 750 | 751 | # infer id 752 | id <- if (grepl("centos", contents, ignore.case = TRUE)) 753 | "centos" 754 | else if (grepl("redhat", contents, ignore.case = TRUE)) 755 | "redhat" 756 | else 757 | "unknown" 758 | 759 | # try to find a version component (very hacky) 760 | version <- "unknown" 761 | 762 | parts <- strsplit(contents, "[[:space:]]")[[1L]] 763 | for (part in parts) { 764 | 765 | nv <- tryCatch(numeric_version(part), error = identity) 766 | if (inherits(nv, "error")) 767 | next 768 | 769 | version <- nv[1, 1] 770 | break 771 | 772 | } 773 | 774 | paste(c("linux", id, version), collapse = "-") 775 | 776 | } 777 | 778 | renv_bootstrap_library_root_name <- function(project) { 779 | 780 | # use project name as-is if requested 781 | asis <- Sys.getenv("RENV_PATHS_LIBRARY_ROOT_ASIS", unset = "FALSE") 782 | if (asis) 783 | return(basename(project)) 784 | 785 | # otherwise, disambiguate based on project's path 786 | id <- substring(renv_bootstrap_hash_text(project), 1L, 8L) 787 | paste(basename(project), id, sep = "-") 788 | 789 | } 790 | 791 | renv_bootstrap_library_root <- function(project) { 792 | 793 | prefix <- renv_bootstrap_profile_prefix() 794 | 795 | path <- Sys.getenv("RENV_PATHS_LIBRARY", unset = NA) 796 | if (!is.na(path)) 797 | return(paste(c(path, prefix), collapse = "/")) 798 | 799 | path <- renv_bootstrap_library_root_impl(project) 800 | if (!is.null(path)) { 801 | name <- renv_bootstrap_library_root_name(project) 802 | return(paste(c(path, prefix, name), collapse = "/")) 803 | } 804 | 805 | renv_bootstrap_paths_renv("library", project = project) 806 | 807 | } 808 | 809 | renv_bootstrap_library_root_impl <- function(project) { 810 | 811 | root <- Sys.getenv("RENV_PATHS_LIBRARY_ROOT", unset = NA) 812 | if (!is.na(root)) 813 | return(root) 814 | 815 | type <- renv_bootstrap_project_type(project) 816 | if (identical(type, "package")) { 817 | userdir <- renv_bootstrap_user_dir() 818 | return(file.path(userdir, "library")) 819 | } 820 | 821 | } 822 | 823 | renv_bootstrap_validate_version <- function(version, description = NULL) { 824 | 825 | # resolve description file 826 | # 827 | # avoid passing lib.loc to `packageDescription()` below, since R will 828 | # use the loaded version of the package by default anyhow. note that 829 | # this function should only be called after 'renv' is loaded 830 | # https://github.com/rstudio/renv/issues/1625 831 | description <- description %||% packageDescription("renv") 832 | 833 | # check whether requested version 'version' matches loaded version of renv 834 | sha <- attr(version, "sha", exact = TRUE) 835 | valid <- if (!is.null(sha)) 836 | renv_bootstrap_validate_version_dev(sha, description) 837 | else 838 | renv_bootstrap_validate_version_release(version, description) 839 | 840 | if (valid) 841 | return(TRUE) 842 | 843 | # the loaded version of renv doesn't match the requested version; 844 | # give the user instructions on how to proceed 845 | dev <- identical(description[["RemoteType"]], "github") 846 | remote <- if (dev) 847 | paste("rstudio/renv", description[["RemoteSha"]], sep = "@") 848 | else 849 | paste("renv", description[["Version"]], sep = "@") 850 | 851 | # display both loaded version + sha if available 852 | friendly <- renv_bootstrap_version_friendly( 853 | version = description[["Version"]], 854 | sha = if (dev) description[["RemoteSha"]] 855 | ) 856 | 857 | fmt <- heredoc(" 858 | renv %1$s was loaded from project library, but this project is configured to use renv %2$s. 859 | - Use `renv::record(\"%3$s\")` to record renv %1$s in the lockfile. 860 | - Use `renv::restore(packages = \"renv\")` to install renv %2$s into the project library. 861 | ") 862 | catf(fmt, friendly, renv_bootstrap_version_friendly(version), remote) 863 | 864 | FALSE 865 | 866 | } 867 | 868 | renv_bootstrap_validate_version_dev <- function(version, description) { 869 | expected <- description[["RemoteSha"]] 870 | is.character(expected) && startswith(expected, version) 871 | } 872 | 873 | renv_bootstrap_validate_version_release <- function(version, description) { 874 | expected <- description[["Version"]] 875 | is.character(expected) && identical(expected, version) 876 | } 877 | 878 | renv_bootstrap_hash_text <- function(text) { 879 | 880 | hashfile <- tempfile("renv-hash-") 881 | on.exit(unlink(hashfile), add = TRUE) 882 | 883 | writeLines(text, con = hashfile) 884 | tools::md5sum(hashfile) 885 | 886 | } 887 | 888 | renv_bootstrap_load <- function(project, libpath, version) { 889 | 890 | # try to load renv from the project library 891 | if (!requireNamespace("renv", lib.loc = libpath, quietly = TRUE)) 892 | return(FALSE) 893 | 894 | # warn if the version of renv loaded does not match 895 | renv_bootstrap_validate_version(version) 896 | 897 | # execute renv load hooks, if any 898 | hooks <- getHook("renv::autoload") 899 | for (hook in hooks) 900 | if (is.function(hook)) 901 | tryCatch(hook(), error = warnify) 902 | 903 | # load the project 904 | renv::load(project) 905 | 906 | TRUE 907 | 908 | } 909 | 910 | renv_bootstrap_profile_load <- function(project) { 911 | 912 | # if RENV_PROFILE is already set, just use that 913 | profile <- Sys.getenv("RENV_PROFILE", unset = NA) 914 | if (!is.na(profile) && nzchar(profile)) 915 | return(profile) 916 | 917 | # check for a profile file (nothing to do if it doesn't exist) 918 | path <- renv_bootstrap_paths_renv("profile", profile = FALSE, project = project) 919 | if (!file.exists(path)) 920 | return(NULL) 921 | 922 | # read the profile, and set it if it exists 923 | contents <- readLines(path, warn = FALSE) 924 | if (length(contents) == 0L) 925 | return(NULL) 926 | 927 | # set RENV_PROFILE 928 | profile <- contents[[1L]] 929 | if (!profile %in% c("", "default")) 930 | Sys.setenv(RENV_PROFILE = profile) 931 | 932 | profile 933 | 934 | } 935 | 936 | renv_bootstrap_profile_prefix <- function() { 937 | profile <- renv_bootstrap_profile_get() 938 | if (!is.null(profile)) 939 | return(file.path("profiles", profile, "renv")) 940 | } 941 | 942 | renv_bootstrap_profile_get <- function() { 943 | profile <- Sys.getenv("RENV_PROFILE", unset = "") 944 | renv_bootstrap_profile_normalize(profile) 945 | } 946 | 947 | renv_bootstrap_profile_set <- function(profile) { 948 | profile <- renv_bootstrap_profile_normalize(profile) 949 | if (is.null(profile)) 950 | Sys.unsetenv("RENV_PROFILE") 951 | else 952 | Sys.setenv(RENV_PROFILE = profile) 953 | } 954 | 955 | renv_bootstrap_profile_normalize <- function(profile) { 956 | 957 | if (is.null(profile) || profile %in% c("", "default")) 958 | return(NULL) 959 | 960 | profile 961 | 962 | } 963 | 964 | renv_bootstrap_path_absolute <- function(path) { 965 | 966 | substr(path, 1L, 1L) %in% c("~", "/", "\\") || ( 967 | substr(path, 1L, 1L) %in% c(letters, LETTERS) && 968 | substr(path, 2L, 3L) %in% c(":/", ":\\") 969 | ) 970 | 971 | } 972 | 973 | renv_bootstrap_paths_renv <- function(..., profile = TRUE, project = NULL) { 974 | renv <- Sys.getenv("RENV_PATHS_RENV", unset = "renv") 975 | root <- if (renv_bootstrap_path_absolute(renv)) NULL else project 976 | prefix <- if (profile) renv_bootstrap_profile_prefix() 977 | components <- c(root, renv, prefix, ...) 978 | paste(components, collapse = "/") 979 | } 980 | 981 | renv_bootstrap_project_type <- function(path) { 982 | 983 | descpath <- file.path(path, "DESCRIPTION") 984 | if (!file.exists(descpath)) 985 | return("unknown") 986 | 987 | desc <- tryCatch( 988 | read.dcf(descpath, all = TRUE), 989 | error = identity 990 | ) 991 | 992 | if (inherits(desc, "error")) 993 | return("unknown") 994 | 995 | type <- desc$Type 996 | if (!is.null(type)) 997 | return(tolower(type)) 998 | 999 | package <- desc$Package 1000 | if (!is.null(package)) 1001 | return("package") 1002 | 1003 | "unknown" 1004 | 1005 | } 1006 | 1007 | renv_bootstrap_user_dir <- function() { 1008 | dir <- renv_bootstrap_user_dir_impl() 1009 | path.expand(chartr("\\", "/", dir)) 1010 | } 1011 | 1012 | renv_bootstrap_user_dir_impl <- function() { 1013 | 1014 | # use local override if set 1015 | override <- getOption("renv.userdir.override") 1016 | if (!is.null(override)) 1017 | return(override) 1018 | 1019 | # use R_user_dir if available 1020 | tools <- asNamespace("tools") 1021 | if (is.function(tools$R_user_dir)) 1022 | return(tools$R_user_dir("renv", "cache")) 1023 | 1024 | # try using our own backfill for older versions of R 1025 | envvars <- c("R_USER_CACHE_DIR", "XDG_CACHE_HOME") 1026 | for (envvar in envvars) { 1027 | root <- Sys.getenv(envvar, unset = NA) 1028 | if (!is.na(root)) 1029 | return(file.path(root, "R/renv")) 1030 | } 1031 | 1032 | # use platform-specific default fallbacks 1033 | if (Sys.info()[["sysname"]] == "Windows") 1034 | file.path(Sys.getenv("LOCALAPPDATA"), "R/cache/R/renv") 1035 | else if (Sys.info()[["sysname"]] == "Darwin") 1036 | "~/Library/Caches/org.R-project.R/R/renv" 1037 | else 1038 | "~/.cache/R/renv" 1039 | 1040 | } 1041 | 1042 | renv_bootstrap_version_friendly <- function(version, shafmt = NULL, sha = NULL) { 1043 | sha <- sha %||% attr(version, "sha", exact = TRUE) 1044 | parts <- c(version, sprintf(shafmt %||% " [sha: %s]", substring(sha, 1L, 7L))) 1045 | paste(parts, collapse = "") 1046 | } 1047 | 1048 | renv_bootstrap_exec <- function(project, libpath, version) { 1049 | if (!renv_bootstrap_load(project, libpath, version)) 1050 | renv_bootstrap_run(version, libpath) 1051 | } 1052 | 1053 | renv_bootstrap_run <- function(version, libpath) { 1054 | 1055 | # perform bootstrap 1056 | bootstrap(version, libpath) 1057 | 1058 | # exit early if we're just testing bootstrap 1059 | if (!is.na(Sys.getenv("RENV_BOOTSTRAP_INSTALL_ONLY", unset = NA))) 1060 | return(TRUE) 1061 | 1062 | # try again to load 1063 | if (requireNamespace("renv", lib.loc = libpath, quietly = TRUE)) { 1064 | return(renv::load(project = getwd())) 1065 | } 1066 | 1067 | # failed to download or load renv; warn the user 1068 | msg <- c( 1069 | "Failed to find an renv installation: the project will not be loaded.", 1070 | "Use `renv::activate()` to re-initialize the project." 1071 | ) 1072 | 1073 | warning(paste(msg, collapse = "\n"), call. = FALSE) 1074 | 1075 | } 1076 | 1077 | renv_json_read <- function(file = NULL, text = NULL) { 1078 | 1079 | jlerr <- NULL 1080 | 1081 | # if jsonlite is loaded, use that instead 1082 | if ("jsonlite" %in% loadedNamespaces()) { 1083 | 1084 | json <- tryCatch(renv_json_read_jsonlite(file, text), error = identity) 1085 | if (!inherits(json, "error")) 1086 | return(json) 1087 | 1088 | jlerr <- json 1089 | 1090 | } 1091 | 1092 | # otherwise, fall back to the default JSON reader 1093 | json <- tryCatch(renv_json_read_default(file, text), error = identity) 1094 | if (!inherits(json, "error")) 1095 | return(json) 1096 | 1097 | # report an error 1098 | if (!is.null(jlerr)) 1099 | stop(jlerr) 1100 | else 1101 | stop(json) 1102 | 1103 | } 1104 | 1105 | renv_json_read_jsonlite <- function(file = NULL, text = NULL) { 1106 | text <- paste(text %||% readLines(file, warn = FALSE), collapse = "\n") 1107 | jsonlite::fromJSON(txt = text, simplifyVector = FALSE) 1108 | } 1109 | 1110 | renv_json_read_default <- function(file = NULL, text = NULL) { 1111 | 1112 | # find strings in the JSON 1113 | text <- paste(text %||% readLines(file, warn = FALSE), collapse = "\n") 1114 | pattern <- '["](?:(?:\\\\.)|(?:[^"\\\\]))*?["]' 1115 | locs <- gregexpr(pattern, text, perl = TRUE)[[1]] 1116 | 1117 | # if any are found, replace them with placeholders 1118 | replaced <- text 1119 | strings <- character() 1120 | replacements <- character() 1121 | 1122 | if (!identical(c(locs), -1L)) { 1123 | 1124 | # get the string values 1125 | starts <- locs 1126 | ends <- locs + attr(locs, "match.length") - 1L 1127 | strings <- substring(text, starts, ends) 1128 | 1129 | # only keep those requiring escaping 1130 | strings <- grep("[[\\]{}:]", strings, perl = TRUE, value = TRUE) 1131 | 1132 | # compute replacements 1133 | replacements <- sprintf('"\032%i\032"', seq_along(strings)) 1134 | 1135 | # replace the strings 1136 | mapply(function(string, replacement) { 1137 | replaced <<- sub(string, replacement, replaced, fixed = TRUE) 1138 | }, strings, replacements) 1139 | 1140 | } 1141 | 1142 | # transform the JSON into something the R parser understands 1143 | transformed <- replaced 1144 | transformed <- gsub("{}", "`names<-`(list(), character())", transformed, fixed = TRUE) 1145 | transformed <- gsub("[[{]", "list(", transformed, perl = TRUE) 1146 | transformed <- gsub("[]}]", ")", transformed, perl = TRUE) 1147 | transformed <- gsub(":", "=", transformed, fixed = TRUE) 1148 | text <- paste(transformed, collapse = "\n") 1149 | 1150 | # parse it 1151 | json <- parse(text = text, keep.source = FALSE, srcfile = NULL)[[1L]] 1152 | 1153 | # construct map between source strings, replaced strings 1154 | map <- as.character(parse(text = strings)) 1155 | names(map) <- as.character(parse(text = replacements)) 1156 | 1157 | # convert to list 1158 | map <- as.list(map) 1159 | 1160 | # remap strings in object 1161 | remapped <- renv_json_read_remap(json, map) 1162 | 1163 | # evaluate 1164 | eval(remapped, envir = baseenv()) 1165 | 1166 | } 1167 | 1168 | renv_json_read_remap <- function(json, map) { 1169 | 1170 | # fix names 1171 | if (!is.null(names(json))) { 1172 | lhs <- match(names(json), names(map), nomatch = 0L) 1173 | rhs <- match(names(map), names(json), nomatch = 0L) 1174 | names(json)[rhs] <- map[lhs] 1175 | } 1176 | 1177 | # fix values 1178 | if (is.character(json)) 1179 | return(map[[json]] %||% json) 1180 | 1181 | # handle true, false, null 1182 | if (is.name(json)) { 1183 | text <- as.character(json) 1184 | if (text == "true") 1185 | return(TRUE) 1186 | else if (text == "false") 1187 | return(FALSE) 1188 | else if (text == "null") 1189 | return(NULL) 1190 | } 1191 | 1192 | # recurse 1193 | if (is.recursive(json)) { 1194 | for (i in seq_along(json)) { 1195 | json[i] <- list(renv_json_read_remap(json[[i]], map)) 1196 | } 1197 | } 1198 | 1199 | json 1200 | 1201 | } 1202 | 1203 | # load the renv profile, if any 1204 | renv_bootstrap_profile_load(project) 1205 | 1206 | # construct path to library root 1207 | root <- renv_bootstrap_library_root(project) 1208 | 1209 | # construct library prefix for platform 1210 | prefix <- renv_bootstrap_platform_prefix() 1211 | 1212 | # construct full libpath 1213 | libpath <- file.path(root, prefix) 1214 | 1215 | # run bootstrap code 1216 | renv_bootstrap_exec(project, libpath, version) 1217 | 1218 | invisible() 1219 | 1220 | }) 1221 | -------------------------------------------------------------------------------- /renv/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "bioconductor.version": null, 3 | "external.libraries": [], 4 | "ignored.packages": [], 5 | "package.dependency.fields": [ 6 | "Imports", 7 | "Depends", 8 | "LinkingTo" 9 | ], 10 | "ppm.enabled": null, 11 | "ppm.ignored.urls": [], 12 | "r.version": null, 13 | "snapshot.type": "implicit", 14 | "use.cache": true, 15 | "vcs.ignore.cellar": true, 16 | "vcs.ignore.library": true, 17 | "vcs.ignore.local": true, 18 | "vcs.manage.ignores": true 19 | } 20 | -------------------------------------------------------------------------------- /run.R: -------------------------------------------------------------------------------- 1 | targets::tar_make() -------------------------------------------------------------------------------- /tests/testthat.R: -------------------------------------------------------------------------------- 1 | # This file is part of the standard setup for testthat. 2 | # It is recommended that you do not modify it. 3 | # 4 | # Where should you do additional test configuration? 5 | # Learn more about the roles of various files in: 6 | # * https://r-pkgs.org/testing-design.html#sec-tests-files-overview 7 | # * https://testthat.r-lib.org/articles/special-files.html 8 | 9 | library(testthat) 10 | # library(docker-duckdb-r) 11 | 12 | # test_check("docker-duckdb-r") 13 | --------------------------------------------------------------------------------