From 52b79be254f17d2bc9f067be301601ecd293c674 Mon Sep 17 00:00:00 2001 From: Michael Milton Date: Thu, 15 Jun 2023 16:31:33 +1000 Subject: [PATCH 01/34] future -> crew --- episodes/introduction.Rmd | 2 +- episodes/parallel.Rmd | 47 +++++++++++++-------- renv/profiles/lesson-requirements/renv.lock | 5 ++- 3 files changed, 35 insertions(+), 19 deletions(-) diff --git a/episodes/introduction.Rmd b/episodes/introduction.Rmd index fe60c050..6f2259ec 100644 --- a/episodes/introduction.Rmd +++ b/episodes/introduction.Rmd @@ -94,7 +94,7 @@ We will gradually build up the analysis through this lesson, but you can see the - We can only have confidence in the results of scientific analyses if they can be reproduced by others (including your future self) - `targets` helps achieve reproducibility by automating workflow -- `targests` is designed for use with the R programming language +- `targets` is designed for use with the R programming language - The example dataset for this workshop includes measurements taken on penguins in Antarctica :::::::::::::::::::::::::::::::::::::::::::::::: diff --git a/episodes/parallel.Rmd b/episodes/parallel.Rmd index d3746ea9..a6751904 100644 --- a/episodes/parallel.Rmd +++ b/episodes/parallel.Rmd @@ -43,7 +43,8 @@ This takes advantage of multiple processors in your computer to build multiple t ## When to use parallel processing -Parallel processing should only be used if your workflow has a structure such that it makes sense---if your workflow only consists of a linear sequence of targets, then there is nothing to parallelize. +Parallel processing should only be used if your workflow has independent tasks---if your workflow only consists of a linear sequence of targets, then there is nothing to parallelize. +Most workflows that use branching can benefit from parallelism. ::::::::::::::::::::::::::::::::::::: @@ -53,34 +54,39 @@ If you are interested in high-performance computing, [see the `targets` manual]( ### Install R packages for parallel computing -For this demo, we will use the [`future` backend](https://github.com/HenrikBengtsson/future). +For this demo, we will use the new [`crew` backend](https://wlandau.github.io/crew/). ::::::::::::::::::::::::::::::::::::: {.prereq} ### Install required packages -You will need to install several packages to use the `future` backend: +You will need to install several packages to use the `crew` backend: ```{r} -#| label: install-future +#| label: install-vrew #| eval: false -install.packages("future") -install.packages("future.callr") +install.packages("nanonext", repos = "https://shikokuchuo.r-universe.dev") +install.packages("mirai", repos = "https://shikokuchuo.r-universe.dev") +install.packages("crew", type = "source") ``` ::::::::::::::::::::::::::::::::::::: ### Set up workflow -There are a few things you need to change to enable parallel processing with `future`: +To enable parallel processing with `crew` you only need to load the `crew` package, then tell `targets` to use it using `tar_option_set`. +Specifically, the following lines enable crew, and tells it to use 2 parallel workers. +You can increase this number on more powerful machines: -- Load the `future` and `future.callr` packages -- Add a line with `plan(callr)` -- When you run the pipeline, use `tar_make_future(workers = 2)` instead of `tar_make()` - -Here, `workers = 2` is the number of processes to run in parallel. You may increase this up to the number of cores available on your machine. +```R +library(crew) +tar_option_set( + controller = crew_controller_local(workers = 2) +) +``` -Make these changes to the penguins analysis. It should now look like this: +Make these changes to the penguins analysis. +It should now look like this: ```{r} #| label: example-model-show-setup @@ -88,7 +94,11 @@ Make these changes to the penguins analysis. It should now look like this: source("R/packages.R") source("R/functions.R") -plan(callr) +# We just added this! +library(crew) +tar_option_set( + controller = crew_controller_local(workers = 2) +) tar_plan( # Load raw data @@ -159,7 +169,10 @@ Then, change the plan to use the "slow" version of the functions: source("R/packages.R") source("R/functions.R") -plan(callr) +library(crew) +tar_option_set( + controller = crew_controller_local(workers = 2) +) tar_plan( # Load raw data @@ -194,7 +207,7 @@ tar_plan( ) ``` -Finally, run the pipeline with `tar_make_future()`. +Finally, run the pipeline with `tar_make()` as normal. ```{r} #| label: example-model-hide-9 @@ -205,7 +218,7 @@ tar_dir({ tar_make(reporter = "silent") # Slow functions write_example_plan(8) - tar_make_future(workers = 2) + tar_make() }) ``` diff --git a/renv/profiles/lesson-requirements/renv.lock b/renv/profiles/lesson-requirements/renv.lock index 50782226..5d82526f 100644 --- a/renv/profiles/lesson-requirements/renv.lock +++ b/renv/profiles/lesson-requirements/renv.lock @@ -1419,7 +1419,10 @@ "Package": "stringi", "Version": "1.7.12", "Source": "Repository", - "Repository": "CRAN", + "Repository": "https://carpentries.r-universe.dev", + "RemoteUrl": "https://github.com/gagolews/stringi", + "RemoteRef": "v1.7.12", + "RemoteSha": "e4cf3176bc3943e6c477885be3445cbbd7d4bab6", "Requirements": [ "R", "stats", From a93a6f311f93c76e986cc1dc6bc4708f3cdcdcb8 Mon Sep 17 00:00:00 2001 From: Michael Milton Date: Thu, 15 Jun 2023 16:34:09 +1000 Subject: [PATCH 02/34] Fix typo --- episodes/parallel.Rmd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/episodes/parallel.Rmd b/episodes/parallel.Rmd index a6751904..130264c1 100644 --- a/episodes/parallel.Rmd +++ b/episodes/parallel.Rmd @@ -63,7 +63,7 @@ For this demo, we will use the new [`crew` backend](https://wlandau.github.io/cr You will need to install several packages to use the `crew` backend: ```{r} -#| label: install-vrew +#| label: install-crew #| eval: false install.packages("nanonext", repos = "https://shikokuchuo.r-universe.dev") install.packages("mirai", repos = "https://shikokuchuo.r-universe.dev") From e3fee835918b422184affc316997fee2be190dff Mon Sep 17 00:00:00 2001 From: Michael Milton Date: Fri, 30 Jun 2023 10:47:58 +1000 Subject: [PATCH 03/34] HPC draft --- config.yaml | 1 + episodes/hpc.Rmd | 135 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 136 insertions(+) create mode 100644 episodes/hpc.Rmd diff --git a/config.yaml b/config.yaml index d7951c6c..325ff6a6 100644 --- a/config.yaml +++ b/config.yaml @@ -69,6 +69,7 @@ episodes: - branch.Rmd - parallel.Rmd - quarto.Rmd +- hpc.Rmd # Information for Learners learners: diff --git a/episodes/hpc.Rmd b/episodes/hpc.Rmd new file mode 100644 index 00000000..7d8d1fbb --- /dev/null +++ b/episodes/hpc.Rmd @@ -0,0 +1,135 @@ +--- +title: 'Deploying Targets on HPC' +teaching: 10 +exercises: 2 +--- + +```{r, echo=FALSE} +# Handle the case when the +if (!nzchar(Sys.which("sbatch"))){ + knitr::knit_exit("sbatch was not detected. Likely Slurm is not installed. Exiting.") +} +``` + +:::::::::::::::::::::::::::::::::::::: questions + +- Why would we use HPC to run Targets workflows? +- How can we run Targets workflows on Slurm? + +:::::::::::::::::::::::::::::::::::::::::::::::: + +::::::::::::::::::::::::::::::::::::: objectives + +- Be able to generate a report using `targets` + +:::::::::::::::::::::::::::::::::::::::::::::::: + +::::::::::::::::::::::::::::::::::::: instructor + +Episode summary: Show how to write reports with Quarto + +:::::::::::::::::::::::::::::::::::::::::::::::: + +```{r} +#| label: setup +#| echo: FALSE +#| message: FALSE +#| warning: FALSE +library(targets) +library(tarchetypes) +library(quarto) # don't actually need to load, but put here so renv catches it +source("https://raw.githubusercontent.com/joelnitta/targets-workshop/main/episodes/files/functions.R?token=$(date%20+%s)") # nolint + +# Increase width for printing tibbles +options(width = 140) +``` + +## Advantages of HPC + +If your analysis involves computationally intensive or long-running tasks such as training machine learning models or processing very large amounts of data, it will quickly become infeasible to use a single machine to run this. +If you are part of an organisation with access to a High Performance Computing (HPC) cluster, you can easily leverage the numerous machines with Targets to scale up your analysis. +This differs from the exucution we have learned so far, which spawns extra R processes on the *same machine* to speed up execution. + +## Configuring Targets for Slurm + +Fortunately, using HPC is as simple as changing the Targets `controller`. +In this section we will assume that our HPC uses Slurm as its job scheduler, but you can easily use other schedulers such as PBS/TORQUE, Sun Grid Engine (SGE) or LSF. + +In the Parallel Processing section, we used the following configuration: +```{R} +library(crew) +tar_option_set( + controller = crew_controller_local(workers = 2) +) +``` +To configure this for Slurm, we just swap out the controller with a new one from the `crew.cluster` package: + +```{R} +library(crew.cluster) +tar_option_set( + controller = crew_controller_slurm( + workers = 3, + script_lines = "module load R" + ) +) +``` + +There are a number of options you can pass to `crew_controller_slurm()` to fine-tune the Slurm execution, [which you can find here](https://wlandau.github.io/crew.cluster/reference/crew_controller_slurm.html). +Here we are only using two: + * `workers` sets the number of jobs that are submitted to Slurm to process targets. + * `script_lines` adds some lines to the Slurm submit script used by Targets. This is useful for loading Environment Modules. + +Let's run the modified workflow: + +```{r} +source("R/packages.R") +source("R/functions.R") + +library(crew.cluster) +tar_option_set( + controller = crew_controller_slurm( + workers = 3, + script_lines = "module load R" + ) +) + +tar_plan( + # Load raw data + tar_file_read( + penguins_data_raw, + path_to_file("penguins_raw.csv"), + read_csv(!!.x, show_col_types = FALSE) + ), + # Clean data + penguins_data = clean_penguin_data(penguins_data_raw), + # Build models + models = list( + combined_model = lm( + bill_depth_mm ~ bill_length_mm, data = penguins_data), + species_model = lm( + bill_depth_mm ~ bill_length_mm + species, data = penguins_data), + interaction_model = lm( + bill_depth_mm ~ bill_length_mm * species, data = penguins_data) + ), + # Get model summaries + tar_target( + model_summaries, + glance_with_mod_name_slow(models), + pattern = map(models) + ), + # Get model predictions + tar_target( + model_predictions, + augment_with_mod_name_slow(models), + pattern = map(models) + ) +) +``` + +::::::::::::::::::::::::::::::::::::: keypoints + +- `tarchetypes::tar_quarto()` is used to render Quarto documents +- You should load targets within the Quarto document using `tar_load()` and `tar_read()` +- It is recommended to do heavy computations in the main targets workflow, and lighter formatting and plot generation in the Quarto document + +:::::::::::::::::::::::::::::::::::::::::::::::: From 3af1a0dde3be47959f9850af27cebb14cd3b4e5b Mon Sep 17 00:00:00 2001 From: Michael Milton Date: Fri, 30 Jun 2023 16:08:45 +1000 Subject: [PATCH 04/34] Start worker section --- episodes/hpc.Rmd | 45 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/episodes/hpc.Rmd b/episodes/hpc.Rmd index 7d8d1fbb..9509eaf1 100644 --- a/episodes/hpc.Rmd +++ b/episodes/hpc.Rmd @@ -4,7 +4,7 @@ teaching: 10 exercises: 2 --- -```{r, echo=FALSE} +```{R, echo=FALSE} # Handle the case when the if (!nzchar(Sys.which("sbatch"))){ knitr::knit_exit("sbatch was not detected. Likely Slurm is not installed. Exiting.") @@ -81,7 +81,10 @@ Here we are only using two: Let's run the modified workflow: -```{r} +```{r, file="/home/users/allstaff/milton.m/targets-workshop/episodes/files/functions.R"} +``` + +``` source("R/packages.R") source("R/functions.R") @@ -126,6 +129,44 @@ tar_plan( ) ``` +::: challenge +## Increasing Resources + +Q: How would you modify your `_targets.R` if your targets needed 200GB of RAM? + +::: hint +Check the arguments for [`crew_controller_slurm`](https://wlandau.github.io/crew.cluster/reference/crew_controller_slurm.html#arguments-1). +::: +::: solution +```R +tar_option_set( + controller = crew_controller_slurm( + workers = 3, + script_lines = "module load R", + # Added this + slurm_memory_gigabytes_per_cpu = 200, + slurm_cpus_per_task = 1 + ) +) +``` +::: +::: + +## HPC Workers + +Unlike what you might expect, `crew` does not submit one Slurm job for each target. +Instead, it uses persistent workers, meaning that you define a pool of workers when configuring the workflow. +In our example above we used 3 workers. +For each worker, `crew` submits a single Slurm job, and these workers will process multiple targets over their lifetime. + +We can verify that this has happened using `sacct`: + +```{bash} +sacct +``` + +The downside of this approach + ::::::::::::::::::::::::::::::::::::: keypoints - `tarchetypes::tar_quarto()` is used to render Quarto documents From f8928bcbb7dec84921ffb556239c564f6c1f33ec Mon Sep 17 00:00:00 2001 From: Michael Milton Date: Fri, 7 Jul 2023 15:31:19 +1000 Subject: [PATCH 05/34] Finished draft of heterogenous workers --- episodes/hpc.Rmd | 103 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 99 insertions(+), 4 deletions(-) diff --git a/episodes/hpc.Rmd b/episodes/hpc.Rmd index 9509eaf1..b9cdf3db 100644 --- a/episodes/hpc.Rmd +++ b/episodes/hpc.Rmd @@ -81,9 +81,6 @@ Here we are only using two: Let's run the modified workflow: -```{r, file="/home/users/allstaff/milton.m/targets-workshop/episodes/files/functions.R"} -``` - ``` source("R/packages.R") source("R/functions.R") @@ -165,7 +162,105 @@ We can verify that this has happened using `sacct`: sacct ``` -The downside of this approach +The upside of this approach is that we don't have to work out the minutae of how long each target takes to build, or what resources it needs. +It also means that we don't submit a lot of jobs, making our Slurm usage more efficient and easy to monitor. + +The downside of this mechanism is that **the resources of the worker have to be sufficient to build each of your targets**. +So for example if you have two targets, the first of which uses 1 GB of RAM, and the second of which uses 100 GB, your worker will need 100 GB of RAM. + +In some cases we may prefer heterogeneous workers, especially if some of our targets need a GPU and others need a CPU. +To do this, we firstly define each worker configuration using `crew_controller_slurm`: + +```{R} +library(crew.cluster) +crew_controller_slurm( + name = "cpu_worker" + workers = 3, + script_lines = "module load R", + slurm_memory_gigabytes_per_cpu = 200, + slurm_cpus_per_task = 1 +) +``` + +Then we specify this controller by name in each target definition: + +```{R} +tar_target( + name = cpu_task, + command = run_model2(data), + resources = tar_resources( + crew = tar_resources_crew(controller = "cpu_worker") + ) +) +``` + +::: challenge +## Mixing GPU and CPU targets + +Q: Say we have the following targets workflow. How would we modify it so that `gpu_task` is only run in a GPU Slurm job? +```{R, eval=FALSE} +graphics_devices <- function(){ + system2("lshw", c("-class", "display), stdout=TRUE, stderr=TRUE) +} + +tar_plan( + tar_target( + cpu_hardware, + graphics_devices() + ), + tar_target( + gpu_hardware, + graphics_devices() + ) +) +``` + +::: hint +You will need to define two different crew controllers. +::: +::: solution +```R +graphics_devices <- function(){ + system2("lshw", c("-class", "display), stdout=TRUE, stderr=TRUE) +} + +library(crew.cluster) +crew_controller_slurm( + name = "cpu_worker" + workers = 3, + script_lines = "module load R", + slurm_memory_gigabytes_per_cpu = 200, + slurm_cpus_per_task = 1 +) +crew_controller_slurm( + name = "gpu_worker" + workers = 3, + script_lines = "#SBATCH --gres=gpu:1 +module load R", + slurm_memory_gigabytes_per_cpu = 200, + slurm_cpus_per_task = 1 +) +``` + +tar_plan( + tar_target( + cpu_hardware, + graphics_devices(), + resources = tar_resources( + crew = tar_resources_crew(controller = "cpu_worker") + ) + ), + tar_target( + gpu_hardware, + graphics_devices(), + resources = tar_resources( + crew = tar_resources_crew(controller = "gpu_worker") + ) + ) +) +``` +::: +::: ::::::::::::::::::::::::::::::::::::: keypoints From 0f8e054793d45002b5b92fcb86c7cd6d74cad104 Mon Sep 17 00:00:00 2001 From: Michael Milton Date: Mon, 10 Jul 2023 14:33:12 +1000 Subject: [PATCH 06/34] Update dependencies to have crew, finish first draft of HPC --- episodes/hpc.Rmd | 52 +++++++++++----- learners/setup.md | 2 +- renv/profiles/lesson-requirements/renv.lock | 69 ++++++++++++--------- 3 files changed, 78 insertions(+), 45 deletions(-) diff --git a/episodes/hpc.Rmd b/episodes/hpc.Rmd index b9cdf3db..0cc86ef8 100644 --- a/episodes/hpc.Rmd +++ b/episodes/hpc.Rmd @@ -76,12 +76,13 @@ tar_option_set( There are a number of options you can pass to `crew_controller_slurm()` to fine-tune the Slurm execution, [which you can find here](https://wlandau.github.io/crew.cluster/reference/crew_controller_slurm.html). Here we are only using two: + * `workers` sets the number of jobs that are submitted to Slurm to process targets. - * `script_lines` adds some lines to the Slurm submit script used by Targets. This is useful for loading Environment Modules. + * `script_lines` adds some lines to the Slurm submit script used by Targets. This is useful for loading Environment Modules and adding `#SBATCH` options. Let's run the modified workflow: -``` +```{R, eval=FALSE} source("R/packages.R") source("R/functions.R") @@ -151,7 +152,7 @@ tar_option_set( ## HPC Workers -Unlike what you might expect, `crew` does not submit one Slurm job for each target. +Despite what you might expect, `crew` does not submit one Slurm job for each target. Instead, it uses persistent workers, meaning that you define a pool of workers when configuring the workflow. In our example above we used 3 workers. For each worker, `crew` submits a single Slurm job, and these workers will process multiple targets over their lifetime. @@ -166,15 +167,39 @@ The upside of this approach is that we don't have to work out the minutae of how It also means that we don't submit a lot of jobs, making our Slurm usage more efficient and easy to monitor. The downside of this mechanism is that **the resources of the worker have to be sufficient to build each of your targets**. -So for example if you have two targets, the first of which uses 1 GB of RAM, and the second of which uses 100 GB, your worker will need 100 GB of RAM. + +::: challenge +## Choosing a Worker + +Q: Say we have two targets. One uses 100 GB of RAM and 1 CPU, and the other needs 10 GB of RAM and 8 CPUs to run a multi-threaded function. What worker configuration do we use? + +::: solution +We need to choose the maximum of all resources if we have a single worker. +It will need 100 GB of RAM and 8 CPUs. +To do this we might use a controller a bit like this: +```{R, results="hide"} +crew_controller_slurm( + name = "cpu_worker", + workers = 3, + script_lines = " +#SBATCH --cpus-per-task=8 +module load R", + slurm_memory_gigabytes_per_cpu = 100 +) +``` +::: +::: + +## Heterogeneous Workers In some cases we may prefer heterogeneous workers, especially if some of our targets need a GPU and others need a CPU. -To do this, we firstly define each worker configuration using `crew_controller_slurm`: +To do this, we firstly define each worker configuration by adding the `name` argument to `crew_controller_slurm`. +Note that this time we aren't passing it into `tar_option_set`: -```{R} +```{R, results="hide"} library(crew.cluster) crew_controller_slurm( - name = "cpu_worker" + name = "cpu_worker", workers = 3, script_lines = "module load R", slurm_memory_gigabytes_per_cpu = 200, @@ -184,7 +209,7 @@ crew_controller_slurm( Then we specify this controller by name in each target definition: -```{R} +```{R, results="hide"} tar_target( name = cpu_task, command = run_model2(data), @@ -200,7 +225,7 @@ tar_target( Q: Say we have the following targets workflow. How would we modify it so that `gpu_task` is only run in a GPU Slurm job? ```{R, eval=FALSE} graphics_devices <- function(){ - system2("lshw", c("-class", "display), stdout=TRUE, stderr=TRUE) + system2("lshw", c("-class", "display"), stdout=TRUE, stderr=FALSE) } tar_plan( @@ -221,7 +246,7 @@ You will need to define two different crew controllers. ::: solution ```R graphics_devices <- function(){ - system2("lshw", c("-class", "display), stdout=TRUE, stderr=TRUE) + system2("lshw", c("-class", "display"), stdout=TRUE, stderr=FALSE) } library(crew.cluster) @@ -240,7 +265,6 @@ module load R", slurm_memory_gigabytes_per_cpu = 200, slurm_cpus_per_task = 1 ) -``` tar_plan( tar_target( @@ -264,8 +288,8 @@ tar_plan( ::::::::::::::::::::::::::::::::::::: keypoints -- `tarchetypes::tar_quarto()` is used to render Quarto documents -- You should load targets within the Quarto document using `tar_load()` and `tar_read()` -- It is recommended to do heavy computations in the main targets workflow, and lighter formatting and plot generation in the Quarto document +- `crew.cluster::crew_controller_slurm()` is used to configure a workflow to use Slurm +- Crew uses persistent workers on HPC, and you need to choose your resources accordingly +- You can create heterogeneous workers by using multiple calls to `crew_controller_slurm(name=)` :::::::::::::::::::::::::::::::::::::::::::::::: diff --git a/learners/setup.md b/learners/setup.md index bde2472a..bb0c1544 100644 --- a/learners/setup.md +++ b/learners/setup.md @@ -31,4 +31,4 @@ install.packages( There is a [Posit Cloud](https://posit.cloud/) instance with RStudio and all necessary packages pre-installed available, so you don't need to install anything on your own computer. You may need to create an account (free). -Click this link to open: \ No newline at end of file +Click this link to open: diff --git a/renv/profiles/lesson-requirements/renv.lock b/renv/profiles/lesson-requirements/renv.lock index 5d82526f..611ae217 100644 --- a/renv/profiles/lesson-requirements/renv.lock +++ b/renv/profiles/lesson-requirements/renv.lock @@ -63,7 +63,7 @@ "Package": "R6", "Version": "2.5.1", "Source": "Repository", - "Repository": "CRAN", + "Repository": "RSPM", "Requirements": [ "R" ], @@ -114,7 +114,7 @@ "Package": "base64enc", "Version": "0.1-3", "Source": "Repository", - "Repository": "CRAN", + "Repository": "RSPM", "Requirements": [ "R" ], @@ -191,7 +191,7 @@ "Package": "bslib", "Version": "0.4.2", "Source": "Repository", - "Repository": "CRAN", + "Repository": "RSPM", "Requirements": [ "R", "base64enc", @@ -247,7 +247,7 @@ "Package": "cli", "Version": "3.6.1", "Source": "Repository", - "Repository": "CRAN", + "Repository": "RSPM", "Requirements": [ "R", "utils" @@ -373,7 +373,7 @@ "Package": "digest", "Version": "0.6.31", "Source": "Repository", - "Repository": "CRAN", + "Repository": "RSPM", "Requirements": [ "R", "utils" @@ -426,7 +426,7 @@ "Package": "ellipsis", "Version": "0.3.2", "Source": "Repository", - "Repository": "CRAN", + "Repository": "RSPM", "Requirements": [ "R", "rlang" @@ -474,7 +474,7 @@ "Package": "fontawesome", "Version": "0.5.1", "Source": "Repository", - "Repository": "CRAN", + "Repository": "RSPM", "Requirements": [ "R", "htmltools", @@ -502,7 +502,7 @@ "Package": "fs", "Version": "1.6.2", "Source": "Repository", - "Repository": "CRAN", + "Repository": "RSPM", "Requirements": [ "R", "methods" @@ -625,7 +625,7 @@ "Package": "glue", "Version": "1.6.2", "Source": "Repository", - "Repository": "CRAN", + "Repository": "RSPM", "Requirements": [ "R", "methods" @@ -725,7 +725,7 @@ "Package": "highr", "Version": "0.10", "Source": "Repository", - "Repository": "CRAN", + "Repository": "RSPM", "Requirements": [ "R", "xfun" @@ -750,7 +750,7 @@ "Package": "htmltools", "Version": "0.5.5", "Source": "Repository", - "Repository": "CRAN", + "Repository": "RSPM", "Requirements": [ "R", "base64enc", @@ -797,7 +797,7 @@ "Package": "ids", "Version": "1.0.1", "Source": "Repository", - "Repository": "CRAN", + "Repository": "RSPM", "Requirements": [ "openssl", "uuid" @@ -849,7 +849,7 @@ "Package": "jsonlite", "Version": "1.8.4", "Source": "Repository", - "Repository": "CRAN", + "Repository": "RSPM", "Requirements": [ "methods" ], @@ -912,7 +912,7 @@ "Package": "lifecycle", "Version": "1.0.3", "Source": "Repository", - "Repository": "CRAN", + "Repository": "RSPM", "Requirements": [ "R", "cli", @@ -948,7 +948,7 @@ "Package": "magrittr", "Version": "2.0.3", "Source": "Repository", - "Repository": "CRAN", + "Repository": "RSPM", "Requirements": [ "R" ], @@ -986,7 +986,7 @@ "Package": "mime", "Version": "0.12", "Source": "Repository", - "Repository": "CRAN", + "Repository": "RSPM", "Requirements": [ "tools" ], @@ -1039,7 +1039,10 @@ "Package": "openssl", "Version": "2.0.6", "Source": "Repository", - "Repository": "CRAN", + "Repository": "https://carpentries.r-universe.dev", + "RemoteUrl": "https://github.com/jeroen/openssl", + "RemoteRef": "v2.0.6", + "RemoteSha": "72aa50a79b99bb1eb57a2668b1c437b1adecbabb", "Requirements": [ "askpass" ], @@ -1186,7 +1189,10 @@ "Package": "ragg", "Version": "1.2.5", "Source": "Repository", - "Repository": "CRAN", + "Repository": "https://carpentries.r-universe.dev", + "RemoteUrl": "https://github.com/r-lib/ragg", + "RemoteRef": "v1.2.5", + "RemoteSha": "dafb0d8e0308c4db56abed2290f32a1ca0719307", "Requirements": [ "systemfonts", "textshaping" @@ -1197,7 +1203,7 @@ "Package": "rappdirs", "Version": "0.3.3", "Source": "Repository", - "Repository": "CRAN", + "Repository": "RSPM", "Requirements": [ "R" ], @@ -1294,7 +1300,7 @@ "Package": "rlang", "Version": "1.1.1", "Source": "Repository", - "Repository": "CRAN", + "Repository": "RSPM", "Requirements": [ "R", "utils" @@ -1305,7 +1311,7 @@ "Package": "rmarkdown", "Version": "2.21", "Source": "Repository", - "Repository": "CRAN", + "Repository": "RSPM", "Requirements": [ "R", "bslib", @@ -1419,10 +1425,7 @@ "Package": "stringi", "Version": "1.7.12", "Source": "Repository", - "Repository": "https://carpentries.r-universe.dev", - "RemoteUrl": "https://github.com/gagolews/stringi", - "RemoteRef": "v1.7.12", - "RemoteSha": "e4cf3176bc3943e6c477885be3445cbbd7d4bab6", + "Repository": "RSPM", "Requirements": [ "R", "stats", @@ -1435,7 +1438,7 @@ "Package": "stringr", "Version": "1.5.0", "Source": "Repository", - "Repository": "CRAN", + "Repository": "RSPM", "Requirements": [ "R", "cli", @@ -1459,7 +1462,10 @@ "Package": "systemfonts", "Version": "1.0.4", "Source": "Repository", - "Repository": "CRAN", + "Repository": "https://carpentries.r-universe.dev", + "RemoteUrl": "https://github.com/r-lib/systemfonts", + "RemoteRef": "v1.0.4", + "RemoteSha": "7cefacd49d39bb77e88f99d161a76a8cea28dc1b", "Requirements": [ "R", "cpp11" @@ -1521,7 +1527,10 @@ "Package": "textshaping", "Version": "0.3.6", "Source": "Repository", - "Repository": "CRAN", + "Repository": "https://carpentries.r-universe.dev", + "RemoteUrl": "https://github.com/r-lib/textshaping", + "RemoteRef": "v0.3.6", + "RemoteSha": "0ae8e32a2dab09a920db4b6a60fc10380ba1c4bc", "Requirements": [ "R", "cpp11", @@ -1683,7 +1692,7 @@ "Package": "vctrs", "Version": "0.6.2", "Source": "Repository", - "Repository": "CRAN", + "Repository": "RSPM", "Requirements": [ "R", "cli", @@ -1764,7 +1773,7 @@ "Package": "xfun", "Version": "0.39", "Source": "Repository", - "Repository": "CRAN", + "Repository": "RSPM", "Requirements": [ "stats", "tools" From 5255346e220e9bce480a6af9f080da14576a44bf Mon Sep 17 00:00:00 2001 From: Michael Milton Date: Mon, 10 Jul 2023 14:36:19 +1000 Subject: [PATCH 07/34] Update knit_exit comment --- episodes/hpc.Rmd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/episodes/hpc.Rmd b/episodes/hpc.Rmd index 0cc86ef8..50dda9d0 100644 --- a/episodes/hpc.Rmd +++ b/episodes/hpc.Rmd @@ -5,7 +5,7 @@ exercises: 2 --- ```{R, echo=FALSE} -# Handle the case when the +# Exit sensibly when Slurm isn't installed if (!nzchar(Sys.which("sbatch"))){ knitr::knit_exit("sbatch was not detected. Likely Slurm is not installed. Exiting.") } From 9c054c4c20bd7e467939a911ce2527ca04e90e7e Mon Sep 17 00:00:00 2001 From: Michael Milton Date: Mon, 10 Jul 2023 15:58:21 +1000 Subject: [PATCH 08/34] Better wording when discussing HPC Co-authored-by: Edward Yang <94523015+edoyango@users.noreply.github.com> --- episodes/hpc.Rmd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/episodes/hpc.Rmd b/episodes/hpc.Rmd index 50dda9d0..d5e6d98e 100644 --- a/episodes/hpc.Rmd +++ b/episodes/hpc.Rmd @@ -47,7 +47,7 @@ options(width = 140) ## Advantages of HPC If your analysis involves computationally intensive or long-running tasks such as training machine learning models or processing very large amounts of data, it will quickly become infeasible to use a single machine to run this. -If you are part of an organisation with access to a High Performance Computing (HPC) cluster, you can easily leverage the numerous machines with Targets to scale up your analysis. +If you have access to a High Performance Computing (HPC) cluster, you can leverage the numerous machines with Targets to scale up your analysis. This differs from the exucution we have learned so far, which spawns extra R processes on the *same machine* to speed up execution. ## Configuring Targets for Slurm From 46fc2390d5963099024b9b20724c3753d9c6e199 Mon Sep 17 00:00:00 2001 From: Michael Milton Date: Mon, 10 Jul 2023 15:59:15 +1000 Subject: [PATCH 09/34] Don't assume learner expectations Co-authored-by: Edward Yang <94523015+edoyango@users.noreply.github.com> --- episodes/hpc.Rmd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/episodes/hpc.Rmd b/episodes/hpc.Rmd index d5e6d98e..739b776b 100644 --- a/episodes/hpc.Rmd +++ b/episodes/hpc.Rmd @@ -152,7 +152,7 @@ tar_option_set( ## HPC Workers -Despite what you might expect, `crew` does not submit one Slurm job for each target. +`crew` uses a persistent worker strategy. This means that `crew` does not submit one Slurm job for each target. Instead, it uses persistent workers, meaning that you define a pool of workers when configuring the workflow. In our example above we used 3 workers. For each worker, `crew` submits a single Slurm job, and these workers will process multiple targets over their lifetime. From 589075bb32076410e93931d8109b4e0c2fabe73f Mon Sep 17 00:00:00 2001 From: Michael Milton Date: Mon, 10 Jul 2023 15:59:29 +1000 Subject: [PATCH 10/34] Update episodes/hpc.Rmd Co-authored-by: Edward Yang <94523015+edoyango@users.noreply.github.com> --- episodes/hpc.Rmd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/episodes/hpc.Rmd b/episodes/hpc.Rmd index 739b776b..e0fa0276 100644 --- a/episodes/hpc.Rmd +++ b/episodes/hpc.Rmd @@ -62,7 +62,7 @@ tar_option_set( controller = crew_controller_local(workers = 2) ) ``` -To configure this for Slurm, we just swap out the controller with a new one from the `crew.cluster` package: +To configure this for Slurm, we swap out the controller with a new one from the `crew.cluster` package: ```{R} library(crew.cluster) From 629c7aa69f6ed4e378effa601a74d13c41a0ba59 Mon Sep 17 00:00:00 2001 From: Michael Milton Date: Mon, 10 Jul 2023 16:08:34 +1000 Subject: [PATCH 11/34] Update episodes/hpc.Rmd Co-authored-by: Edward Yang <94523015+edoyango@users.noreply.github.com> --- episodes/hpc.Rmd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/episodes/hpc.Rmd b/episodes/hpc.Rmd index e0fa0276..76514c3d 100644 --- a/episodes/hpc.Rmd +++ b/episodes/hpc.Rmd @@ -163,7 +163,7 @@ We can verify that this has happened using `sacct`: sacct ``` -The upside of this approach is that we don't have to work out the minutae of how long each target takes to build, or what resources it needs. +The upside of this approach is that we don't have to know how long each target takes to build, or what resources it needs. It also means that we don't submit a lot of jobs, making our Slurm usage more efficient and easy to monitor. The downside of this mechanism is that **the resources of the worker have to be sufficient to build each of your targets**. From 5a674227dc7b9c5bee483fbcde4ad4c8bd50b678 Mon Sep 17 00:00:00 2001 From: Michael Milton Date: Mon, 10 Jul 2023 16:10:19 +1000 Subject: [PATCH 12/34] Update episodes/hpc.Rmd Co-authored-by: Edward Yang <94523015+edoyango@users.noreply.github.com> --- episodes/hpc.Rmd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/episodes/hpc.Rmd b/episodes/hpc.Rmd index 76514c3d..ece48482 100644 --- a/episodes/hpc.Rmd +++ b/episodes/hpc.Rmd @@ -52,7 +52,7 @@ This differs from the exucution we have learned so far, which spawns extra R pro ## Configuring Targets for Slurm -Fortunately, using HPC is as simple as changing the Targets `controller`. +To adapt Targets to use the Slurm HPC scheduler, we change the `controller`. In this section we will assume that our HPC uses Slurm as its job scheduler, but you can easily use other schedulers such as PBS/TORQUE, Sun Grid Engine (SGE) or LSF. In the Parallel Processing section, we used the following configuration: From 180f290c681bc6efe67198950e5d9718ac8d0897 Mon Sep 17 00:00:00 2001 From: Michael Milton Date: Tue, 11 Jul 2023 13:51:42 +1000 Subject: [PATCH 13/34] Update episodes/hpc.Rmd Co-authored-by: Edward Yang <94523015+edoyango@users.noreply.github.com> --- episodes/hpc.Rmd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/episodes/hpc.Rmd b/episodes/hpc.Rmd index ece48482..2b2503fc 100644 --- a/episodes/hpc.Rmd +++ b/episodes/hpc.Rmd @@ -53,7 +53,7 @@ This differs from the exucution we have learned so far, which spawns extra R pro ## Configuring Targets for Slurm To adapt Targets to use the Slurm HPC scheduler, we change the `controller`. -In this section we will assume that our HPC uses Slurm as its job scheduler, but you can easily use other schedulers such as PBS/TORQUE, Sun Grid Engine (SGE) or LSF. +In this section we will assume that our HPC uses Slurm as its job scheduler, but you can use other schedulers such as PBS/TORQUE, Sun Grid Engine (SGE) or LSF. In the Parallel Processing section, we used the following configuration: ```{R} From 31353d6f8721ba8dafb36e8fc9ca02e44129850d Mon Sep 17 00:00:00 2001 From: Michael Milton Date: Wed, 12 Jul 2023 11:46:42 +1000 Subject: [PATCH 14/34] Code review, and update dependencies --- episodes/hpc.Rmd | 53 ++++++++++++++---- renv/profiles/lesson-requirements/renv.lock | 59 +++++++++++---------- 2 files changed, 74 insertions(+), 38 deletions(-) diff --git a/episodes/hpc.Rmd b/episodes/hpc.Rmd index 50dda9d0..ca2f5071 100644 --- a/episodes/hpc.Rmd +++ b/episodes/hpc.Rmd @@ -48,7 +48,7 @@ options(width = 140) If your analysis involves computationally intensive or long-running tasks such as training machine learning models or processing very large amounts of data, it will quickly become infeasible to use a single machine to run this. If you are part of an organisation with access to a High Performance Computing (HPC) cluster, you can easily leverage the numerous machines with Targets to scale up your analysis. -This differs from the exucution we have learned so far, which spawns extra R processes on the *same machine* to speed up execution. +This differs from the execution we have learned so far, which spawns extra R processes on the *same machine* to speed up execution. ## Configuring Targets for Slurm @@ -62,7 +62,7 @@ tar_option_set( controller = crew_controller_local(workers = 2) ) ``` -To configure this for Slurm, we just swap out the controller with a new one from the `crew.cluster` package: +To configure this for Slurm, we just swap out the controller with a new one from the [`crew.cluster`](https://wlandau.github.io/crew.cluster/index.html) package: ```{R} library(crew.cluster) @@ -74,11 +74,17 @@ tar_option_set( ) ``` +::: aside +If you were using a scheduler other than Slurm, you would instead select [`crew_controller_lsf()`](https://wlandau.github.io/crew.cluster/reference/crew_controller_lsf.html), [`crew_controller_pbs()`](https://wlandau.github.io/crew.cluster/reference/crew_controller_pbs.html) or [`crew_controller_sge`](https://wlandau.github.io/crew.cluster/reference/crew_controller_sge.html) instead of `crew_controller_slurm`. + +These functions have their own unique arguments which are associated with the scheduler. +::: + There are a number of options you can pass to `crew_controller_slurm()` to fine-tune the Slurm execution, [which you can find here](https://wlandau.github.io/crew.cluster/reference/crew_controller_slurm.html). Here we are only using two: * `workers` sets the number of jobs that are submitted to Slurm to process targets. - * `script_lines` adds some lines to the Slurm submit script used by Targets. This is useful for loading Environment Modules and adding `#SBATCH` options. + * `script_lines` adds some lines to the Slurm submit script used by Targets. This is useful for loading Environment Modules as we have done here. Let's run the modified workflow: @@ -130,7 +136,7 @@ tar_plan( ::: challenge ## Increasing Resources -Q: How would you modify your `_targets.R` if your targets needed 200GB of RAM? +Q: How would you modify your `_targets.R` if your targets needed 100GB of RAM? ::: hint Check the arguments for [`crew_controller_slurm`](https://wlandau.github.io/crew.cluster/reference/crew_controller_slurm.html#arguments-1). @@ -142,7 +148,7 @@ tar_option_set( workers = 3, script_lines = "module load R", # Added this - slurm_memory_gigabytes_per_cpu = 200, + slurm_memory_gigabytes_per_cpu = 100, slurm_cpus_per_task = 1 ) ) @@ -150,6 +156,28 @@ tar_option_set( ::: ::: +## SBATCH Options + +The `script_lines` argument shown above can also be used to add `#SBATCH` flags, to configure your worker job. +Each entry in the vector will be treated as a new line to be added to the `sbatch` script that is generated. +However, you have to be careful to put all of your `#SBATCH` lines before any other bash commands. +`sbatch` flags [are listed here](https://slurm.schedmd.com/sbatch.html#SECTION_OPTIONS) in the Slurm documentation. +For instance, to request that the worker has a GPU available, you could do the following: +```R +tar_option_set( + controller = crew_controller_slurm( + workers = 3, + script_lines = c( + "#SBATCH --gres=gpu:1", + "module load R" + ) + ) +) +``` + +In general, it's better to use a dedicated `crew_controller_slurm` argument than to use `script_lines`, if one exists. +For example, prefer `slurm_cpus_per_task=2` to `script_lines="--cpus-per-task=2"` and set `name="my_name"` rather than using `script_lines="--job-name=my_name"`. + ## HPC Workers Despite what you might expect, `crew` does not submit one Slurm job for each target. @@ -164,9 +192,10 @@ sacct ``` The upside of this approach is that we don't have to work out the minutae of how long each target takes to build, or what resources it needs. -It also means that we don't submit a lot of jobs, making our Slurm usage more efficient and easy to monitor. +It also means that we don't submit a lot of jobs, making our Slurm usage easy to monitor. -The downside of this mechanism is that **the resources of the worker have to be sufficient to build each of your targets**. +The downside of this mechanism is that **the resources of the worker have to be sufficient to build all of your targets**. +In other words, you need to work out the maximum RAM and CPUs used across all of your targets, and specify those maximum resources in the `crew_controller_slurm()` function. ::: challenge ## Choosing a Worker @@ -181,9 +210,10 @@ To do this we might use a controller a bit like this: crew_controller_slurm( name = "cpu_worker", workers = 3, - script_lines = " -#SBATCH --cpus-per-task=8 -module load R", + script_lines = c( + "#SBATCH --cpus-per-task=8", + "module load R" + ), slurm_memory_gigabytes_per_cpu = 100 ) ``` @@ -192,7 +222,8 @@ module load R", ## Heterogeneous Workers -In some cases we may prefer heterogeneous workers, especially if some of our targets need a GPU and others need a CPU. +In some cases we may prefer to use more than one different Slurm job processing our targets, especially if some of our targets need a GPU and others need a CPU. +When we do this, we say we have "heterogeneous workers", meaning that not all worker jobs are the same as each other. To do this, we firstly define each worker configuration by adding the `name` argument to `crew_controller_slurm`. Note that this time we aren't passing it into `tar_option_set`: diff --git a/renv/profiles/lesson-requirements/renv.lock b/renv/profiles/lesson-requirements/renv.lock index 79406def..9071bac8 100644 --- a/renv/profiles/lesson-requirements/renv.lock +++ b/renv/profiles/lesson-requirements/renv.lock @@ -63,7 +63,7 @@ "Package": "R6", "Version": "2.5.1", "Source": "Repository", - "Repository": "RSPM", + "Repository": "CRAN", "Requirements": [ "R" ], @@ -114,7 +114,7 @@ "Package": "base64enc", "Version": "0.1-3", "Source": "Repository", - "Repository": "RSPM", + "Repository": "CRAN", "Requirements": [ "R" ], @@ -191,7 +191,7 @@ "Package": "bslib", "Version": "0.4.2", "Source": "Repository", - "Repository": "RSPM", + "Repository": "CRAN", "Requirements": [ "R", "base64enc", @@ -247,7 +247,7 @@ "Package": "cli", "Version": "3.6.1", "Source": "Repository", - "Repository": "RSPM", + "Repository": "CRAN", "Requirements": [ "R", "utils" @@ -373,7 +373,7 @@ "Package": "digest", "Version": "0.6.31", "Source": "Repository", - "Repository": "RSPM", + "Repository": "CRAN", "Requirements": [ "R", "utils" @@ -426,7 +426,7 @@ "Package": "ellipsis", "Version": "0.3.2", "Source": "Repository", - "Repository": "RSPM", + "Repository": "CRAN", "Requirements": [ "R", "rlang" @@ -474,7 +474,7 @@ "Package": "fontawesome", "Version": "0.5.1", "Source": "Repository", - "Repository": "RSPM", + "Repository": "CRAN", "Requirements": [ "R", "htmltools", @@ -502,7 +502,7 @@ "Package": "fs", "Version": "1.6.2", "Source": "Repository", - "Repository": "RSPM", + "Repository": "CRAN", "Requirements": [ "R", "methods" @@ -625,7 +625,7 @@ "Package": "glue", "Version": "1.6.2", "Source": "Repository", - "Repository": "RSPM", + "Repository": "CRAN", "Requirements": [ "R", "methods" @@ -725,7 +725,7 @@ "Package": "highr", "Version": "0.10", "Source": "Repository", - "Repository": "RSPM", + "Repository": "CRAN", "Requirements": [ "R", "xfun" @@ -750,7 +750,7 @@ "Package": "htmltools", "Version": "0.5.5", "Source": "Repository", - "Repository": "RSPM", + "Repository": "CRAN", "Requirements": [ "R", "base64enc", @@ -797,7 +797,7 @@ "Package": "ids", "Version": "1.0.1", "Source": "Repository", - "Repository": "RSPM", + "Repository": "CRAN", "Requirements": [ "openssl", "uuid" @@ -849,7 +849,7 @@ "Package": "jsonlite", "Version": "1.8.4", "Source": "Repository", - "Repository": "RSPM", + "Repository": "CRAN", "Requirements": [ "methods" ], @@ -912,7 +912,7 @@ "Package": "lifecycle", "Version": "1.0.3", "Source": "Repository", - "Repository": "RSPM", + "Repository": "CRAN", "Requirements": [ "R", "cli", @@ -948,7 +948,7 @@ "Package": "magrittr", "Version": "2.0.3", "Source": "Repository", - "Repository": "RSPM", + "Repository": "CRAN", "Requirements": [ "R" ], @@ -986,7 +986,7 @@ "Package": "mime", "Version": "0.12", "Source": "Repository", - "Repository": "RSPM", + "Repository": "CRAN", "Requirements": [ "tools" ], @@ -1040,13 +1040,14 @@ "Version": "2.0.6", "Source": "Repository", "Repository": "https://carpentries.r-universe.dev", + "RemoteType": "repository", "RemoteUrl": "https://github.com/jeroen/openssl", "RemoteRef": "v2.0.6", "RemoteSha": "72aa50a79b99bb1eb57a2668b1c437b1adecbabb", "Requirements": [ "askpass" ], - "Hash": "0f7cd2962e3044bb940cca4f4b5cecbe" + "Hash": "51976e7d0fcabbadb3c1baec93103756" }, "packrat": { "Package": "packrat", @@ -1190,6 +1191,7 @@ "Version": "1.2.5", "Source": "Repository", "Repository": "https://carpentries.r-universe.dev", + "RemoteType": "repository", "RemoteUrl": "https://github.com/r-lib/ragg", "RemoteRef": "v1.2.5", "RemoteSha": "dafb0d8e0308c4db56abed2290f32a1ca0719307", @@ -1197,13 +1199,13 @@ "systemfonts", "textshaping" ], - "Hash": "690bc058ea2b1b8a407d3cfe3dce3ef9" + "Hash": "fa83fe2ea7fc41d5033a0759a8172910" }, "rappdirs": { "Package": "rappdirs", "Version": "0.3.3", "Source": "Repository", - "Repository": "RSPM", + "Repository": "CRAN", "Requirements": [ "R" ], @@ -1300,7 +1302,7 @@ "Package": "rlang", "Version": "1.1.1", "Source": "Repository", - "Repository": "RSPM", + "Repository": "CRAN", "Requirements": [ "R", "utils" @@ -1311,7 +1313,7 @@ "Package": "rmarkdown", "Version": "2.21", "Source": "Repository", - "Repository": "RSPM", + "Repository": "CRAN", "Requirements": [ "R", "bslib", @@ -1426,6 +1428,7 @@ "Version": "1.7.12", "Source": "Repository", "Repository": "https://carpentries.r-universe.dev", + "RemoteType": "repository", "RemoteUrl": "https://github.com/gagolews/stringi", "RemoteRef": "v1.7.12", "RemoteSha": "e4cf3176bc3943e6c477885be3445cbbd7d4bab6", @@ -1435,13 +1438,13 @@ "tools", "utils" ], - "Hash": "ca8bd84263c77310739d2cf64d84d7c9" + "Hash": "832ef2a01603f8f5b4f2af024080d5d9" }, "stringr": { "Package": "stringr", "Version": "1.5.0", "Source": "Repository", - "Repository": "RSPM", + "Repository": "CRAN", "Requirements": [ "R", "cli", @@ -1466,6 +1469,7 @@ "Version": "1.0.4", "Source": "Repository", "Repository": "https://carpentries.r-universe.dev", + "RemoteType": "repository", "RemoteUrl": "https://github.com/r-lib/systemfonts", "RemoteRef": "v1.0.4", "RemoteSha": "7cefacd49d39bb77e88f99d161a76a8cea28dc1b", @@ -1473,7 +1477,7 @@ "R", "cpp11" ], - "Hash": "90b28393209827327de889f49935140a" + "Hash": "fd4f62a091a20ea96ecb9350b6f8d951" }, "tarchetypes": { "Package": "tarchetypes", @@ -1531,6 +1535,7 @@ "Version": "0.3.6", "Source": "Repository", "Repository": "https://carpentries.r-universe.dev", + "RemoteType": "repository", "RemoteUrl": "https://github.com/r-lib/textshaping", "RemoteRef": "v0.3.6", "RemoteSha": "0ae8e32a2dab09a920db4b6a60fc10380ba1c4bc", @@ -1539,7 +1544,7 @@ "cpp11", "systemfonts" ], - "Hash": "1ab6223d3670fac7143202cb6a2d43d5" + "Hash": "b0f9ff08a8a4885acc43248bf3e37541" }, "tibble": { "Package": "tibble", @@ -1695,7 +1700,7 @@ "Package": "vctrs", "Version": "0.6.2", "Source": "Repository", - "Repository": "RSPM", + "Repository": "CRAN", "Requirements": [ "R", "cli", @@ -1776,7 +1781,7 @@ "Package": "xfun", "Version": "0.39", "Source": "Repository", - "Repository": "RSPM", + "Repository": "CRAN", "Requirements": [ "stats", "tools" From a4829455d2ae3c84c723deddd75d35fbcc89d773 Mon Sep 17 00:00:00 2001 From: Michael Milton Date: Wed, 12 Jul 2023 15:42:06 +1000 Subject: [PATCH 15/34] Minor improvements and rewording --- episodes/hpc.Rmd | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/episodes/hpc.Rmd b/episodes/hpc.Rmd index 721ef022..61023076 100644 --- a/episodes/hpc.Rmd +++ b/episodes/hpc.Rmd @@ -62,7 +62,7 @@ tar_option_set( controller = crew_controller_local(workers = 2) ) ``` -To configure this for Slurm, we swap out the controller with a new one from the [`crew.cluster`](https://wlandau.github.io/crew.cluster/index.html) package: +To configure this for Slurm, we swap out the controller with [`crew_controller_slurm`](https://wlandau.github.io/crew.cluster/reference/crew_controller_slurm.html) a new one from the [`crew.cluster`](https://wlandau.github.io/crew.cluster/index.html) package: ```{R} library(crew.cluster) @@ -74,7 +74,7 @@ tar_option_set( ) ``` -::: aside +::: callout If you were using a scheduler other than Slurm, you would instead select [`crew_controller_lsf()`](https://wlandau.github.io/crew.cluster/reference/crew_controller_lsf.html), [`crew_controller_pbs()`](https://wlandau.github.io/crew.cluster/reference/crew_controller_pbs.html) or [`crew_controller_sge`](https://wlandau.github.io/crew.cluster/reference/crew_controller_sge.html) instead of `crew_controller_slurm`. These functions have their own unique arguments which are associated with the scheduler. @@ -180,8 +180,9 @@ For example, prefer `slurm_cpus_per_task=2` to `script_lines="--cpus-per-task=2" ## HPC Workers -`crew` uses a persistent worker strategy. This means that `crew` does not submit one Slurm job for each target. -Instead, it uses persistent workers, meaning that you define a pool of workers when configuring the workflow. +`crew` uses a persistent worker strategy. +This means that `crew` does not submit one Slurm job for each target. +Instead, you define a pool of workers when configuring the workflow. In our example above we used 3 workers. For each worker, `crew` submits a single Slurm job, and these workers will process multiple targets over their lifetime. @@ -210,11 +211,9 @@ To do this we might use a controller a bit like this: crew_controller_slurm( name = "cpu_worker", workers = 3, - script_lines = c( - "#SBATCH --cpus-per-task=8", - "module load R" - ), - slurm_memory_gigabytes_per_cpu = 100 + script_lines = "module load R", + slurm_cpus_per_task = 8, + slurm_memory_gigabytes_per_cpu = 100 / 8 ) ``` ::: @@ -222,7 +221,7 @@ crew_controller_slurm( ## Heterogeneous Workers -In some cases we may prefer to use more than one different Slurm job processing our targets, especially if some of our targets need a GPU and others need a CPU. +In some cases we may prefer to use more than one different Slurm job processing our targets, especially if some of our targets need different hardware from others, such as a GPU. When we do this, we say we have "heterogeneous workers", meaning that not all worker jobs are the same as each other. To do this, we firstly define each worker configuration by adding the `name` argument to `crew_controller_slurm`. Note that this time we aren't passing it into `tar_option_set`: @@ -250,6 +249,8 @@ tar_target( ) ``` +We can repeat this process with a different `name` argument to define multiple different workers. + ::: challenge ## Mixing GPU and CPU targets @@ -291,8 +292,10 @@ crew_controller_slurm( crew_controller_slurm( name = "gpu_worker" workers = 3, - script_lines = "#SBATCH --gres=gpu:1 -module load R", + script_lines = c( + "#SBATCH --gres=gpu:1", + "module load R" + ), slurm_memory_gigabytes_per_cpu = 200, slurm_cpus_per_task = 1 ) From 540385e92e8679d96d2db749dabe839df43889f7 Mon Sep 17 00:00:00 2001 From: Michael Milton Date: Thu, 20 Jul 2023 10:41:07 +1000 Subject: [PATCH 16/34] Install crew.cluster Co-authored-by: Edward Yang <94523015+edoyango@users.noreply.github.com> --- episodes/hpc.Rmd | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/episodes/hpc.Rmd b/episodes/hpc.Rmd index 61023076..34543d7f 100644 --- a/episodes/hpc.Rmd +++ b/episodes/hpc.Rmd @@ -55,6 +55,19 @@ This differs from the exucution we have learned so far, which spawns extra R pro To adapt Targets to use the Slurm HPC scheduler, we change the `controller`. In this section we will assume that our HPC uses Slurm as its job scheduler, but you can use other schedulers such as PBS/TORQUE, Sun Grid Engine (SGE) or LSF. +::::::::::::::::::::::::::::::::::::: {.prereq} + +### Install required packages + +You will need to install several packages to use the `crew` backend: + +```{r} +#| label: install-crew.cluster +#| eval: false +install.packages("crew.cluster", type = "source") +\``` + +::::::::::::::::::::::::::::::::::::: In the Parallel Processing section, we used the following configuration: ```{R} library(crew) From 704759d704b2c0fac3ca23d87893e54159aade6f Mon Sep 17 00:00:00 2001 From: Michael Milton Date: Thu, 20 Jul 2023 10:41:34 +1000 Subject: [PATCH 17/34] Elaborate on `sacct` Co-authored-by: Edward Yang <94523015+edoyango@users.noreply.github.com> --- episodes/hpc.Rmd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/episodes/hpc.Rmd b/episodes/hpc.Rmd index 34543d7f..d203a150 100644 --- a/episodes/hpc.Rmd +++ b/episodes/hpc.Rmd @@ -199,7 +199,7 @@ Instead, you define a pool of workers when configuring the workflow. In our example above we used 3 workers. For each worker, `crew` submits a single Slurm job, and these workers will process multiple targets over their lifetime. -We can verify that this has happened using `sacct`: +We can verify that this has happened using `sacct`, which we can use to query information about our past jobs: ```{bash} sacct From 414a742b916d1ed655a8bccbbf6ce86eef625d64 Mon Sep 17 00:00:00 2001 From: Michael Milton Date: Mon, 24 Jul 2023 18:23:44 +1000 Subject: [PATCH 18/34] Tweaks to the HPC episode --- episodes/files/functions.R | 112 +++++++++++++- episodes/hpc.Rmd | 154 ++++++++++++++------ renv/profiles/lesson-requirements/renv.lock | 5 +- 3 files changed, 221 insertions(+), 50 deletions(-) diff --git a/episodes/files/functions.R b/episodes/files/functions.R index c063edc2..431d0c45 100644 --- a/episodes/files/functions.R +++ b/episodes/files/functions.R @@ -334,6 +334,114 @@ write_example_plan <- function(plan_select) { " )", ")" ) + plan_10 <- c( + glance_slow_func, + augment_slow_func, + clean_penguin_data_func, + ' +library(crew.cluster) +library(targets) +library(tarchetypes) +library(palmerpenguins) +library(broom) +suppressPackageStartupMessages(library(tidyverse)) + +tar_option_set( + controller = crew_controller_slurm( + workers = 3, + script_lines = "module load R", + slurm_memory_gigabytes_per_cpu = 1 + ) +) + +tar_plan( + # Load raw data + tar_file_read( + penguins_data_raw, + path_to_file("penguins_raw.csv"), + read_csv(!!.x, show_col_types = FALSE) + ), + # Clean data + penguins_data = clean_penguin_data(penguins_data_raw), + # Build models + models = list( + combined_model = lm( + bill_depth_mm ~ bill_length_mm, data = penguins_data), + species_model = lm( + bill_depth_mm ~ bill_length_mm + species, data = penguins_data), + interaction_model = lm( + bill_depth_mm ~ bill_length_mm * species, data = penguins_data) + ), + # Get model summaries + tar_target( + model_summaries, + glance_with_mod_name_slow(models), + pattern = map(models) + ), + # Get model predictions + tar_target( + model_predictions, + augment_with_mod_name_slow(models), + pattern = map(models) + ) +) +') +plan_11 <- c( + ' + graphics_devices <- function(){ + system2("lshw", c("-class", "display"), stdout=TRUE, stderr=FALSE) +} + +library(crew) +library(crew.cluster) +library(targets) +library(tarchetypes) + +tar_option_set( + controller = crew_controller_group( + crew_controller_slurm( + name = "cpu_worker", + workers = 1, + script_lines = "module load R", + slurm_memory_gigabytes_per_cpu = 1, + slurm_cpus_per_task = 1 + ), + + crew_controller_slurm( + name = "gpu_worker", + workers = 1, + script_lines = c( + "#SBATCH --partition=gpuq", + "#SBATCH --gres=gpu:1", + "module load R" + ), + slurm_memory_gigabytes_per_cpu = 1, + slurm_cpus_per_task = 1 + ) + ), + resources = tar_resources( + crew = tar_resources_crew(controller = "cpu_worker") + ) +) + +tar_plan( + tar_target( + cpu_hardware, + graphics_devices(), + resources = tar_resources( + crew = tar_resources_crew(controller = "cpu_worker") + ) + ), + tar_target( + gpu_hardware, + graphics_devices(), + resources = tar_resources( + crew = tar_resources_crew(controller = "gpu_worker") + ) + ) +) + ' +) switch( as.character(plan_select), "1" = readr::write_lines(plan_1, "_targets.R"), @@ -345,7 +453,9 @@ write_example_plan <- function(plan_select) { "7" = readr::write_lines(plan_7, "_targets.R"), "8" = readr::write_lines(plan_8, "_targets.R"), "9" = readr::write_lines(plan_9, "_targets.R"), - stop("plan_select must be 1, 2, 3, 4, 5, 6, 7, 8, or 9") + "10" = readr::write_lines(plan_10, "_targets.R"), + "11" = readr::write_lines(plan_11, "_targets.R"), + stop("plan_select must be 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 or 11") ) } diff --git a/episodes/hpc.Rmd b/episodes/hpc.Rmd index d203a150..8b74da19 100644 --- a/episodes/hpc.Rmd +++ b/episodes/hpc.Rmd @@ -4,31 +4,29 @@ teaching: 10 exercises: 2 --- -```{R, echo=FALSE} +```{R} +#| echo: false # Exit sensibly when Slurm isn't installed if (!nzchar(Sys.which("sbatch"))){ knitr::knit_exit("sbatch was not detected. Likely Slurm is not installed. Exiting.") } ``` -:::::::::::::::::::::::::::::::::::::: questions +::: questions - Why would we use HPC to run Targets workflows? - How can we run Targets workflows on Slurm? -:::::::::::::::::::::::::::::::::::::::::::::::: - -::::::::::::::::::::::::::::::::::::: objectives - -- Be able to generate a report using `targets` - -:::::::::::::::::::::::::::::::::::::::::::::::: +::: -::::::::::::::::::::::::::::::::::::: instructor +::: objectives -Episode summary: Show how to write reports with Quarto +- Be able to run a Targets workflow on Slurm +- Understand how workers relate to targets +- Know how to configure Slurm jobs within targets +- Be able to create a pipeline with heterogeneous workers -:::::::::::::::::::::::::::::::::::::::::::::::: +::: ```{r} #| label: setup @@ -38,7 +36,7 @@ Episode summary: Show how to write reports with Quarto library(targets) library(tarchetypes) library(quarto) # don't actually need to load, but put here so renv catches it -source("https://raw.githubusercontent.com/joelnitta/targets-workshop/main/episodes/files/functions.R?token=$(date%20+%s)") # nolint +source("files/functions.R") # nolint # Increase width for printing tibbles options(width = 140) @@ -55,21 +53,22 @@ This differs from the exucution we have learned so far, which spawns extra R pro To adapt Targets to use the Slurm HPC scheduler, we change the `controller`. In this section we will assume that our HPC uses Slurm as its job scheduler, but you can use other schedulers such as PBS/TORQUE, Sun Grid Engine (SGE) or LSF. -::::::::::::::::::::::::::::::::::::: {.prereq} +::: {.prereq} ### Install required packages -You will need to install several packages to use the `crew` backend: +You will need to install `crew.cluster` to enable the HPC integration: ```{r} #| label: install-crew.cluster #| eval: false -install.packages("crew.cluster", type = "source") -\``` +install.packages("crew.cluster") +``` +::: -::::::::::::::::::::::::::::::::::::: In the Parallel Processing section, we used the following configuration: ```{R} +#| label: one-machine-crew library(crew) tar_option_set( controller = crew_controller_local(workers = 2) @@ -78,11 +77,13 @@ tar_option_set( To configure this for Slurm, we swap out the controller with [`crew_controller_slurm`](https://wlandau.github.io/crew.cluster/reference/crew_controller_slurm.html) a new one from the [`crew.cluster`](https://wlandau.github.io/crew.cluster/index.html) package: ```{R} +#| label: slurm-crew library(crew.cluster) tar_option_set( controller = crew_controller_slurm( workers = 3, - script_lines = "module load R" + script_lines = "module load R", + slurm_memory_gigabytes_per_cpu = 1 ) ) ``` @@ -94,14 +95,17 @@ These functions have their own unique arguments which are associated with the sc ::: There are a number of options you can pass to `crew_controller_slurm()` to fine-tune the Slurm execution, [which you can find here](https://wlandau.github.io/crew.cluster/reference/crew_controller_slurm.html). -Here we are only using two: +Here we are only using three: * `workers` sets the number of jobs that are submitted to Slurm to process targets. * `script_lines` adds some lines to the Slurm submit script used by Targets. This is useful for loading Environment Modules as we have done here. + * `slurm_memory_gigabytes_per_cpu` specifies the amount of memory we need. Let's run the modified workflow: ```{R, eval=FALSE} +#| label: slurm-workflow-show +#| eval: false source("R/packages.R") source("R/functions.R") @@ -109,7 +113,8 @@ library(crew.cluster) tar_option_set( controller = crew_controller_slurm( workers = 3, - script_lines = "module load R" + script_lines = "module load R", + slurm_memory_gigabytes_per_cpu = 1 ) ) @@ -145,11 +150,22 @@ tar_plan( ) ) ``` +```{R} +#| label: slurm-workflow-hide +#| echo: false +tar_dir({ + write_example_plan(10) + tar_make() + launcher_name <- tar_option_get("controller")$launcher$name +}) +``` + +We've successfully transferred our analysis onto a Slurm cluster! ::: challenge ## Increasing Resources -Q: How would you modify your `_targets.R` if your targets needed 100GB of RAM? +Q: How would you modify your `_targets.R` if your functions needed 2 CPUs? ::: hint Check the arguments for [`crew_controller_slurm`](https://wlandau.github.io/crew.cluster/reference/crew_controller_slurm.html#arguments-1). @@ -160,9 +176,9 @@ tar_option_set( controller = crew_controller_slurm( workers = 3, script_lines = "module load R", + slurm_memory_gigabytes_per_cpu = 1, # Added this - slurm_memory_gigabytes_per_cpu = 100, - slurm_cpus_per_task = 1 + slurm_cpus_per_task = 2 ) ) ``` @@ -183,8 +199,9 @@ tar_option_set( script_lines = c( "#SBATCH --gres=gpu:1", "module load R" - ) - ) + ), + slurm_memory_gigabytes_per_cpu = 1 + ) ) ``` @@ -196,13 +213,14 @@ For example, prefer `slurm_cpus_per_task=2` to `script_lines="--cpus-per-task=2" `crew` uses a persistent worker strategy. This means that `crew` does not submit one Slurm job for each target. Instead, you define a pool of workers when configuring the workflow. -In our example above we used 3 workers. +In our example above we specified a maximum of 3 workers. For each worker, `crew` submits a single Slurm job, and these workers will process multiple targets over their lifetime. -We can verify that this has happened using `sacct`, which we can use to query information about our past jobs: +We can verify that this has happened using `sacct`, which we can use to query information about our past jobs. +All the Slurm jobs with the same hash (the part after `crew-`) belong to the same Slurm controller: ```{bash} -sacct +sacct --starttime now-5minutes --allocations ``` The upside of this approach is that we don't have to know how long each target takes to build, or what resources it needs. @@ -289,28 +307,40 @@ tar_plan( You will need to define two different crew controllers. ::: ::: solution -```R +```{R} +#| label: heterogeneous-controllers-show +#| eval: false graphics_devices <- function(){ system2("lshw", c("-class", "display"), stdout=TRUE, stderr=FALSE) } +library(crew) library(crew.cluster) -crew_controller_slurm( - name = "cpu_worker" - workers = 3, - script_lines = "module load R", - slurm_memory_gigabytes_per_cpu = 200, - slurm_cpus_per_task = 1 -) -crew_controller_slurm( - name = "gpu_worker" - workers = 3, - script_lines = c( - "#SBATCH --gres=gpu:1", - "module load R" - ), - slurm_memory_gigabytes_per_cpu = 200, - slurm_cpus_per_task = 1 +library(targets) +library(tarchetypes) + +tar_option_set( + controller = crew_controller_group( + crew_controller_slurm( + name = "cpu_worker", + workers = 1, + script_lines = "module load R", + slurm_memory_gigabytes_per_cpu = 1, + slurm_cpus_per_task = 1 + ), + + crew_controller_slurm( + name = "gpu_worker", + workers = 1, + script_lines = c( + "#SBATCH --partition=gpuq", + "#SBATCH --gres=gpu:1", + "module load R" + ), + slurm_memory_gigabytes_per_cpu = 1, + slurm_cpus_per_task = 1 + ) + ) ) tar_plan( @@ -330,6 +360,38 @@ tar_plan( ) ) ``` +```{R} +#| label: heterogeneous-controllers-hide +#| eval: true +#| echo: false +tar_dir({ + write_example_plan(11) + tar_make() + tar_load_everything() +}) +``` +```{R} +#| label: heterogeneous-controllers-cpu-show +#| eval: false +tar_read("cpu_hardware") +``` +```{R} +#| label: heterogeneous-controllers-cpu-hide +#| eval: true +#| echo: false +cpu_hardware +``` +```{R} +#| label: heterogeneous-controllers-gpu-show +#| eval: false +tar_read("gpu_hardware") +``` +```{R} +#| label: heterogeneous-controllers-gpu-hide +#| eval: true +#| echo: false +gpu_hardware +``` ::: ::: diff --git a/renv/profiles/lesson-requirements/renv.lock b/renv/profiles/lesson-requirements/renv.lock index 9071bac8..b921bc9d 100644 --- a/renv/profiles/lesson-requirements/renv.lock +++ b/renv/profiles/lesson-requirements/renv.lock @@ -1504,7 +1504,7 @@ }, "targets": { "Package": "targets", - "Version": "1.0.0", + "Version": "1.2.0", "Source": "Repository", "Repository": "CRAN", "Requirements": [ @@ -1525,10 +1525,9 @@ "tools", "utils", "vctrs", - "withr", "yaml" ], - "Hash": "3f299989005cd3ccd101eb97623fd533" + "Hash": "a1e56d84e94c41a03391aea66c32b732" }, "textshaping": { "Package": "textshaping", From 4092364a18172de490904de5abb9350f90f76509 Mon Sep 17 00:00:00 2001 From: Michael Milton Date: Tue, 25 Jul 2023 12:02:49 +1000 Subject: [PATCH 19/34] Use crew_controller_group, and add memory example --- episodes/files/functions.R | 3 - episodes/hpc.Rmd | 119 ++++++++++++++++++++++++++++++------- 2 files changed, 99 insertions(+), 23 deletions(-) diff --git a/episodes/files/functions.R b/episodes/files/functions.R index 431d0c45..fbd48d55 100644 --- a/episodes/files/functions.R +++ b/episodes/files/functions.R @@ -418,9 +418,6 @@ tar_option_set( slurm_memory_gigabytes_per_cpu = 1, slurm_cpus_per_task = 1 ) - ), - resources = tar_resources( - crew = tar_resources_crew(controller = "cpu_worker") ) ) diff --git a/episodes/hpc.Rmd b/episodes/hpc.Rmd index 8b74da19..aca3dc30 100644 --- a/episodes/hpc.Rmd +++ b/episodes/hpc.Rmd @@ -50,9 +50,6 @@ This differs from the exucution we have learned so far, which spawns extra R pro ## Configuring Targets for Slurm -To adapt Targets to use the Slurm HPC scheduler, we change the `controller`. -In this section we will assume that our HPC uses Slurm as its job scheduler, but you can use other schedulers such as PBS/TORQUE, Sun Grid Engine (SGE) or LSF. - ::: {.prereq} ### Install required packages @@ -66,6 +63,9 @@ install.packages("crew.cluster") ``` ::: +To adapt Targets to use the Slurm HPC scheduler, we change the `controller`. +In this section we will assume that our HPC uses Slurm as its job scheduler, but you can use other schedulers such as PBS/TORQUE, Sun Grid Engine (SGE) or LSF. + In the Parallel Processing section, we used the following configuration: ```{R} #| label: one-machine-crew @@ -254,33 +254,110 @@ crew_controller_slurm( In some cases we may prefer to use more than one different Slurm job processing our targets, especially if some of our targets need different hardware from others, such as a GPU. When we do this, we say we have "heterogeneous workers", meaning that not all worker jobs are the same as each other. -To do this, we firstly define each worker configuration by adding the `name` argument to `crew_controller_slurm`. -Note that this time we aren't passing it into `tar_option_set`: +To do this, we firstly define each worker configuration by adding the `name` argument to `crew_controller_slurm`: -```{R, results="hide"} +```{R} +#| results: hide library(crew.cluster) -crew_controller_slurm( - name = "cpu_worker", - workers = 3, +small_memory <- crew_controller_slurm( + name = "small_memory", + script_lines = "module load R", + slurm_memory_gigabytes_per_cpu = 1 +) +big_memory <- crew_controller_slurm( + name = "big_memory", script_lines = "module load R", - slurm_memory_gigabytes_per_cpu = 200, - slurm_cpus_per_task = 1 + slurm_memory_gigabytes_per_cpu = 10 ) ``` -Then we specify this controller by name in each target definition: +Next, we tell Targets about these controllers using `tar_option_set` as before, with one difference: we have to combine them in a controller group: -```{R, results="hide"} -tar_target( - name = cpu_task, - command = run_model2(data), - resources = tar_resources( - crew = tar_resources_crew(controller = "cpu_worker") +```{R} +#| results: hide +tar_option_set( + controller = crew_controller_group(small_memory, big_memory) +) +``` + +Then we specify each controller by name in each target definition: + +```{R} +#| results: hide +tar_plan( + tar_target( + name = big_memory_task, + command = Sys.getenv("SBATCH_MEM_PER_CPU"), + resources = tar_resources( + crew = tar_resources_crew(controller = "big_memory") + ) + ), + tar_target( + name = small_memory_task, + command = Sys.getenv("SBATCH_MEM_PER_CPU"), + resources = tar_resources( + crew = tar_resources_crew(controller = "small_memory") + ) ) ) ``` -We can repeat this process with a different `name` argument to define multiple different workers. +When we run the pipeline, we can see the differing results: +```{R} +#| label: het-example-show +#| eval: false +tar_make() +tar_read("big_memory_task") +tar_read("small_memory_task") +``` +```{R} +#| label: het-example-hide +#| eval: true +#| echo: false +tar_dir({ + tar_script({ + library(crew) + library(crew.cluster) + library(targets) + library(tarchetypes) + + small_memory <- crew_controller_slurm( + name = "small_memory", + script_lines = "module load R", + slurm_memory_gigabytes_per_cpu = 1 + ) + big_memory <- crew_controller_slurm( + name = "big_memory", + script_lines = "module load R", + slurm_memory_gigabytes_per_cpu = 10 + ) + + tar_option_set( + controller = crew_controller_group(small_memory, big_memory) + ) + + tar_plan( + tar_target( + name = big_memory_task, + command = Sys.getenv("SLURM_MEM_PER_CPU"), + resources = tar_resources( + crew = tar_resources_crew(controller = "big_memory") + ) + ), + tar_target( + name = small_memory_task, + command = Sys.getenv("SLURM_MEM_PER_CPU"), + resources = tar_resources( + crew = tar_resources_crew(controller = "small_memory") + ) + ) + ) + }) + tar_make() + print(tar_read("big_memory_task")) + print(tar_read("small_memory_task")) +}) +``` ::: challenge ## Mixing GPU and CPU targets @@ -305,6 +382,8 @@ tar_plan( ::: hint You will need to define two different crew controllers. +Also, [you will need to request a GPU from Slurm](https://slurm.schedmd.com/gres.html#Running_Jobs). +You can find an example of this above. ::: ::: solution ```{R} @@ -326,7 +405,7 @@ tar_option_set( workers = 1, script_lines = "module load R", slurm_memory_gigabytes_per_cpu = 1, - slurm_cpus_per_task = 1 + slurm_cpus_per_task = 1 ), crew_controller_slurm( From 06b2cb96ec0c35456848ce4d5e398466d5d6527d Mon Sep 17 00:00:00 2001 From: Michael Milton Date: Wed, 26 Jul 2023 09:20:56 +1000 Subject: [PATCH 20/34] Update episodes/hpc.Rmd Co-authored-by: Edward Yang <94523015+edoyango@users.noreply.github.com> --- episodes/hpc.Rmd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/episodes/hpc.Rmd b/episodes/hpc.Rmd index aca3dc30..965eb6a2 100644 --- a/episodes/hpc.Rmd +++ b/episodes/hpc.Rmd @@ -294,7 +294,7 @@ tar_plan( ), tar_target( name = small_memory_task, - command = Sys.getenv("SBATCH_MEM_PER_CPU"), + command = Sys.getenv("SLURM_MEM_PER_CPU"), resources = tar_resources( crew = tar_resources_crew(controller = "small_memory") ) From 141e7743f4754207b235e94232a87195ae1babdb Mon Sep 17 00:00:00 2001 From: Michael Milton Date: Wed, 26 Jul 2023 09:30:27 +1000 Subject: [PATCH 21/34] Update episodes/hpc.Rmd Co-authored-by: Edward Yang <94523015+edoyango@users.noreply.github.com> --- episodes/hpc.Rmd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/episodes/hpc.Rmd b/episodes/hpc.Rmd index 965eb6a2..3158667b 100644 --- a/episodes/hpc.Rmd +++ b/episodes/hpc.Rmd @@ -287,7 +287,7 @@ Then we specify each controller by name in each target definition: tar_plan( tar_target( name = big_memory_task, - command = Sys.getenv("SBATCH_MEM_PER_CPU"), + command = Sys.getenv("SLURM_MEM_PER_CPU"), resources = tar_resources( crew = tar_resources_crew(controller = "big_memory") ) From a9c04a5025cb6939bb575ac30d4d367053dec8b0 Mon Sep 17 00:00:00 2001 From: Michael Milton Date: Tue, 9 Jul 2024 17:11:54 +1000 Subject: [PATCH 22/34] Remove some superfluous files --- episodes/renv/activate.R | 1181 ----------------- episodes/renv/profile | 1 - .../profiles/learner_profiles/renv/.gitignore | 7 - .../lesson-requirements/renv/.gitignore | 7 - .../lesson_requirements/renv/.gitignore | 7 - .../lesson-requirements/renv/.gitignore | 7 - .../lesson-requirements/renv/activate.R | 1032 -------------- .../lesson-requirements/renv/settings.json | 17 - 8 files changed, 2259 deletions(-) delete mode 100644 episodes/renv/activate.R delete mode 100644 episodes/renv/profile delete mode 100644 episodes/renv/profiles/learner_profiles/renv/.gitignore delete mode 100644 episodes/renv/profiles/lesson-requirements/renv/.gitignore delete mode 100644 episodes/renv/profiles/lesson_requirements/renv/.gitignore delete mode 100644 renv/profiles/lesson-requirements/renv/.gitignore delete mode 100644 renv/profiles/lesson-requirements/renv/activate.R delete mode 100644 renv/profiles/lesson-requirements/renv/settings.json diff --git a/episodes/renv/activate.R b/episodes/renv/activate.R deleted file mode 100644 index 6f4fb747..00000000 --- a/episodes/renv/activate.R +++ /dev/null @@ -1,1181 +0,0 @@ - -local({ - - # the requested version of renv - version <- "0.17.3" - attr(version, "sha") <- NULL - - # the project directory - project <- getwd() - - # figure out whether the autoloader is enabled - enabled <- local({ - - # first, check config option - override <- getOption("renv.config.autoloader.enabled") - if (!is.null(override)) - return(override) - - # next, check environment variables - # TODO: prefer using the configuration one in the future - envvars <- c( - "RENV_CONFIG_AUTOLOADER_ENABLED", - "RENV_AUTOLOADER_ENABLED", - "RENV_ACTIVATE_PROJECT" - ) - - for (envvar in envvars) { - envval <- Sys.getenv(envvar, unset = NA) - if (!is.na(envval)) - return(tolower(envval) %in% c("true", "t", "1")) - } - - # enable by default - TRUE - - }) - - if (!enabled) - return(FALSE) - - # avoid recursion - if (identical(getOption("renv.autoloader.running"), TRUE)) { - warning("ignoring recursive attempt to run renv autoloader") - return(invisible(TRUE)) - } - - # signal that we're loading renv during R startup - options(renv.autoloader.running = TRUE) - on.exit(options(renv.autoloader.running = NULL), add = TRUE) - - # signal that we've consented to use renv - options(renv.consent = TRUE) - - # load the 'utils' package eagerly -- this ensures that renv shims, which - # mask 'utils' packages, will come first on the search path - library(utils, lib.loc = .Library) - - # unload renv if it's already been loaded - if ("renv" %in% loadedNamespaces()) - unloadNamespace("renv") - - # load bootstrap tools - `%||%` <- function(x, y) { - if (is.null(x)) y else x - } - - catf <- function(fmt, ..., appendLF = TRUE) { - - quiet <- getOption("renv.bootstrap.quiet", default = FALSE) - if (quiet) - return(invisible()) - - msg <- sprintf(fmt, ...) - cat(msg, file = stdout(), sep = if (appendLF) "\n" else "") - - invisible(msg) - - } - - header <- function(label, - ..., - prefix = "#", - suffix = "-", - n = min(getOption("width"), 78)) - { - label <- sprintf(label, ...) - n <- max(n - nchar(label) - nchar(prefix) - 2L, 8L) - if (n <= 0) - return(paste(prefix, label)) - - tail <- paste(rep.int(suffix, n), collapse = "") - paste0(prefix, " ", label, " ", tail) - - } - - startswith <- function(string, prefix) { - substring(string, 1, nchar(prefix)) == prefix - } - - bootstrap <- function(version, library) { - - friendly <- renv_bootstrap_version_friendly(version) - section <- header(sprintf("Bootstrapping renv %s", friendly)) - catf(section) - - # attempt to download renv - catf("- Downloading renv ... ", appendLF = FALSE) - withCallingHandlers( - tarball <- renv_bootstrap_download(version), - error = function(err) { - catf("FAILED") - stop("failed to download:\n", conditionMessage(err)) - } - ) - catf("OK") - on.exit(unlink(tarball), add = TRUE) - - # now attempt to install - catf("- Installing renv ... ", appendLF = FALSE) - withCallingHandlers( - status <- renv_bootstrap_install(version, tarball, library), - error = function(err) { - catf("FAILED") - stop("failed to install:\n", conditionMessage(err)) - } - ) - catf("OK") - - # add empty line to break up bootstrapping from normal output - catf("") - - return(invisible()) - } - - renv_bootstrap_tests_running <- function() { - getOption("renv.tests.running", default = FALSE) - } - - renv_bootstrap_repos <- function() { - - # get CRAN repository - cran <- getOption("renv.repos.cran", "https://cloud.r-project.org") - - # check for repos override - repos <- Sys.getenv("RENV_CONFIG_REPOS_OVERRIDE", unset = NA) - if (!is.na(repos)) { - - # check for RSPM; if set, use a fallback repository for renv - rspm <- Sys.getenv("RSPM", unset = NA) - if (identical(rspm, repos)) - repos <- c(RSPM = rspm, CRAN = cran) - - return(repos) - - } - - # check for lockfile repositories - repos <- tryCatch(renv_bootstrap_repos_lockfile(), error = identity) - if (!inherits(repos, "error") && length(repos)) - return(repos) - - # retrieve current repos - repos <- getOption("repos") - - # ensure @CRAN@ entries are resolved - repos[repos == "@CRAN@"] <- cran - - # add in renv.bootstrap.repos if set - default <- c(FALLBACK = "https://cloud.r-project.org") - extra <- getOption("renv.bootstrap.repos", default = default) - repos <- c(repos, extra) - - # remove duplicates that might've snuck in - dupes <- duplicated(repos) | duplicated(names(repos)) - repos[!dupes] - - } - - renv_bootstrap_repos_lockfile <- function() { - - lockpath <- Sys.getenv("RENV_PATHS_LOCKFILE", unset = "renv.lock") - if (!file.exists(lockpath)) - return(NULL) - - lockfile <- tryCatch(renv_json_read(lockpath), error = identity) - if (inherits(lockfile, "error")) { - warning(lockfile) - return(NULL) - } - - repos <- lockfile$R$Repositories - if (length(repos) == 0) - return(NULL) - - keys <- vapply(repos, `[[`, "Name", FUN.VALUE = character(1)) - vals <- vapply(repos, `[[`, "URL", FUN.VALUE = character(1)) - names(vals) <- keys - - return(vals) - - } - - renv_bootstrap_download <- function(version) { - - sha <- attr(version, "sha", exact = TRUE) - - methods <- if (!is.null(sha)) { - - # attempting to bootstrap a development version of renv - c( - function() renv_bootstrap_download_tarball(sha), - function() renv_bootstrap_download_github(sha) - ) - - } else { - - # attempting to bootstrap a release version of renv - c( - function() renv_bootstrap_download_tarball(version), - function() renv_bootstrap_download_cran_latest(version), - function() renv_bootstrap_download_cran_archive(version) - ) - - } - - for (method in methods) { - path <- tryCatch(method(), error = identity) - if (is.character(path) && file.exists(path)) - return(path) - } - - stop("All download methods failed") - - } - - renv_bootstrap_download_impl <- function(url, destfile) { - - mode <- "wb" - - # https://bugs.r-project.org/bugzilla/show_bug.cgi?id=17715 - fixup <- - Sys.info()[["sysname"]] == "Windows" && - substring(url, 1L, 5L) == "file:" - - if (fixup) - mode <- "w+b" - - args <- list( - url = url, - destfile = destfile, - mode = mode, - quiet = TRUE - ) - - if ("headers" %in% names(formals(utils::download.file))) - args$headers <- renv_bootstrap_download_custom_headers(url) - - do.call(utils::download.file, args) - - } - - renv_bootstrap_download_custom_headers <- function(url) { - - headers <- getOption("renv.download.headers") - if (is.null(headers)) - return(character()) - - if (!is.function(headers)) - stopf("'renv.download.headers' is not a function") - - headers <- headers(url) - if (length(headers) == 0L) - return(character()) - - if (is.list(headers)) - headers <- unlist(headers, recursive = FALSE, use.names = TRUE) - - ok <- - is.character(headers) && - is.character(names(headers)) && - all(nzchar(names(headers))) - - if (!ok) - stop("invocation of 'renv.download.headers' did not return a named character vector") - - headers - - } - - renv_bootstrap_download_cran_latest <- function(version) { - - spec <- renv_bootstrap_download_cran_latest_find(version) - type <- spec$type - repos <- spec$repos - - baseurl <- utils::contrib.url(repos = repos, type = type) - ext <- if (identical(type, "source")) - ".tar.gz" - else if (Sys.info()[["sysname"]] == "Windows") - ".zip" - else - ".tgz" - name <- sprintf("renv_%s%s", version, ext) - url <- paste(baseurl, name, sep = "/") - - destfile <- file.path(tempdir(), name) - status <- tryCatch( - renv_bootstrap_download_impl(url, destfile), - condition = identity - ) - - if (inherits(status, "condition")) - return(FALSE) - - # report success and return - destfile - - } - - renv_bootstrap_download_cran_latest_find <- function(version) { - - # check whether binaries are supported on this system - binary <- - getOption("renv.bootstrap.binary", default = TRUE) && - !identical(.Platform$pkgType, "source") && - !identical(getOption("pkgType"), "source") && - Sys.info()[["sysname"]] %in% c("Darwin", "Windows") - - types <- c(if (binary) "binary", "source") - - # iterate over types + repositories - for (type in types) { - for (repos in renv_bootstrap_repos()) { - - # retrieve package database - db <- tryCatch( - as.data.frame( - utils::available.packages(type = type, repos = repos), - stringsAsFactors = FALSE - ), - error = identity - ) - - if (inherits(db, "error")) - next - - # check for compatible entry - entry <- db[db$Package %in% "renv" & db$Version %in% version, ] - if (nrow(entry) == 0) - next - - # found it; return spec to caller - spec <- list(entry = entry, type = type, repos = repos) - return(spec) - - } - } - - # if we got here, we failed to find renv - fmt <- "renv %s is not available from your declared package repositories" - stop(sprintf(fmt, version)) - - } - - renv_bootstrap_download_cran_archive <- function(version) { - - name <- sprintf("renv_%s.tar.gz", version) - repos <- renv_bootstrap_repos() - urls <- file.path(repos, "src/contrib/Archive/renv", name) - destfile <- file.path(tempdir(), name) - - for (url in urls) { - - status <- tryCatch( - renv_bootstrap_download_impl(url, destfile), - condition = identity - ) - - if (identical(status, 0L)) - return(destfile) - - } - - return(FALSE) - - } - - renv_bootstrap_download_tarball <- function(version) { - - # if the user has provided the path to a tarball via - # an environment variable, then use it - tarball <- Sys.getenv("RENV_BOOTSTRAP_TARBALL", unset = NA) - if (is.na(tarball)) - return() - - # allow directories - if (dir.exists(tarball)) { - name <- sprintf("renv_%s.tar.gz", version) - tarball <- file.path(tarball, name) - } - - # bail if it doesn't exist - if (!file.exists(tarball)) { - - # let the user know we weren't able to honour their request - fmt <- "- RENV_BOOTSTRAP_TARBALL is set (%s) but does not exist." - msg <- sprintf(fmt, tarball) - warning(msg) - - # bail - return() - - } - - catf("- Using local tarball '%s'.", tarball) - tarball - - } - - renv_bootstrap_download_github <- function(version) { - - enabled <- Sys.getenv("RENV_BOOTSTRAP_FROM_GITHUB", unset = "TRUE") - if (!identical(enabled, "TRUE")) - return(FALSE) - - # prepare download options - pat <- Sys.getenv("GITHUB_PAT") - if (nzchar(Sys.which("curl")) && nzchar(pat)) { - fmt <- "--location --fail --header \"Authorization: token %s\"" - extra <- sprintf(fmt, pat) - saved <- options("download.file.method", "download.file.extra") - options(download.file.method = "curl", download.file.extra = extra) - on.exit(do.call(base::options, saved), add = TRUE) - } else if (nzchar(Sys.which("wget")) && nzchar(pat)) { - fmt <- "--header=\"Authorization: token %s\"" - extra <- sprintf(fmt, pat) - saved <- options("download.file.method", "download.file.extra") - options(download.file.method = "wget", download.file.extra = extra) - on.exit(do.call(base::options, saved), add = TRUE) - } - - url <- file.path("https://api.github.com/repos/rstudio/renv/tarball", version) - name <- sprintf("renv_%s.tar.gz", version) - destfile <- file.path(tempdir(), name) - - status <- tryCatch( - renv_bootstrap_download_impl(url, destfile), - condition = identity - ) - - if (!identical(status, 0L)) - return(FALSE) - - renv_bootstrap_download_augment(destfile) - - return(destfile) - - } - - # Add Sha to DESCRIPTION. This is stop gap until #890, after which we - # can use renv::install() to fully capture metadata. - renv_bootstrap_download_augment <- function(destfile) { - sha <- renv_bootstrap_git_extract_sha1_tar(destfile) - if (is.null(sha)) { - return() - } - - # Untar - tempdir <- tempfile("renv-github-") - on.exit(unlink(tempdir, recursive = TRUE), add = TRUE) - untar(destfile, exdir = tempdir) - pkgdir <- dir(tempdir, full.names = TRUE)[[1]] - - # Modify description - desc_path <- file.path(pkgdir, "DESCRIPTION") - desc_lines <- readLines(desc_path) - remotes_fields <- c( - "RemoteType: github", - "RemoteHost: api.github.com", - "RemoteRepo: renv", - "RemoteUsername: rstudio", - "RemotePkgRef: rstudio/renv", - paste("RemoteRef: ", sha), - paste("RemoteSha: ", sha) - ) - writeLines(c(desc_lines[desc_lines != ""], remotes_fields), con = desc_path) - - # Re-tar - local({ - old <- setwd(tempdir) - on.exit(setwd(old), add = TRUE) - - tar(destfile, compression = "gzip") - }) - invisible() - } - - # Extract the commit hash from a git archive. Git archives include the SHA1 - # hash as the comment field of the tarball pax extended header - # (see https://www.kernel.org/pub/software/scm/git/docs/git-archive.html) - # For GitHub archives this should be the first header after the default one - # (512 byte) header. - renv_bootstrap_git_extract_sha1_tar <- function(bundle) { - - # open the bundle for reading - # We use gzcon for everything because (from ?gzcon) - # > Reading from a connection which does not supply a ‘gzip’ magic - # > header is equivalent to reading from the original connection - conn <- gzcon(file(bundle, open = "rb", raw = TRUE)) - on.exit(close(conn)) - - # The default pax header is 512 bytes long and the first pax extended header - # with the comment should be 51 bytes long - # `52 comment=` (11 chars) + 40 byte SHA1 hash - len <- 0x200 + 0x33 - res <- rawToChar(readBin(conn, "raw", n = len)[0x201:len]) - - if (grepl("^52 comment=", res)) { - sub("52 comment=", "", res) - } else { - NULL - } - } - - renv_bootstrap_install <- function(version, tarball, library) { - - # attempt to install it into project library - dir.create(library, showWarnings = FALSE, recursive = TRUE) - output <- renv_bootstrap_install_impl(library, tarball) - - # check for successful install - status <- attr(output, "status") - if (is.null(status) || identical(status, 0L)) - return(status) - - # an error occurred; report it - header <- "installation of renv failed" - lines <- paste(rep.int("=", nchar(header)), collapse = "") - text <- paste(c(header, lines, output), collapse = "\n") - stop(text) - - } - - renv_bootstrap_install_impl <- function(library, tarball) { - - # invoke using system2 so we can capture and report output - bin <- R.home("bin") - exe <- if (Sys.info()[["sysname"]] == "Windows") "R.exe" else "R" - R <- file.path(bin, exe) - - args <- c( - "--vanilla", "CMD", "INSTALL", "--no-multiarch", - "-l", shQuote(path.expand(library)), - shQuote(path.expand(tarball)) - ) - - system2(R, args, stdout = TRUE, stderr = TRUE) - - } - - renv_bootstrap_platform_prefix <- function() { - - # construct version prefix - version <- paste(R.version$major, R.version$minor, sep = ".") - prefix <- paste("R", numeric_version(version)[1, 1:2], sep = "-") - - # include SVN revision for development versions of R - # (to avoid sharing platform-specific artefacts with released versions of R) - devel <- - identical(R.version[["status"]], "Under development (unstable)") || - identical(R.version[["nickname"]], "Unsuffered Consequences") - - if (devel) - prefix <- paste(prefix, R.version[["svn rev"]], sep = "-r") - - # build list of path components - components <- c(prefix, R.version$platform) - - # include prefix if provided by user - prefix <- renv_bootstrap_platform_prefix_impl() - if (!is.na(prefix) && nzchar(prefix)) - components <- c(prefix, components) - - # build prefix - paste(components, collapse = "/") - - } - - renv_bootstrap_platform_prefix_impl <- function() { - - # if an explicit prefix has been supplied, use it - prefix <- Sys.getenv("RENV_PATHS_PREFIX", unset = NA) - if (!is.na(prefix)) - return(prefix) - - # if the user has requested an automatic prefix, generate it - auto <- Sys.getenv("RENV_PATHS_PREFIX_AUTO", unset = NA) - if (auto %in% c("TRUE", "True", "true", "1")) - return(renv_bootstrap_platform_prefix_auto()) - - # empty string on failure - "" - - } - - renv_bootstrap_platform_prefix_auto <- function() { - - prefix <- tryCatch(renv_bootstrap_platform_os(), error = identity) - if (inherits(prefix, "error") || prefix %in% "unknown") { - - msg <- paste( - "failed to infer current operating system", - "please file a bug report at https://github.com/rstudio/renv/issues", - sep = "; " - ) - - warning(msg) - - } - - prefix - - } - - renv_bootstrap_platform_os <- function() { - - sysinfo <- Sys.info() - sysname <- sysinfo[["sysname"]] - - # handle Windows + macOS up front - if (sysname == "Windows") - return("windows") - else if (sysname == "Darwin") - return("macos") - - # check for os-release files - for (file in c("/etc/os-release", "/usr/lib/os-release")) - if (file.exists(file)) - return(renv_bootstrap_platform_os_via_os_release(file, sysinfo)) - - # check for redhat-release files - if (file.exists("/etc/redhat-release")) - return(renv_bootstrap_platform_os_via_redhat_release()) - - "unknown" - - } - - renv_bootstrap_platform_os_via_os_release <- function(file, sysinfo) { - - # read /etc/os-release - release <- utils::read.table( - file = file, - sep = "=", - quote = c("\"", "'"), - col.names = c("Key", "Value"), - comment.char = "#", - stringsAsFactors = FALSE - ) - - vars <- as.list(release$Value) - names(vars) <- release$Key - - # get os name - os <- tolower(sysinfo[["sysname"]]) - - # read id - id <- "unknown" - for (field in c("ID", "ID_LIKE")) { - if (field %in% names(vars) && nzchar(vars[[field]])) { - id <- vars[[field]] - break - } - } - - # read version - version <- "unknown" - for (field in c("UBUNTU_CODENAME", "VERSION_CODENAME", "VERSION_ID", "BUILD_ID")) { - if (field %in% names(vars) && nzchar(vars[[field]])) { - version <- vars[[field]] - break - } - } - - # join together - paste(c(os, id, version), collapse = "-") - - } - - renv_bootstrap_platform_os_via_redhat_release <- function() { - - # read /etc/redhat-release - contents <- readLines("/etc/redhat-release", warn = FALSE) - - # infer id - id <- if (grepl("centos", contents, ignore.case = TRUE)) - "centos" - else if (grepl("redhat", contents, ignore.case = TRUE)) - "redhat" - else - "unknown" - - # try to find a version component (very hacky) - version <- "unknown" - - parts <- strsplit(contents, "[[:space:]]")[[1L]] - for (part in parts) { - - nv <- tryCatch(numeric_version(part), error = identity) - if (inherits(nv, "error")) - next - - version <- nv[1, 1] - break - - } - - paste(c("linux", id, version), collapse = "-") - - } - - renv_bootstrap_library_root_name <- function(project) { - - # use project name as-is if requested - asis <- Sys.getenv("RENV_PATHS_LIBRARY_ROOT_ASIS", unset = "FALSE") - if (asis) - return(basename(project)) - - # otherwise, disambiguate based on project's path - id <- substring(renv_bootstrap_hash_text(project), 1L, 8L) - paste(basename(project), id, sep = "-") - - } - - renv_bootstrap_library_root <- function(project) { - - prefix <- renv_bootstrap_profile_prefix() - - path <- Sys.getenv("RENV_PATHS_LIBRARY", unset = NA) - if (!is.na(path)) - return(paste(c(path, prefix), collapse = "/")) - - path <- renv_bootstrap_library_root_impl(project) - if (!is.null(path)) { - name <- renv_bootstrap_library_root_name(project) - return(paste(c(path, prefix, name), collapse = "/")) - } - - renv_bootstrap_paths_renv("library", project = project) - - } - - renv_bootstrap_library_root_impl <- function(project) { - - root <- Sys.getenv("RENV_PATHS_LIBRARY_ROOT", unset = NA) - if (!is.na(root)) - return(root) - - type <- renv_bootstrap_project_type(project) - if (identical(type, "package")) { - userdir <- renv_bootstrap_user_dir() - return(file.path(userdir, "library")) - } - - } - - renv_bootstrap_validate_version <- function(version, description = NULL) { - - # resolve description file - description <- description %||% { - path <- getNamespaceInfo("renv", "path") - packageDescription("renv", lib.loc = dirname(path)) - } - - # check whether requested version 'version' matches loaded version of renv - sha <- attr(version, "sha", exact = TRUE) - valid <- if (!is.null(sha)) - renv_bootstrap_validate_version_dev(sha, description) - else - renv_bootstrap_validate_version_release(version, description) - - if (valid) - return(TRUE) - - # the loaded version of renv doesn't match the requested version; - # give the user instructions on how to proceed - remote <- if (!is.null(description[["RemoteSha"]])) { - paste("rstudio/renv", description[["RemoteSha"]], sep = "@") - } else { - paste("renv", description[["Version"]], sep = "@") - } - - # display both loaded version + sha if available - friendly <- renv_bootstrap_version_friendly( - version = description[["Version"]], - sha = description[["RemoteSha"]] - ) - - fmt <- paste( - "renv %1$s was loaded from project library, but this project is configured to use renv %2$s.", - "- Use `renv::record(\"%3$s\")` to record renv %1$s in the lockfile.", - "- Use `renv::restore(packages = \"renv\")` to install renv %2$s into the project library.", - sep = "\n" - ) - catf(fmt, friendly, renv_bootstrap_version_friendly(version), remote) - - FALSE - - } - - renv_bootstrap_validate_version_dev <- function(version, description) { - expected <- description[["RemoteSha"]] - is.character(expected) && startswith(expected, version) - } - - renv_bootstrap_validate_version_release <- function(version, description) { - expected <- description[["Version"]] - is.character(expected) && identical(expected, version) - } - - renv_bootstrap_hash_text <- function(text) { - - hashfile <- tempfile("renv-hash-") - on.exit(unlink(hashfile), add = TRUE) - - writeLines(text, con = hashfile) - tools::md5sum(hashfile) - - } - - renv_bootstrap_load <- function(project, libpath, version) { - - # try to load renv from the project library - if (!requireNamespace("renv", lib.loc = libpath, quietly = TRUE)) - return(FALSE) - - # warn if the version of renv loaded does not match - renv_bootstrap_validate_version(version) - - # execute renv load hooks, if any - hooks <- getHook("renv::autoload") - for (hook in hooks) - if (is.function(hook)) - tryCatch(hook(), error = warning) - - # load the project - renv::load(project) - - TRUE - - } - - renv_bootstrap_profile_load <- function(project) { - - # if RENV_PROFILE is already set, just use that - profile <- Sys.getenv("RENV_PROFILE", unset = NA) - if (!is.na(profile) && nzchar(profile)) - return(profile) - - # check for a profile file (nothing to do if it doesn't exist) - path <- renv_bootstrap_paths_renv("profile", profile = FALSE, project = project) - if (!file.exists(path)) - return(NULL) - - # read the profile, and set it if it exists - contents <- readLines(path, warn = FALSE) - if (length(contents) == 0L) - return(NULL) - - # set RENV_PROFILE - profile <- contents[[1L]] - if (!profile %in% c("", "default")) - Sys.setenv(RENV_PROFILE = profile) - - profile - - } - - renv_bootstrap_profile_prefix <- function() { - profile <- renv_bootstrap_profile_get() - if (!is.null(profile)) - return(file.path("profiles", profile, "renv")) - } - - renv_bootstrap_profile_get <- function() { - profile <- Sys.getenv("RENV_PROFILE", unset = "") - renv_bootstrap_profile_normalize(profile) - } - - renv_bootstrap_profile_set <- function(profile) { - profile <- renv_bootstrap_profile_normalize(profile) - if (is.null(profile)) - Sys.unsetenv("RENV_PROFILE") - else - Sys.setenv(RENV_PROFILE = profile) - } - - renv_bootstrap_profile_normalize <- function(profile) { - - if (is.null(profile) || profile %in% c("", "default")) - return(NULL) - - profile - - } - - renv_bootstrap_path_absolute <- function(path) { - - substr(path, 1L, 1L) %in% c("~", "/", "\\") || ( - substr(path, 1L, 1L) %in% c(letters, LETTERS) && - substr(path, 2L, 3L) %in% c(":/", ":\\") - ) - - } - - renv_bootstrap_paths_renv <- function(..., profile = TRUE, project = NULL) { - renv <- Sys.getenv("RENV_PATHS_RENV", unset = "renv") - root <- if (renv_bootstrap_path_absolute(renv)) NULL else project - prefix <- if (profile) renv_bootstrap_profile_prefix() - components <- c(root, renv, prefix, ...) - paste(components, collapse = "/") - } - - renv_bootstrap_project_type <- function(path) { - - descpath <- file.path(path, "DESCRIPTION") - if (!file.exists(descpath)) - return("unknown") - - desc <- tryCatch( - read.dcf(descpath, all = TRUE), - error = identity - ) - - if (inherits(desc, "error")) - return("unknown") - - type <- desc$Type - if (!is.null(type)) - return(tolower(type)) - - package <- desc$Package - if (!is.null(package)) - return("package") - - "unknown" - - } - - renv_bootstrap_user_dir <- function() { - dir <- renv_bootstrap_user_dir_impl() - path.expand(chartr("\\", "/", dir)) - } - - renv_bootstrap_user_dir_impl <- function() { - - # use local override if set - override <- getOption("renv.userdir.override") - if (!is.null(override)) - return(override) - - # use R_user_dir if available - tools <- asNamespace("tools") - if (is.function(tools$R_user_dir)) - return(tools$R_user_dir("renv", "cache")) - - # try using our own backfill for older versions of R - envvars <- c("R_USER_CACHE_DIR", "XDG_CACHE_HOME") - for (envvar in envvars) { - root <- Sys.getenv(envvar, unset = NA) - if (!is.na(root)) - return(file.path(root, "R/renv")) - } - - # use platform-specific default fallbacks - if (Sys.info()[["sysname"]] == "Windows") - file.path(Sys.getenv("LOCALAPPDATA"), "R/cache/R/renv") - else if (Sys.info()[["sysname"]] == "Darwin") - "~/Library/Caches/org.R-project.R/R/renv" - else - "~/.cache/R/renv" - - } - - renv_bootstrap_version_friendly <- function(version, sha = NULL) { - sha <- sha %||% attr(version, "sha", exact = TRUE) - parts <- c(version, sprintf("[sha: %s]", substring(sha, 1L, 7L))) - paste(parts, collapse = " ") - } - - renv_bootstrap_run <- function(version, libpath) { - - # perform bootstrap - bootstrap(version, libpath) - - # exit early if we're just testing bootstrap - if (!is.na(Sys.getenv("RENV_BOOTSTRAP_INSTALL_ONLY", unset = NA))) - return(TRUE) - - # try again to load - if (requireNamespace("renv", lib.loc = libpath, quietly = TRUE)) { - return(renv::load(project = getwd())) - } - - # failed to download or load renv; warn the user - msg <- c( - "Failed to find an renv installation: the project will not be loaded.", - "Use `renv::activate()` to re-initialize the project." - ) - - warning(paste(msg, collapse = "\n"), call. = FALSE) - - } - - - renv_bootstrap_in_rstudio <- function() { - commandArgs()[[1]] == "RStudio" - } - - renv_json_read <- function(file = NULL, text = NULL) { - - jlerr <- NULL - - # if jsonlite is loaded, use that instead - if ("jsonlite" %in% loadedNamespaces()) { - - json <- catch(renv_json_read_jsonlite(file, text)) - if (!inherits(json, "error")) - return(json) - - jlerr <- json - - } - - # otherwise, fall back to the default JSON reader - json <- catch(renv_json_read_default(file, text)) - if (!inherits(json, "error")) - return(json) - - # report an error - if (!is.null(jlerr)) - stop(jlerr) - else - stop(json) - - } - - renv_json_read_jsonlite <- function(file = NULL, text = NULL) { - text <- paste(text %||% read(file), collapse = "\n") - jsonlite::fromJSON(txt = text, simplifyVector = FALSE) - } - - renv_json_read_default <- function(file = NULL, text = NULL) { - - # find strings in the JSON - text <- paste(text %||% read(file), collapse = "\n") - pattern <- '["](?:(?:\\\\.)|(?:[^"\\\\]))*?["]' - locs <- gregexpr(pattern, text, perl = TRUE)[[1]] - - # if any are found, replace them with placeholders - replaced <- text - strings <- character() - replacements <- character() - - if (!identical(c(locs), -1L)) { - - # get the string values - starts <- locs - ends <- locs + attr(locs, "match.length") - 1L - strings <- substring(text, starts, ends) - - # only keep those requiring escaping - strings <- grep("[[\\]{}:]", strings, perl = TRUE, value = TRUE) - - # compute replacements - replacements <- sprintf('"\032%i\032"', seq_along(strings)) - - # replace the strings - mapply(function(string, replacement) { - replaced <<- sub(string, replacement, replaced, fixed = TRUE) - }, strings, replacements) - - } - - # transform the JSON into something the R parser understands - transformed <- replaced - transformed <- gsub("{}", "`names<-`(list(), character())", transformed, fixed = TRUE) - transformed <- gsub("[[{]", "list(", transformed, perl = TRUE) - transformed <- gsub("[]}]", ")", transformed, perl = TRUE) - transformed <- gsub(":", "=", transformed, fixed = TRUE) - text <- paste(transformed, collapse = "\n") - - # parse it - json <- parse(text = text, keep.source = FALSE, srcfile = NULL)[[1L]] - - # construct map between source strings, replaced strings - map <- as.character(parse(text = strings)) - names(map) <- as.character(parse(text = replacements)) - - # convert to list - map <- as.list(map) - - # remap strings in object - remapped <- renv_json_remap(json, map) - - # evaluate - eval(remapped, envir = baseenv()) - - } - - renv_json_remap <- function(json, map) { - - # fix names - if (!is.null(names(json))) { - lhs <- match(names(json), names(map), nomatch = 0L) - rhs <- match(names(map), names(json), nomatch = 0L) - names(json)[rhs] <- map[lhs] - } - - # fix values - if (is.character(json)) - return(map[[json]] %||% json) - - # handle true, false, null - if (is.name(json)) { - text <- as.character(json) - if (text == "true") - return(TRUE) - else if (text == "false") - return(FALSE) - else if (text == "null") - return(NULL) - } - - # recurse - if (is.recursive(json)) { - for (i in seq_along(json)) { - json[i] <- list(renv_json_remap(json[[i]], map)) - } - } - - json - - } - - # load the renv profile, if any - renv_bootstrap_profile_load(project) - - # construct path to library root - root <- renv_bootstrap_library_root(project) - - # construct library prefix for platform - prefix <- renv_bootstrap_platform_prefix() - - # construct full libpath - libpath <- file.path(root, prefix) - - # attempt to load - if (renv_bootstrap_load(project, libpath, version)) - return(TRUE) - - if (renv_bootstrap_in_rstudio()) { - setHook("rstudio.sessionInit", function(...) { - renv_bootstrap_run(version, libpath) - - # Work around buglet in RStudio if hook uses readline - tryCatch( - { - tools <- as.environment("tools:rstudio") - tools$.rs.api.sendToConsole("", echo = FALSE, focus = FALSE) - }, - error = function(cnd) {} - ) - }) - } else { - renv_bootstrap_run(version, libpath) - } - - invisible() - -}) diff --git a/episodes/renv/profile b/episodes/renv/profile deleted file mode 100644 index 6d4023b5..00000000 --- a/episodes/renv/profile +++ /dev/null @@ -1 +0,0 @@ -lesson-requirements diff --git a/episodes/renv/profiles/learner_profiles/renv/.gitignore b/episodes/renv/profiles/learner_profiles/renv/.gitignore deleted file mode 100644 index 0ec0cbba..00000000 --- a/episodes/renv/profiles/learner_profiles/renv/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ -library/ -local/ -cellar/ -lock/ -python/ -sandbox/ -staging/ diff --git a/episodes/renv/profiles/lesson-requirements/renv/.gitignore b/episodes/renv/profiles/lesson-requirements/renv/.gitignore deleted file mode 100644 index 0ec0cbba..00000000 --- a/episodes/renv/profiles/lesson-requirements/renv/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ -library/ -local/ -cellar/ -lock/ -python/ -sandbox/ -staging/ diff --git a/episodes/renv/profiles/lesson_requirements/renv/.gitignore b/episodes/renv/profiles/lesson_requirements/renv/.gitignore deleted file mode 100644 index 0ec0cbba..00000000 --- a/episodes/renv/profiles/lesson_requirements/renv/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ -library/ -local/ -cellar/ -lock/ -python/ -sandbox/ -staging/ diff --git a/renv/profiles/lesson-requirements/renv/.gitignore b/renv/profiles/lesson-requirements/renv/.gitignore deleted file mode 100644 index 0ec0cbba..00000000 --- a/renv/profiles/lesson-requirements/renv/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ -library/ -local/ -cellar/ -lock/ -python/ -sandbox/ -staging/ diff --git a/renv/profiles/lesson-requirements/renv/activate.R b/renv/profiles/lesson-requirements/renv/activate.R deleted file mode 100644 index a8fdc320..00000000 --- a/renv/profiles/lesson-requirements/renv/activate.R +++ /dev/null @@ -1,1032 +0,0 @@ - -local({ - - # the requested version of renv - version <- "0.17.3" - - # the project directory - project <- getwd() - - # figure out whether the autoloader is enabled - enabled <- local({ - - # first, check config option - override <- getOption("renv.config.autoloader.enabled") - if (!is.null(override)) - return(override) - - # next, check environment variables - # TODO: prefer using the configuration one in the future - envvars <- c( - "RENV_CONFIG_AUTOLOADER_ENABLED", - "RENV_AUTOLOADER_ENABLED", - "RENV_ACTIVATE_PROJECT" - ) - - for (envvar in envvars) { - envval <- Sys.getenv(envvar, unset = NA) - if (!is.na(envval)) - return(tolower(envval) %in% c("true", "t", "1")) - } - - # enable by default - TRUE - - }) - - if (!enabled) - return(FALSE) - - # avoid recursion - if (identical(getOption("renv.autoloader.running"), TRUE)) { - warning("ignoring recursive attempt to run renv autoloader") - return(invisible(TRUE)) - } - - # signal that we're loading renv during R startup - options(renv.autoloader.running = TRUE) - on.exit(options(renv.autoloader.running = NULL), add = TRUE) - - # signal that we've consented to use renv - options(renv.consent = TRUE) - - # load the 'utils' package eagerly -- this ensures that renv shims, which - # mask 'utils' packages, will come first on the search path - library(utils, lib.loc = .Library) - - # unload renv if it's already been loaded - if ("renv" %in% loadedNamespaces()) - unloadNamespace("renv") - - # load bootstrap tools - `%||%` <- function(x, y) { - if (is.environment(x) || length(x)) x else y - } - - `%??%` <- function(x, y) { - if (is.null(x)) y else x - } - - bootstrap <- function(version, library) { - - # attempt to download renv - tarball <- tryCatch(renv_bootstrap_download(version), error = identity) - if (inherits(tarball, "error")) - stop("failed to download renv ", version) - - # now attempt to install - status <- tryCatch(renv_bootstrap_install(version, tarball, library), error = identity) - if (inherits(status, "error")) - stop("failed to install renv ", version) - - } - - renv_bootstrap_tests_running <- function() { - getOption("renv.tests.running", default = FALSE) - } - - renv_bootstrap_repos <- function() { - - # get CRAN repository - cran <- getOption("renv.repos.cran", "https://cloud.r-project.org") - - # check for repos override - repos <- Sys.getenv("RENV_CONFIG_REPOS_OVERRIDE", unset = NA) - if (!is.na(repos)) { - - # check for RSPM; if set, use a fallback repository for renv - rspm <- Sys.getenv("RSPM", unset = NA) - if (identical(rspm, repos)) - repos <- c(RSPM = rspm, CRAN = cran) - - return(repos) - - } - - # check for lockfile repositories - repos <- tryCatch(renv_bootstrap_repos_lockfile(), error = identity) - if (!inherits(repos, "error") && length(repos)) - return(repos) - - # if we're testing, re-use the test repositories - if (renv_bootstrap_tests_running()) { - repos <- getOption("renv.tests.repos") - if (!is.null(repos)) - return(repos) - } - - # retrieve current repos - repos <- getOption("repos") - - # ensure @CRAN@ entries are resolved - repos[repos == "@CRAN@"] <- cran - - # add in renv.bootstrap.repos if set - default <- c(FALLBACK = "https://cloud.r-project.org") - extra <- getOption("renv.bootstrap.repos", default = default) - repos <- c(repos, extra) - - # remove duplicates that might've snuck in - dupes <- duplicated(repos) | duplicated(names(repos)) - repos[!dupes] - - } - - renv_bootstrap_repos_lockfile <- function() { - - lockpath <- Sys.getenv("RENV_PATHS_LOCKFILE", unset = "renv.lock") - if (!file.exists(lockpath)) - return(NULL) - - lockfile <- tryCatch(renv_json_read(lockpath), error = identity) - if (inherits(lockfile, "error")) { - warning(lockfile) - return(NULL) - } - - repos <- lockfile$R$Repositories - if (length(repos) == 0) - return(NULL) - - keys <- vapply(repos, `[[`, "Name", FUN.VALUE = character(1)) - vals <- vapply(repos, `[[`, "URL", FUN.VALUE = character(1)) - names(vals) <- keys - - return(vals) - - } - - renv_bootstrap_download <- function(version) { - - # if the renv version number has 4 components, assume it must - # be retrieved via github - nv <- numeric_version(version) - components <- unclass(nv)[[1]] - - # if this appears to be a development version of 'renv', we'll - # try to restore from github - dev <- length(components) == 4L - - # begin collecting different methods for finding renv - methods <- c( - renv_bootstrap_download_tarball, - if (dev) - renv_bootstrap_download_github - else c( - renv_bootstrap_download_cran_latest, - renv_bootstrap_download_cran_archive - ) - ) - - for (method in methods) { - path <- tryCatch(method(version), error = identity) - if (is.character(path) && file.exists(path)) - return(path) - } - - stop("failed to download renv ", version) - - } - - renv_bootstrap_download_impl <- function(url, destfile) { - - mode <- "wb" - - # https://bugs.r-project.org/bugzilla/show_bug.cgi?id=17715 - fixup <- - Sys.info()[["sysname"]] == "Windows" && - substring(url, 1L, 5L) == "file:" - - if (fixup) - mode <- "w+b" - - args <- list( - url = url, - destfile = destfile, - mode = mode, - quiet = TRUE - ) - - if ("headers" %in% names(formals(utils::download.file))) - args$headers <- renv_bootstrap_download_custom_headers(url) - - do.call(utils::download.file, args) - - } - - renv_bootstrap_download_custom_headers <- function(url) { - - headers <- getOption("renv.download.headers") - if (is.null(headers)) - return(character()) - - if (!is.function(headers)) - stopf("'renv.download.headers' is not a function") - - headers <- headers(url) - if (length(headers) == 0L) - return(character()) - - if (is.list(headers)) - headers <- unlist(headers, recursive = FALSE, use.names = TRUE) - - ok <- - is.character(headers) && - is.character(names(headers)) && - all(nzchar(names(headers))) - - if (!ok) - stop("invocation of 'renv.download.headers' did not return a named character vector") - - headers - - } - - renv_bootstrap_download_cran_latest <- function(version) { - - spec <- renv_bootstrap_download_cran_latest_find(version) - type <- spec$type - repos <- spec$repos - - message("* Downloading renv ", version, " ... ", appendLF = FALSE) - - baseurl <- utils::contrib.url(repos = repos, type = type) - ext <- if (identical(type, "source")) - ".tar.gz" - else if (Sys.info()[["sysname"]] == "Windows") - ".zip" - else - ".tgz" - name <- sprintf("renv_%s%s", version, ext) - url <- paste(baseurl, name, sep = "/") - - destfile <- file.path(tempdir(), name) - status <- tryCatch( - renv_bootstrap_download_impl(url, destfile), - condition = identity - ) - - if (inherits(status, "condition")) { - message("FAILED") - return(FALSE) - } - - # report success and return - message("OK (downloaded ", type, ")") - destfile - - } - - renv_bootstrap_download_cran_latest_find <- function(version) { - - # check whether binaries are supported on this system - binary <- - getOption("renv.bootstrap.binary", default = TRUE) && - !identical(.Platform$pkgType, "source") && - !identical(getOption("pkgType"), "source") && - Sys.info()[["sysname"]] %in% c("Darwin", "Windows") - - types <- c(if (binary) "binary", "source") - - # iterate over types + repositories - for (type in types) { - for (repos in renv_bootstrap_repos()) { - - # retrieve package database - db <- tryCatch( - as.data.frame( - utils::available.packages(type = type, repos = repos), - stringsAsFactors = FALSE - ), - error = identity - ) - - if (inherits(db, "error")) - next - - # check for compatible entry - entry <- db[db$Package %in% "renv" & db$Version %in% version, ] - if (nrow(entry) == 0) - next - - # found it; return spec to caller - spec <- list(entry = entry, type = type, repos = repos) - return(spec) - - } - } - - # if we got here, we failed to find renv - fmt <- "renv %s is not available from your declared package repositories" - stop(sprintf(fmt, version)) - - } - - renv_bootstrap_download_cran_archive <- function(version) { - - name <- sprintf("renv_%s.tar.gz", version) - repos <- renv_bootstrap_repos() - urls <- file.path(repos, "src/contrib/Archive/renv", name) - destfile <- file.path(tempdir(), name) - - message("* Downloading renv ", version, " ... ", appendLF = FALSE) - - for (url in urls) { - - status <- tryCatch( - renv_bootstrap_download_impl(url, destfile), - condition = identity - ) - - if (identical(status, 0L)) { - message("OK") - return(destfile) - } - - } - - message("FAILED") - return(FALSE) - - } - - renv_bootstrap_download_tarball <- function(version) { - - # if the user has provided the path to a tarball via - # an environment variable, then use it - tarball <- Sys.getenv("RENV_BOOTSTRAP_TARBALL", unset = NA) - if (is.na(tarball)) - return() - - # allow directories - if (dir.exists(tarball)) { - name <- sprintf("renv_%s.tar.gz", version) - tarball <- file.path(tarball, name) - } - - # bail if it doesn't exist - if (!file.exists(tarball)) { - - # let the user know we weren't able to honour their request - fmt <- "* RENV_BOOTSTRAP_TARBALL is set (%s) but does not exist." - msg <- sprintf(fmt, tarball) - warning(msg) - - # bail - return() - - } - - fmt <- "* Bootstrapping with tarball at path '%s'." - msg <- sprintf(fmt, tarball) - message(msg) - - tarball - - } - - renv_bootstrap_download_github <- function(version) { - - enabled <- Sys.getenv("RENV_BOOTSTRAP_FROM_GITHUB", unset = "TRUE") - if (!identical(enabled, "TRUE")) - return(FALSE) - - # prepare download options - pat <- Sys.getenv("GITHUB_PAT") - if (nzchar(Sys.which("curl")) && nzchar(pat)) { - fmt <- "--location --fail --header \"Authorization: token %s\"" - extra <- sprintf(fmt, pat) - saved <- options("download.file.method", "download.file.extra") - options(download.file.method = "curl", download.file.extra = extra) - on.exit(do.call(base::options, saved), add = TRUE) - } else if (nzchar(Sys.which("wget")) && nzchar(pat)) { - fmt <- "--header=\"Authorization: token %s\"" - extra <- sprintf(fmt, pat) - saved <- options("download.file.method", "download.file.extra") - options(download.file.method = "wget", download.file.extra = extra) - on.exit(do.call(base::options, saved), add = TRUE) - } - - message("* Downloading renv ", version, " from GitHub ... ", appendLF = FALSE) - - url <- file.path("https://api.github.com/repos/rstudio/renv/tarball", version) - name <- sprintf("renv_%s.tar.gz", version) - destfile <- file.path(tempdir(), name) - - status <- tryCatch( - renv_bootstrap_download_impl(url, destfile), - condition = identity - ) - - if (!identical(status, 0L)) { - message("FAILED") - return(FALSE) - } - - message("OK") - return(destfile) - - } - - renv_bootstrap_install <- function(version, tarball, library) { - - # attempt to install it into project library - message("* Installing renv ", version, " ... ", appendLF = FALSE) - dir.create(library, showWarnings = FALSE, recursive = TRUE) - - # invoke using system2 so we can capture and report output - bin <- R.home("bin") - exe <- if (Sys.info()[["sysname"]] == "Windows") "R.exe" else "R" - r <- file.path(bin, exe) - - args <- c( - "--vanilla", "CMD", "INSTALL", "--no-multiarch", - "-l", shQuote(path.expand(library)), - shQuote(path.expand(tarball)) - ) - - output <- system2(r, args, stdout = TRUE, stderr = TRUE) - message("Done!") - - # check for successful install - status <- attr(output, "status") - if (is.numeric(status) && !identical(status, 0L)) { - header <- "Error installing renv:" - lines <- paste(rep.int("=", nchar(header)), collapse = "") - text <- c(header, lines, output) - writeLines(text, con = stderr()) - } - - status - - } - - renv_bootstrap_platform_prefix <- function() { - - # construct version prefix - version <- paste(R.version$major, R.version$minor, sep = ".") - prefix <- paste("R", numeric_version(version)[1, 1:2], sep = "-") - - # include SVN revision for development versions of R - # (to avoid sharing platform-specific artefacts with released versions of R) - devel <- - identical(R.version[["status"]], "Under development (unstable)") || - identical(R.version[["nickname"]], "Unsuffered Consequences") - - if (devel) - prefix <- paste(prefix, R.version[["svn rev"]], sep = "-r") - - # build list of path components - components <- c(prefix, R.version$platform) - - # include prefix if provided by user - prefix <- renv_bootstrap_platform_prefix_impl() - if (!is.na(prefix) && nzchar(prefix)) - components <- c(prefix, components) - - # build prefix - paste(components, collapse = "/") - - } - - renv_bootstrap_platform_prefix_impl <- function() { - - # if an explicit prefix has been supplied, use it - prefix <- Sys.getenv("RENV_PATHS_PREFIX", unset = NA) - if (!is.na(prefix)) - return(prefix) - - # if the user has requested an automatic prefix, generate it - auto <- Sys.getenv("RENV_PATHS_PREFIX_AUTO", unset = NA) - if (auto %in% c("TRUE", "True", "true", "1")) - return(renv_bootstrap_platform_prefix_auto()) - - # empty string on failure - "" - - } - - renv_bootstrap_platform_prefix_auto <- function() { - - prefix <- tryCatch(renv_bootstrap_platform_os(), error = identity) - if (inherits(prefix, "error") || prefix %in% "unknown") { - - msg <- paste( - "failed to infer current operating system", - "please file a bug report at https://github.com/rstudio/renv/issues", - sep = "; " - ) - - warning(msg) - - } - - prefix - - } - - renv_bootstrap_platform_os <- function() { - - sysinfo <- Sys.info() - sysname <- sysinfo[["sysname"]] - - # handle Windows + macOS up front - if (sysname == "Windows") - return("windows") - else if (sysname == "Darwin") - return("macos") - - # check for os-release files - for (file in c("/etc/os-release", "/usr/lib/os-release")) - if (file.exists(file)) - return(renv_bootstrap_platform_os_via_os_release(file, sysinfo)) - - # check for redhat-release files - if (file.exists("/etc/redhat-release")) - return(renv_bootstrap_platform_os_via_redhat_release()) - - "unknown" - - } - - renv_bootstrap_platform_os_via_os_release <- function(file, sysinfo) { - - # read /etc/os-release - release <- utils::read.table( - file = file, - sep = "=", - quote = c("\"", "'"), - col.names = c("Key", "Value"), - comment.char = "#", - stringsAsFactors = FALSE - ) - - vars <- as.list(release$Value) - names(vars) <- release$Key - - # get os name - os <- tolower(sysinfo[["sysname"]]) - - # read id - id <- "unknown" - for (field in c("ID", "ID_LIKE")) { - if (field %in% names(vars) && nzchar(vars[[field]])) { - id <- vars[[field]] - break - } - } - - # read version - version <- "unknown" - for (field in c("UBUNTU_CODENAME", "VERSION_CODENAME", "VERSION_ID", "BUILD_ID")) { - if (field %in% names(vars) && nzchar(vars[[field]])) { - version <- vars[[field]] - break - } - } - - # join together - paste(c(os, id, version), collapse = "-") - - } - - renv_bootstrap_platform_os_via_redhat_release <- function() { - - # read /etc/redhat-release - contents <- readLines("/etc/redhat-release", warn = FALSE) - - # infer id - id <- if (grepl("centos", contents, ignore.case = TRUE)) - "centos" - else if (grepl("redhat", contents, ignore.case = TRUE)) - "redhat" - else - "unknown" - - # try to find a version component (very hacky) - version <- "unknown" - - parts <- strsplit(contents, "[[:space:]]")[[1L]] - for (part in parts) { - - nv <- tryCatch(numeric_version(part), error = identity) - if (inherits(nv, "error")) - next - - version <- nv[1, 1] - break - - } - - paste(c("linux", id, version), collapse = "-") - - } - - renv_bootstrap_library_root_name <- function(project) { - - # use project name as-is if requested - asis <- Sys.getenv("RENV_PATHS_LIBRARY_ROOT_ASIS", unset = "FALSE") - if (asis) - return(basename(project)) - - # otherwise, disambiguate based on project's path - id <- substring(renv_bootstrap_hash_text(project), 1L, 8L) - paste(basename(project), id, sep = "-") - - } - - renv_bootstrap_library_root <- function(project) { - - prefix <- renv_bootstrap_profile_prefix() - - path <- Sys.getenv("RENV_PATHS_LIBRARY", unset = NA) - if (!is.na(path)) - return(paste(c(path, prefix), collapse = "/")) - - path <- renv_bootstrap_library_root_impl(project) - if (!is.null(path)) { - name <- renv_bootstrap_library_root_name(project) - return(paste(c(path, prefix, name), collapse = "/")) - } - - renv_bootstrap_paths_renv("library", project = project) - - } - - renv_bootstrap_library_root_impl <- function(project) { - - root <- Sys.getenv("RENV_PATHS_LIBRARY_ROOT", unset = NA) - if (!is.na(root)) - return(root) - - type <- renv_bootstrap_project_type(project) - if (identical(type, "package")) { - userdir <- renv_bootstrap_user_dir() - return(file.path(userdir, "library")) - } - - } - - renv_bootstrap_validate_version <- function(version) { - - loadedversion <- utils::packageDescription("renv", fields = "Version") - if (version == loadedversion) - return(TRUE) - - # assume four-component versions are from GitHub; - # three-component versions are from CRAN - components <- strsplit(loadedversion, "[.-]")[[1]] - remote <- if (length(components) == 4L) - paste("rstudio/renv", loadedversion, sep = "@") - else - paste("renv", loadedversion, sep = "@") - - fmt <- paste( - "renv %1$s was loaded from project library, but this project is configured to use renv %2$s.", - "Use `renv::record(\"%3$s\")` to record renv %1$s in the lockfile.", - "Use `renv::restore(packages = \"renv\")` to install renv %2$s into the project library.", - sep = "\n" - ) - - msg <- sprintf(fmt, loadedversion, version, remote) - warning(msg, call. = FALSE) - - FALSE - - } - - renv_bootstrap_hash_text <- function(text) { - - hashfile <- tempfile("renv-hash-") - on.exit(unlink(hashfile), add = TRUE) - - writeLines(text, con = hashfile) - tools::md5sum(hashfile) - - } - - renv_bootstrap_load <- function(project, libpath, version) { - - # try to load renv from the project library - if (!requireNamespace("renv", lib.loc = libpath, quietly = TRUE)) - return(FALSE) - - # warn if the version of renv loaded does not match - renv_bootstrap_validate_version(version) - - # execute renv load hooks, if any - hooks <- getHook("renv::autoload") - for (hook in hooks) - if (is.function(hook)) - tryCatch(hook(), error = warning) - - # load the project - renv::load(project) - - TRUE - - } - - renv_bootstrap_profile_load <- function(project) { - - # if RENV_PROFILE is already set, just use that - profile <- Sys.getenv("RENV_PROFILE", unset = NA) - if (!is.na(profile) && nzchar(profile)) - return(profile) - - # check for a profile file (nothing to do if it doesn't exist) - path <- renv_bootstrap_paths_renv("profile", profile = FALSE, project = project) - if (!file.exists(path)) - return(NULL) - - # read the profile, and set it if it exists - contents <- readLines(path, warn = FALSE) - if (length(contents) == 0L) - return(NULL) - - # set RENV_PROFILE - profile <- contents[[1L]] - if (!profile %in% c("", "default")) - Sys.setenv(RENV_PROFILE = profile) - - profile - - } - - renv_bootstrap_profile_prefix <- function() { - profile <- renv_bootstrap_profile_get() - if (!is.null(profile)) - return(file.path("profiles", profile, "renv")) - } - - renv_bootstrap_profile_get <- function() { - profile <- Sys.getenv("RENV_PROFILE", unset = "") - renv_bootstrap_profile_normalize(profile) - } - - renv_bootstrap_profile_set <- function(profile) { - profile <- renv_bootstrap_profile_normalize(profile) - if (is.null(profile)) - Sys.unsetenv("RENV_PROFILE") - else - Sys.setenv(RENV_PROFILE = profile) - } - - renv_bootstrap_profile_normalize <- function(profile) { - - if (is.null(profile) || profile %in% c("", "default")) - return(NULL) - - profile - - } - - renv_bootstrap_path_absolute <- function(path) { - - substr(path, 1L, 1L) %in% c("~", "/", "\\") || ( - substr(path, 1L, 1L) %in% c(letters, LETTERS) && - substr(path, 2L, 3L) %in% c(":/", ":\\") - ) - - } - - renv_bootstrap_paths_renv <- function(..., profile = TRUE, project = NULL) { - renv <- Sys.getenv("RENV_PATHS_RENV", unset = "renv") - root <- if (renv_bootstrap_path_absolute(renv)) NULL else project - prefix <- if (profile) renv_bootstrap_profile_prefix() - components <- c(root, renv, prefix, ...) - paste(components, collapse = "/") - } - - renv_bootstrap_project_type <- function(path) { - - descpath <- file.path(path, "DESCRIPTION") - if (!file.exists(descpath)) - return("unknown") - - desc <- tryCatch( - read.dcf(descpath, all = TRUE), - error = identity - ) - - if (inherits(desc, "error")) - return("unknown") - - type <- desc$Type - if (!is.null(type)) - return(tolower(type)) - - package <- desc$Package - if (!is.null(package)) - return("package") - - "unknown" - - } - - renv_bootstrap_user_dir <- function() { - dir <- renv_bootstrap_user_dir_impl() - path.expand(chartr("\\", "/", dir)) - } - - renv_bootstrap_user_dir_impl <- function() { - - # use local override if set - override <- getOption("renv.userdir.override") - if (!is.null(override)) - return(override) - - # use R_user_dir if available - tools <- asNamespace("tools") - if (is.function(tools$R_user_dir)) - return(tools$R_user_dir("renv", "cache")) - - # try using our own backfill for older versions of R - envvars <- c("R_USER_CACHE_DIR", "XDG_CACHE_HOME") - for (envvar in envvars) { - root <- Sys.getenv(envvar, unset = NA) - if (!is.na(root)) - return(file.path(root, "R/renv")) - } - - # use platform-specific default fallbacks - if (Sys.info()[["sysname"]] == "Windows") - file.path(Sys.getenv("LOCALAPPDATA"), "R/cache/R/renv") - else if (Sys.info()[["sysname"]] == "Darwin") - "~/Library/Caches/org.R-project.R/R/renv" - else - "~/.cache/R/renv" - - } - - - renv_json_read <- function(file = NULL, text = NULL) { - - jlerr <- NULL - - # if jsonlite is loaded, use that instead - if ("jsonlite" %in% loadedNamespaces()) { - - json <- catch(renv_json_read_jsonlite(file, text)) - if (!inherits(json, "error")) - return(json) - - jlerr <- json - - } - - # otherwise, fall back to the default JSON reader - json <- catch(renv_json_read_default(file, text)) - if (!inherits(json, "error")) - return(json) - - # report an error - if (!is.null(jlerr)) - stop(jlerr) - else - stop(json) - - } - - renv_json_read_jsonlite <- function(file = NULL, text = NULL) { - text <- paste(text %||% read(file), collapse = "\n") - jsonlite::fromJSON(txt = text, simplifyVector = FALSE) - } - - renv_json_read_default <- function(file = NULL, text = NULL) { - - # find strings in the JSON - text <- paste(text %||% read(file), collapse = "\n") - pattern <- '["](?:(?:\\\\.)|(?:[^"\\\\]))*?["]' - locs <- gregexpr(pattern, text, perl = TRUE)[[1]] - - # if any are found, replace them with placeholders - replaced <- text - strings <- character() - replacements <- character() - - if (!identical(c(locs), -1L)) { - - # get the string values - starts <- locs - ends <- locs + attr(locs, "match.length") - 1L - strings <- substring(text, starts, ends) - - # only keep those requiring escaping - strings <- grep("[[\\]{}:]", strings, perl = TRUE, value = TRUE) - - # compute replacements - replacements <- sprintf('"\032%i\032"', seq_along(strings)) - - # replace the strings - mapply(function(string, replacement) { - replaced <<- sub(string, replacement, replaced, fixed = TRUE) - }, strings, replacements) - - } - - # transform the JSON into something the R parser understands - transformed <- replaced - transformed <- gsub("{}", "`names<-`(list(), character())", transformed, fixed = TRUE) - transformed <- gsub("[[{]", "list(", transformed, perl = TRUE) - transformed <- gsub("[]}]", ")", transformed, perl = TRUE) - transformed <- gsub(":", "=", transformed, fixed = TRUE) - text <- paste(transformed, collapse = "\n") - - # parse it - json <- parse(text = text, keep.source = FALSE, srcfile = NULL)[[1L]] - - # construct map between source strings, replaced strings - map <- as.character(parse(text = strings)) - names(map) <- as.character(parse(text = replacements)) - - # convert to list - map <- as.list(map) - - # remap strings in object - remapped <- renv_json_remap(json, map) - - # evaluate - eval(remapped, envir = baseenv()) - - } - - renv_json_remap <- function(json, map) { - - # fix names - if (!is.null(names(json))) { - lhs <- match(names(json), names(map), nomatch = 0L) - rhs <- match(names(map), names(json), nomatch = 0L) - names(json)[rhs] <- map[lhs] - } - - # fix values - if (is.character(json)) - return(map[[json]] %||% json) - - # handle true, false, null - if (is.name(json)) { - text <- as.character(json) - if (text == "true") - return(TRUE) - else if (text == "false") - return(FALSE) - else if (text == "null") - return(NULL) - } - - # recurse - if (is.recursive(json)) { - for (i in seq_along(json)) { - json[i] <- list(renv_json_remap(json[[i]], map)) - } - } - - json - - } - - # load the renv profile, if any - renv_bootstrap_profile_load(project) - - # construct path to library root - root <- renv_bootstrap_library_root(project) - - # construct library prefix for platform - prefix <- renv_bootstrap_platform_prefix() - - # construct full libpath - libpath <- file.path(root, prefix) - - # attempt to load - if (renv_bootstrap_load(project, libpath, version)) - return(TRUE) - - # load failed; inform user we're about to bootstrap - prefix <- paste("# Bootstrapping renv", version) - postfix <- paste(rep.int("-", 77L - nchar(prefix)), collapse = "") - header <- paste(prefix, postfix) - message(header) - - # perform bootstrap - bootstrap(version, libpath) - - # exit early if we're just testing bootstrap - if (!is.na(Sys.getenv("RENV_BOOTSTRAP_INSTALL_ONLY", unset = NA))) - return(TRUE) - - # try again to load - if (requireNamespace("renv", lib.loc = libpath, quietly = TRUE)) { - message("* Successfully installed and loaded renv ", version, ".") - return(renv::load()) - } - - # failed to download or load renv; warn the user - msg <- c( - "Failed to find an renv installation: the project will not be loaded.", - "Use `renv::activate()` to re-initialize the project." - ) - - warning(paste(msg, collapse = "\n"), call. = FALSE) - -}) diff --git a/renv/profiles/lesson-requirements/renv/settings.json b/renv/profiles/lesson-requirements/renv/settings.json deleted file mode 100644 index 1a06760b..00000000 --- a/renv/profiles/lesson-requirements/renv/settings.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "bioconductor.version": null, - "external.libraries": [], - "ignored.packages": [], - "package.dependency.fields": [ - "Imports", - "Depends", - "LinkingTo" - ], - "r.version": null, - "snapshot.type": "implicit", - "use.cache": true, - "vcs.ignore.cellar": true, - "vcs.ignore.library": true, - "vcs.ignore.local": true, - "vcs.manage.ignores": true -} From 3dd82388e6d6c406edb2d18d74cd962a254ec76a Mon Sep 17 00:00:00 2001 From: Michael Milton Date: Tue, 9 Jul 2024 17:15:38 +1000 Subject: [PATCH 23/34] Remove unused figures --- episodes/fig/python-envs.png | Bin 32869 -> 0 bytes episodes/fig/rstudio-ood.png | Bin 141486 -> 0 bytes 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 episodes/fig/python-envs.png delete mode 100644 episodes/fig/rstudio-ood.png diff --git a/episodes/fig/python-envs.png b/episodes/fig/python-envs.png deleted file mode 100644 index 760b0b39251183f3bb34054165555afb47cc357d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 32869 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%0A<~6o4zISE{#*3`@QXic4&#ymrg} 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 LD~J_|82J8wc&ahj diff --git a/episodes/fig/rstudio-ood.png b/episodes/fig/rstudio-ood.png deleted file mode 100644 index 0af553b93534f5c04469b5894ae19b77fc7ad80c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 141486 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≶pKVP(}M_k$&0# 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%0A_-sSzkfsxUop#F%{PI** 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 fB;_7VZy0AUY{*2CRVUc|0K89wL`ju7X+) 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 From 6cc5c25c1031f3fd53bd8312cb2376b067fb8cde Mon Sep 17 00:00:00 2001 From: Michael Milton Date: Tue, 9 Jul 2024 17:18:11 +1000 Subject: [PATCH 24/34] I guess these files should be tracked? --- .../lesson-requirements/renv/.gitignore | 7 +++++++ .../lesson-requirements/renv/settings.json | 17 +++++++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 renv/profiles/lesson-requirements/renv/.gitignore create mode 100644 renv/profiles/lesson-requirements/renv/settings.json diff --git a/renv/profiles/lesson-requirements/renv/.gitignore b/renv/profiles/lesson-requirements/renv/.gitignore new file mode 100644 index 00000000..0ec0cbba --- /dev/null +++ b/renv/profiles/lesson-requirements/renv/.gitignore @@ -0,0 +1,7 @@ +library/ +local/ +cellar/ +lock/ +python/ +sandbox/ +staging/ diff --git a/renv/profiles/lesson-requirements/renv/settings.json b/renv/profiles/lesson-requirements/renv/settings.json new file mode 100644 index 00000000..1a06760b --- /dev/null +++ b/renv/profiles/lesson-requirements/renv/settings.json @@ -0,0 +1,17 @@ +{ + "bioconductor.version": null, + "external.libraries": [], + "ignored.packages": [], + "package.dependency.fields": [ + "Imports", + "Depends", + "LinkingTo" + ], + "r.version": null, + "snapshot.type": "implicit", + "use.cache": true, + "vcs.ignore.cellar": true, + "vcs.ignore.library": true, + "vcs.ignore.local": true, + "vcs.manage.ignores": true +} From 4723369f09b0c1705846ce09b5b48b98b0c16d1b Mon Sep 17 00:00:00 2001 From: Michael Milton Date: Wed, 10 Jul 2024 11:37:52 +1000 Subject: [PATCH 25/34] Add missing activate file --- .../lesson-requirements/renv/activate.R | 1032 +++++++++++++++++ 1 file changed, 1032 insertions(+) create mode 100644 renv/profiles/lesson-requirements/renv/activate.R diff --git a/renv/profiles/lesson-requirements/renv/activate.R b/renv/profiles/lesson-requirements/renv/activate.R new file mode 100644 index 00000000..a8fdc320 --- /dev/null +++ b/renv/profiles/lesson-requirements/renv/activate.R @@ -0,0 +1,1032 @@ + +local({ + + # the requested version of renv + version <- "0.17.3" + + # the project directory + project <- getwd() + + # figure out whether the autoloader is enabled + enabled <- local({ + + # first, check config option + override <- getOption("renv.config.autoloader.enabled") + if (!is.null(override)) + return(override) + + # next, check environment variables + # TODO: prefer using the configuration one in the future + envvars <- c( + "RENV_CONFIG_AUTOLOADER_ENABLED", + "RENV_AUTOLOADER_ENABLED", + "RENV_ACTIVATE_PROJECT" + ) + + for (envvar in envvars) { + envval <- Sys.getenv(envvar, unset = NA) + if (!is.na(envval)) + return(tolower(envval) %in% c("true", "t", "1")) + } + + # enable by default + TRUE + + }) + + if (!enabled) + return(FALSE) + + # avoid recursion + if (identical(getOption("renv.autoloader.running"), TRUE)) { + warning("ignoring recursive attempt to run renv autoloader") + return(invisible(TRUE)) + } + + # signal that we're loading renv during R startup + options(renv.autoloader.running = TRUE) + on.exit(options(renv.autoloader.running = NULL), add = TRUE) + + # signal that we've consented to use renv + options(renv.consent = TRUE) + + # load the 'utils' package eagerly -- this ensures that renv shims, which + # mask 'utils' packages, will come first on the search path + library(utils, lib.loc = .Library) + + # unload renv if it's already been loaded + if ("renv" %in% loadedNamespaces()) + unloadNamespace("renv") + + # load bootstrap tools + `%||%` <- function(x, y) { + if (is.environment(x) || length(x)) x else y + } + + `%??%` <- function(x, y) { + if (is.null(x)) y else x + } + + bootstrap <- function(version, library) { + + # attempt to download renv + tarball <- tryCatch(renv_bootstrap_download(version), error = identity) + if (inherits(tarball, "error")) + stop("failed to download renv ", version) + + # now attempt to install + status <- tryCatch(renv_bootstrap_install(version, tarball, library), error = identity) + if (inherits(status, "error")) + stop("failed to install renv ", version) + + } + + renv_bootstrap_tests_running <- function() { + getOption("renv.tests.running", default = FALSE) + } + + renv_bootstrap_repos <- function() { + + # get CRAN repository + cran <- getOption("renv.repos.cran", "https://cloud.r-project.org") + + # check for repos override + repos <- Sys.getenv("RENV_CONFIG_REPOS_OVERRIDE", unset = NA) + if (!is.na(repos)) { + + # check for RSPM; if set, use a fallback repository for renv + rspm <- Sys.getenv("RSPM", unset = NA) + if (identical(rspm, repos)) + repos <- c(RSPM = rspm, CRAN = cran) + + return(repos) + + } + + # check for lockfile repositories + repos <- tryCatch(renv_bootstrap_repos_lockfile(), error = identity) + if (!inherits(repos, "error") && length(repos)) + return(repos) + + # if we're testing, re-use the test repositories + if (renv_bootstrap_tests_running()) { + repos <- getOption("renv.tests.repos") + if (!is.null(repos)) + return(repos) + } + + # retrieve current repos + repos <- getOption("repos") + + # ensure @CRAN@ entries are resolved + repos[repos == "@CRAN@"] <- cran + + # add in renv.bootstrap.repos if set + default <- c(FALLBACK = "https://cloud.r-project.org") + extra <- getOption("renv.bootstrap.repos", default = default) + repos <- c(repos, extra) + + # remove duplicates that might've snuck in + dupes <- duplicated(repos) | duplicated(names(repos)) + repos[!dupes] + + } + + renv_bootstrap_repos_lockfile <- function() { + + lockpath <- Sys.getenv("RENV_PATHS_LOCKFILE", unset = "renv.lock") + if (!file.exists(lockpath)) + return(NULL) + + lockfile <- tryCatch(renv_json_read(lockpath), error = identity) + if (inherits(lockfile, "error")) { + warning(lockfile) + return(NULL) + } + + repos <- lockfile$R$Repositories + if (length(repos) == 0) + return(NULL) + + keys <- vapply(repos, `[[`, "Name", FUN.VALUE = character(1)) + vals <- vapply(repos, `[[`, "URL", FUN.VALUE = character(1)) + names(vals) <- keys + + return(vals) + + } + + renv_bootstrap_download <- function(version) { + + # if the renv version number has 4 components, assume it must + # be retrieved via github + nv <- numeric_version(version) + components <- unclass(nv)[[1]] + + # if this appears to be a development version of 'renv', we'll + # try to restore from github + dev <- length(components) == 4L + + # begin collecting different methods for finding renv + methods <- c( + renv_bootstrap_download_tarball, + if (dev) + renv_bootstrap_download_github + else c( + renv_bootstrap_download_cran_latest, + renv_bootstrap_download_cran_archive + ) + ) + + for (method in methods) { + path <- tryCatch(method(version), error = identity) + if (is.character(path) && file.exists(path)) + return(path) + } + + stop("failed to download renv ", version) + + } + + renv_bootstrap_download_impl <- function(url, destfile) { + + mode <- "wb" + + # https://bugs.r-project.org/bugzilla/show_bug.cgi?id=17715 + fixup <- + Sys.info()[["sysname"]] == "Windows" && + substring(url, 1L, 5L) == "file:" + + if (fixup) + mode <- "w+b" + + args <- list( + url = url, + destfile = destfile, + mode = mode, + quiet = TRUE + ) + + if ("headers" %in% names(formals(utils::download.file))) + args$headers <- renv_bootstrap_download_custom_headers(url) + + do.call(utils::download.file, args) + + } + + renv_bootstrap_download_custom_headers <- function(url) { + + headers <- getOption("renv.download.headers") + if (is.null(headers)) + return(character()) + + if (!is.function(headers)) + stopf("'renv.download.headers' is not a function") + + headers <- headers(url) + if (length(headers) == 0L) + return(character()) + + if (is.list(headers)) + headers <- unlist(headers, recursive = FALSE, use.names = TRUE) + + ok <- + is.character(headers) && + is.character(names(headers)) && + all(nzchar(names(headers))) + + if (!ok) + stop("invocation of 'renv.download.headers' did not return a named character vector") + + headers + + } + + renv_bootstrap_download_cran_latest <- function(version) { + + spec <- renv_bootstrap_download_cran_latest_find(version) + type <- spec$type + repos <- spec$repos + + message("* Downloading renv ", version, " ... ", appendLF = FALSE) + + baseurl <- utils::contrib.url(repos = repos, type = type) + ext <- if (identical(type, "source")) + ".tar.gz" + else if (Sys.info()[["sysname"]] == "Windows") + ".zip" + else + ".tgz" + name <- sprintf("renv_%s%s", version, ext) + url <- paste(baseurl, name, sep = "/") + + destfile <- file.path(tempdir(), name) + status <- tryCatch( + renv_bootstrap_download_impl(url, destfile), + condition = identity + ) + + if (inherits(status, "condition")) { + message("FAILED") + return(FALSE) + } + + # report success and return + message("OK (downloaded ", type, ")") + destfile + + } + + renv_bootstrap_download_cran_latest_find <- function(version) { + + # check whether binaries are supported on this system + binary <- + getOption("renv.bootstrap.binary", default = TRUE) && + !identical(.Platform$pkgType, "source") && + !identical(getOption("pkgType"), "source") && + Sys.info()[["sysname"]] %in% c("Darwin", "Windows") + + types <- c(if (binary) "binary", "source") + + # iterate over types + repositories + for (type in types) { + for (repos in renv_bootstrap_repos()) { + + # retrieve package database + db <- tryCatch( + as.data.frame( + utils::available.packages(type = type, repos = repos), + stringsAsFactors = FALSE + ), + error = identity + ) + + if (inherits(db, "error")) + next + + # check for compatible entry + entry <- db[db$Package %in% "renv" & db$Version %in% version, ] + if (nrow(entry) == 0) + next + + # found it; return spec to caller + spec <- list(entry = entry, type = type, repos = repos) + return(spec) + + } + } + + # if we got here, we failed to find renv + fmt <- "renv %s is not available from your declared package repositories" + stop(sprintf(fmt, version)) + + } + + renv_bootstrap_download_cran_archive <- function(version) { + + name <- sprintf("renv_%s.tar.gz", version) + repos <- renv_bootstrap_repos() + urls <- file.path(repos, "src/contrib/Archive/renv", name) + destfile <- file.path(tempdir(), name) + + message("* Downloading renv ", version, " ... ", appendLF = FALSE) + + for (url in urls) { + + status <- tryCatch( + renv_bootstrap_download_impl(url, destfile), + condition = identity + ) + + if (identical(status, 0L)) { + message("OK") + return(destfile) + } + + } + + message("FAILED") + return(FALSE) + + } + + renv_bootstrap_download_tarball <- function(version) { + + # if the user has provided the path to a tarball via + # an environment variable, then use it + tarball <- Sys.getenv("RENV_BOOTSTRAP_TARBALL", unset = NA) + if (is.na(tarball)) + return() + + # allow directories + if (dir.exists(tarball)) { + name <- sprintf("renv_%s.tar.gz", version) + tarball <- file.path(tarball, name) + } + + # bail if it doesn't exist + if (!file.exists(tarball)) { + + # let the user know we weren't able to honour their request + fmt <- "* RENV_BOOTSTRAP_TARBALL is set (%s) but does not exist." + msg <- sprintf(fmt, tarball) + warning(msg) + + # bail + return() + + } + + fmt <- "* Bootstrapping with tarball at path '%s'." + msg <- sprintf(fmt, tarball) + message(msg) + + tarball + + } + + renv_bootstrap_download_github <- function(version) { + + enabled <- Sys.getenv("RENV_BOOTSTRAP_FROM_GITHUB", unset = "TRUE") + if (!identical(enabled, "TRUE")) + return(FALSE) + + # prepare download options + pat <- Sys.getenv("GITHUB_PAT") + if (nzchar(Sys.which("curl")) && nzchar(pat)) { + fmt <- "--location --fail --header \"Authorization: token %s\"" + extra <- sprintf(fmt, pat) + saved <- options("download.file.method", "download.file.extra") + options(download.file.method = "curl", download.file.extra = extra) + on.exit(do.call(base::options, saved), add = TRUE) + } else if (nzchar(Sys.which("wget")) && nzchar(pat)) { + fmt <- "--header=\"Authorization: token %s\"" + extra <- sprintf(fmt, pat) + saved <- options("download.file.method", "download.file.extra") + options(download.file.method = "wget", download.file.extra = extra) + on.exit(do.call(base::options, saved), add = TRUE) + } + + message("* Downloading renv ", version, " from GitHub ... ", appendLF = FALSE) + + url <- file.path("https://api.github.com/repos/rstudio/renv/tarball", version) + name <- sprintf("renv_%s.tar.gz", version) + destfile <- file.path(tempdir(), name) + + status <- tryCatch( + renv_bootstrap_download_impl(url, destfile), + condition = identity + ) + + if (!identical(status, 0L)) { + message("FAILED") + return(FALSE) + } + + message("OK") + return(destfile) + + } + + renv_bootstrap_install <- function(version, tarball, library) { + + # attempt to install it into project library + message("* Installing renv ", version, " ... ", appendLF = FALSE) + dir.create(library, showWarnings = FALSE, recursive = TRUE) + + # invoke using system2 so we can capture and report output + bin <- R.home("bin") + exe <- if (Sys.info()[["sysname"]] == "Windows") "R.exe" else "R" + r <- file.path(bin, exe) + + args <- c( + "--vanilla", "CMD", "INSTALL", "--no-multiarch", + "-l", shQuote(path.expand(library)), + shQuote(path.expand(tarball)) + ) + + output <- system2(r, args, stdout = TRUE, stderr = TRUE) + message("Done!") + + # check for successful install + status <- attr(output, "status") + if (is.numeric(status) && !identical(status, 0L)) { + header <- "Error installing renv:" + lines <- paste(rep.int("=", nchar(header)), collapse = "") + text <- c(header, lines, output) + writeLines(text, con = stderr()) + } + + status + + } + + renv_bootstrap_platform_prefix <- function() { + + # construct version prefix + version <- paste(R.version$major, R.version$minor, sep = ".") + prefix <- paste("R", numeric_version(version)[1, 1:2], sep = "-") + + # include SVN revision for development versions of R + # (to avoid sharing platform-specific artefacts with released versions of R) + devel <- + identical(R.version[["status"]], "Under development (unstable)") || + identical(R.version[["nickname"]], "Unsuffered Consequences") + + if (devel) + prefix <- paste(prefix, R.version[["svn rev"]], sep = "-r") + + # build list of path components + components <- c(prefix, R.version$platform) + + # include prefix if provided by user + prefix <- renv_bootstrap_platform_prefix_impl() + if (!is.na(prefix) && nzchar(prefix)) + components <- c(prefix, components) + + # build prefix + paste(components, collapse = "/") + + } + + renv_bootstrap_platform_prefix_impl <- function() { + + # if an explicit prefix has been supplied, use it + prefix <- Sys.getenv("RENV_PATHS_PREFIX", unset = NA) + if (!is.na(prefix)) + return(prefix) + + # if the user has requested an automatic prefix, generate it + auto <- Sys.getenv("RENV_PATHS_PREFIX_AUTO", unset = NA) + if (auto %in% c("TRUE", "True", "true", "1")) + return(renv_bootstrap_platform_prefix_auto()) + + # empty string on failure + "" + + } + + renv_bootstrap_platform_prefix_auto <- function() { + + prefix <- tryCatch(renv_bootstrap_platform_os(), error = identity) + if (inherits(prefix, "error") || prefix %in% "unknown") { + + msg <- paste( + "failed to infer current operating system", + "please file a bug report at https://github.com/rstudio/renv/issues", + sep = "; " + ) + + warning(msg) + + } + + prefix + + } + + renv_bootstrap_platform_os <- function() { + + sysinfo <- Sys.info() + sysname <- sysinfo[["sysname"]] + + # handle Windows + macOS up front + if (sysname == "Windows") + return("windows") + else if (sysname == "Darwin") + return("macos") + + # check for os-release files + for (file in c("/etc/os-release", "/usr/lib/os-release")) + if (file.exists(file)) + return(renv_bootstrap_platform_os_via_os_release(file, sysinfo)) + + # check for redhat-release files + if (file.exists("/etc/redhat-release")) + return(renv_bootstrap_platform_os_via_redhat_release()) + + "unknown" + + } + + renv_bootstrap_platform_os_via_os_release <- function(file, sysinfo) { + + # read /etc/os-release + release <- utils::read.table( + file = file, + sep = "=", + quote = c("\"", "'"), + col.names = c("Key", "Value"), + comment.char = "#", + stringsAsFactors = FALSE + ) + + vars <- as.list(release$Value) + names(vars) <- release$Key + + # get os name + os <- tolower(sysinfo[["sysname"]]) + + # read id + id <- "unknown" + for (field in c("ID", "ID_LIKE")) { + if (field %in% names(vars) && nzchar(vars[[field]])) { + id <- vars[[field]] + break + } + } + + # read version + version <- "unknown" + for (field in c("UBUNTU_CODENAME", "VERSION_CODENAME", "VERSION_ID", "BUILD_ID")) { + if (field %in% names(vars) && nzchar(vars[[field]])) { + version <- vars[[field]] + break + } + } + + # join together + paste(c(os, id, version), collapse = "-") + + } + + renv_bootstrap_platform_os_via_redhat_release <- function() { + + # read /etc/redhat-release + contents <- readLines("/etc/redhat-release", warn = FALSE) + + # infer id + id <- if (grepl("centos", contents, ignore.case = TRUE)) + "centos" + else if (grepl("redhat", contents, ignore.case = TRUE)) + "redhat" + else + "unknown" + + # try to find a version component (very hacky) + version <- "unknown" + + parts <- strsplit(contents, "[[:space:]]")[[1L]] + for (part in parts) { + + nv <- tryCatch(numeric_version(part), error = identity) + if (inherits(nv, "error")) + next + + version <- nv[1, 1] + break + + } + + paste(c("linux", id, version), collapse = "-") + + } + + renv_bootstrap_library_root_name <- function(project) { + + # use project name as-is if requested + asis <- Sys.getenv("RENV_PATHS_LIBRARY_ROOT_ASIS", unset = "FALSE") + if (asis) + return(basename(project)) + + # otherwise, disambiguate based on project's path + id <- substring(renv_bootstrap_hash_text(project), 1L, 8L) + paste(basename(project), id, sep = "-") + + } + + renv_bootstrap_library_root <- function(project) { + + prefix <- renv_bootstrap_profile_prefix() + + path <- Sys.getenv("RENV_PATHS_LIBRARY", unset = NA) + if (!is.na(path)) + return(paste(c(path, prefix), collapse = "/")) + + path <- renv_bootstrap_library_root_impl(project) + if (!is.null(path)) { + name <- renv_bootstrap_library_root_name(project) + return(paste(c(path, prefix, name), collapse = "/")) + } + + renv_bootstrap_paths_renv("library", project = project) + + } + + renv_bootstrap_library_root_impl <- function(project) { + + root <- Sys.getenv("RENV_PATHS_LIBRARY_ROOT", unset = NA) + if (!is.na(root)) + return(root) + + type <- renv_bootstrap_project_type(project) + if (identical(type, "package")) { + userdir <- renv_bootstrap_user_dir() + return(file.path(userdir, "library")) + } + + } + + renv_bootstrap_validate_version <- function(version) { + + loadedversion <- utils::packageDescription("renv", fields = "Version") + if (version == loadedversion) + return(TRUE) + + # assume four-component versions are from GitHub; + # three-component versions are from CRAN + components <- strsplit(loadedversion, "[.-]")[[1]] + remote <- if (length(components) == 4L) + paste("rstudio/renv", loadedversion, sep = "@") + else + paste("renv", loadedversion, sep = "@") + + fmt <- paste( + "renv %1$s was loaded from project library, but this project is configured to use renv %2$s.", + "Use `renv::record(\"%3$s\")` to record renv %1$s in the lockfile.", + "Use `renv::restore(packages = \"renv\")` to install renv %2$s into the project library.", + sep = "\n" + ) + + msg <- sprintf(fmt, loadedversion, version, remote) + warning(msg, call. = FALSE) + + FALSE + + } + + renv_bootstrap_hash_text <- function(text) { + + hashfile <- tempfile("renv-hash-") + on.exit(unlink(hashfile), add = TRUE) + + writeLines(text, con = hashfile) + tools::md5sum(hashfile) + + } + + renv_bootstrap_load <- function(project, libpath, version) { + + # try to load renv from the project library + if (!requireNamespace("renv", lib.loc = libpath, quietly = TRUE)) + return(FALSE) + + # warn if the version of renv loaded does not match + renv_bootstrap_validate_version(version) + + # execute renv load hooks, if any + hooks <- getHook("renv::autoload") + for (hook in hooks) + if (is.function(hook)) + tryCatch(hook(), error = warning) + + # load the project + renv::load(project) + + TRUE + + } + + renv_bootstrap_profile_load <- function(project) { + + # if RENV_PROFILE is already set, just use that + profile <- Sys.getenv("RENV_PROFILE", unset = NA) + if (!is.na(profile) && nzchar(profile)) + return(profile) + + # check for a profile file (nothing to do if it doesn't exist) + path <- renv_bootstrap_paths_renv("profile", profile = FALSE, project = project) + if (!file.exists(path)) + return(NULL) + + # read the profile, and set it if it exists + contents <- readLines(path, warn = FALSE) + if (length(contents) == 0L) + return(NULL) + + # set RENV_PROFILE + profile <- contents[[1L]] + if (!profile %in% c("", "default")) + Sys.setenv(RENV_PROFILE = profile) + + profile + + } + + renv_bootstrap_profile_prefix <- function() { + profile <- renv_bootstrap_profile_get() + if (!is.null(profile)) + return(file.path("profiles", profile, "renv")) + } + + renv_bootstrap_profile_get <- function() { + profile <- Sys.getenv("RENV_PROFILE", unset = "") + renv_bootstrap_profile_normalize(profile) + } + + renv_bootstrap_profile_set <- function(profile) { + profile <- renv_bootstrap_profile_normalize(profile) + if (is.null(profile)) + Sys.unsetenv("RENV_PROFILE") + else + Sys.setenv(RENV_PROFILE = profile) + } + + renv_bootstrap_profile_normalize <- function(profile) { + + if (is.null(profile) || profile %in% c("", "default")) + return(NULL) + + profile + + } + + renv_bootstrap_path_absolute <- function(path) { + + substr(path, 1L, 1L) %in% c("~", "/", "\\") || ( + substr(path, 1L, 1L) %in% c(letters, LETTERS) && + substr(path, 2L, 3L) %in% c(":/", ":\\") + ) + + } + + renv_bootstrap_paths_renv <- function(..., profile = TRUE, project = NULL) { + renv <- Sys.getenv("RENV_PATHS_RENV", unset = "renv") + root <- if (renv_bootstrap_path_absolute(renv)) NULL else project + prefix <- if (profile) renv_bootstrap_profile_prefix() + components <- c(root, renv, prefix, ...) + paste(components, collapse = "/") + } + + renv_bootstrap_project_type <- function(path) { + + descpath <- file.path(path, "DESCRIPTION") + if (!file.exists(descpath)) + return("unknown") + + desc <- tryCatch( + read.dcf(descpath, all = TRUE), + error = identity + ) + + if (inherits(desc, "error")) + return("unknown") + + type <- desc$Type + if (!is.null(type)) + return(tolower(type)) + + package <- desc$Package + if (!is.null(package)) + return("package") + + "unknown" + + } + + renv_bootstrap_user_dir <- function() { + dir <- renv_bootstrap_user_dir_impl() + path.expand(chartr("\\", "/", dir)) + } + + renv_bootstrap_user_dir_impl <- function() { + + # use local override if set + override <- getOption("renv.userdir.override") + if (!is.null(override)) + return(override) + + # use R_user_dir if available + tools <- asNamespace("tools") + if (is.function(tools$R_user_dir)) + return(tools$R_user_dir("renv", "cache")) + + # try using our own backfill for older versions of R + envvars <- c("R_USER_CACHE_DIR", "XDG_CACHE_HOME") + for (envvar in envvars) { + root <- Sys.getenv(envvar, unset = NA) + if (!is.na(root)) + return(file.path(root, "R/renv")) + } + + # use platform-specific default fallbacks + if (Sys.info()[["sysname"]] == "Windows") + file.path(Sys.getenv("LOCALAPPDATA"), "R/cache/R/renv") + else if (Sys.info()[["sysname"]] == "Darwin") + "~/Library/Caches/org.R-project.R/R/renv" + else + "~/.cache/R/renv" + + } + + + renv_json_read <- function(file = NULL, text = NULL) { + + jlerr <- NULL + + # if jsonlite is loaded, use that instead + if ("jsonlite" %in% loadedNamespaces()) { + + json <- catch(renv_json_read_jsonlite(file, text)) + if (!inherits(json, "error")) + return(json) + + jlerr <- json + + } + + # otherwise, fall back to the default JSON reader + json <- catch(renv_json_read_default(file, text)) + if (!inherits(json, "error")) + return(json) + + # report an error + if (!is.null(jlerr)) + stop(jlerr) + else + stop(json) + + } + + renv_json_read_jsonlite <- function(file = NULL, text = NULL) { + text <- paste(text %||% read(file), collapse = "\n") + jsonlite::fromJSON(txt = text, simplifyVector = FALSE) + } + + renv_json_read_default <- function(file = NULL, text = NULL) { + + # find strings in the JSON + text <- paste(text %||% read(file), collapse = "\n") + pattern <- '["](?:(?:\\\\.)|(?:[^"\\\\]))*?["]' + locs <- gregexpr(pattern, text, perl = TRUE)[[1]] + + # if any are found, replace them with placeholders + replaced <- text + strings <- character() + replacements <- character() + + if (!identical(c(locs), -1L)) { + + # get the string values + starts <- locs + ends <- locs + attr(locs, "match.length") - 1L + strings <- substring(text, starts, ends) + + # only keep those requiring escaping + strings <- grep("[[\\]{}:]", strings, perl = TRUE, value = TRUE) + + # compute replacements + replacements <- sprintf('"\032%i\032"', seq_along(strings)) + + # replace the strings + mapply(function(string, replacement) { + replaced <<- sub(string, replacement, replaced, fixed = TRUE) + }, strings, replacements) + + } + + # transform the JSON into something the R parser understands + transformed <- replaced + transformed <- gsub("{}", "`names<-`(list(), character())", transformed, fixed = TRUE) + transformed <- gsub("[[{]", "list(", transformed, perl = TRUE) + transformed <- gsub("[]}]", ")", transformed, perl = TRUE) + transformed <- gsub(":", "=", transformed, fixed = TRUE) + text <- paste(transformed, collapse = "\n") + + # parse it + json <- parse(text = text, keep.source = FALSE, srcfile = NULL)[[1L]] + + # construct map between source strings, replaced strings + map <- as.character(parse(text = strings)) + names(map) <- as.character(parse(text = replacements)) + + # convert to list + map <- as.list(map) + + # remap strings in object + remapped <- renv_json_remap(json, map) + + # evaluate + eval(remapped, envir = baseenv()) + + } + + renv_json_remap <- function(json, map) { + + # fix names + if (!is.null(names(json))) { + lhs <- match(names(json), names(map), nomatch = 0L) + rhs <- match(names(map), names(json), nomatch = 0L) + names(json)[rhs] <- map[lhs] + } + + # fix values + if (is.character(json)) + return(map[[json]] %||% json) + + # handle true, false, null + if (is.name(json)) { + text <- as.character(json) + if (text == "true") + return(TRUE) + else if (text == "false") + return(FALSE) + else if (text == "null") + return(NULL) + } + + # recurse + if (is.recursive(json)) { + for (i in seq_along(json)) { + json[i] <- list(renv_json_remap(json[[i]], map)) + } + } + + json + + } + + # load the renv profile, if any + renv_bootstrap_profile_load(project) + + # construct path to library root + root <- renv_bootstrap_library_root(project) + + # construct library prefix for platform + prefix <- renv_bootstrap_platform_prefix() + + # construct full libpath + libpath <- file.path(root, prefix) + + # attempt to load + if (renv_bootstrap_load(project, libpath, version)) + return(TRUE) + + # load failed; inform user we're about to bootstrap + prefix <- paste("# Bootstrapping renv", version) + postfix <- paste(rep.int("-", 77L - nchar(prefix)), collapse = "") + header <- paste(prefix, postfix) + message(header) + + # perform bootstrap + bootstrap(version, libpath) + + # exit early if we're just testing bootstrap + if (!is.na(Sys.getenv("RENV_BOOTSTRAP_INSTALL_ONLY", unset = NA))) + return(TRUE) + + # try again to load + if (requireNamespace("renv", lib.loc = libpath, quietly = TRUE)) { + message("* Successfully installed and loaded renv ", version, ".") + return(renv::load()) + } + + # failed to download or load renv; warn the user + msg <- c( + "Failed to find an renv installation: the project will not be loaded.", + "Use `renv::activate()` to re-initialize the project." + ) + + warning(paste(msg, collapse = "\n"), call. = FALSE) + +}) From d872dfb02b44ded7afd826074c36d5d12f5e8c55 Mon Sep 17 00:00:00 2001 From: Michael Milton Date: Wed, 10 Jul 2024 11:48:08 +1000 Subject: [PATCH 26/34] Add missing .gitignore --- renv/profiles/lesson-requirements/renv/.gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/renv/profiles/lesson-requirements/renv/.gitignore b/renv/profiles/lesson-requirements/renv/.gitignore index 0ec0cbba..a33237e3 100644 --- a/renv/profiles/lesson-requirements/renv/.gitignore +++ b/renv/profiles/lesson-requirements/renv/.gitignore @@ -5,3 +5,4 @@ lock/ python/ sandbox/ staging/ + From 24aa76718dc96e92838fa455102cfbb559b668a3 Mon Sep 17 00:00:00 2001 From: Michael Milton Date: Wed, 10 Jul 2024 11:48:41 +1000 Subject: [PATCH 27/34] Fix execution typo --- episodes/hpc.Rmd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/episodes/hpc.Rmd b/episodes/hpc.Rmd index 4e2aad07..eee9b0ed 100644 --- a/episodes/hpc.Rmd +++ b/episodes/hpc.Rmd @@ -46,7 +46,7 @@ options(width = 140) If your analysis involves computationally intensive or long-running tasks such as training machine learning models or processing very large amounts of data, it will quickly become infeasible to use a single machine to run this. If you have access to a High Performance Computing (HPC) cluster, you can leverage the numerous machines with Targets to scale up your analysis. -This differs from the exucution we have learned so far, which spawns extra R processes on the *same machine* to speed up execution. +This differs from the execution we have learned so far, which spawns extra R processes on the *same machine* to speed up execution. ## Configuring Targets for Slurm From 5e40be11794e2cc3d14b7e6485333cc58cf8407f Mon Sep 17 00:00:00 2001 From: Michael Milton Date: Wed, 10 Jul 2024 11:52:44 +1000 Subject: [PATCH 28/34] Start slurm for build --- .github/workflows/sandpaper-main.yaml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.github/workflows/sandpaper-main.yaml b/.github/workflows/sandpaper-main.yaml index e17707ac..25026ff7 100644 --- a/.github/workflows/sandpaper-main.yaml +++ b/.github/workflows/sandpaper-main.yaml @@ -29,6 +29,14 @@ jobs: env: GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} RENV_PATHS_ROOT: ~/.local/share/renv/ + services: + mysql: + image: mysql:8.0 + env: + MYSQL_ROOT_PASSWORD: root + ports: + - "8888:3306" + options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 steps: - name: "Checkout Lesson" @@ -53,6 +61,9 @@ jobs: with: cache-version: ${{ secrets.CACHE_VERSION }} + - name: Setup slurm + uses: koesterlab/setup-slurm-action@v1 + - name: "Deploy Site" run: | reset <- "${{ github.event.inputs.reset }}" == "true" From 4eb5f063e3bfdf2b34dc5f807a01e5fd7b9b46be Mon Sep 17 00:00:00 2001 From: joelnitta Date: Wed, 10 Jul 2024 11:17:55 +0900 Subject: [PATCH 29/34] Force dep on {htmlwidgets} needed for {visNetwork} --- episodes/files/packages.R | 1 + 1 file changed, 1 insertion(+) diff --git a/episodes/files/packages.R b/episodes/files/packages.R index fed1ade8..6f8bcc90 100644 --- a/episodes/files/packages.R +++ b/episodes/files/packages.R @@ -3,3 +3,4 @@ library(tarchetypes) library(palmerpenguins) library(tidyverse) library(broom) +library(htmlwidgets) From 9489bea236965744de194bc5768a79d2483ac5f6 Mon Sep 17 00:00:00 2001 From: joelnitta Date: Wed, 10 Jul 2024 11:18:17 +0900 Subject: [PATCH 30/34] Update renv and packages --- renv/profiles/lesson-requirements/renv.lock | 74 +++++++------------ .../lesson-requirements/renv/settings.json | 2 + 2 files changed, 30 insertions(+), 46 deletions(-) diff --git a/renv/profiles/lesson-requirements/renv.lock b/renv/profiles/lesson-requirements/renv.lock index cbc8e988..ab89dac4 100644 --- a/renv/profiles/lesson-requirements/renv.lock +++ b/renv/profiles/lesson-requirements/renv.lock @@ -1,6 +1,6 @@ { "R": { - "Version": "4.3.3", + "Version": "4.4.0", "Repositories": [ { "Name": "carpentries", @@ -19,18 +19,18 @@ "Packages": { "DBI": { "Package": "DBI", - "Version": "1.2.3", + "Version": "1.2.2", "Source": "Repository", "Repository": "CRAN", "Requirements": [ "R", "methods" ], - "Hash": "065ae649b05f1ff66bb0c793107508f5" + "Hash": "164809cd72e1d5160b4cb3aa57f510fe" }, "MASS": { "Package": "MASS", - "Version": "7.3-59", + "Version": "7.3-60.2", "Source": "Repository", "Repository": "CRAN", "Requirements": [ @@ -41,15 +41,16 @@ "stats", "utils" ], - "Hash": "26570ae748e78cb2b0f56019dd2ba354" + "Hash": "2f342c46163b0b54d7b64d1f798e2c78" }, "Matrix": { "Package": "Matrix", - "Version": "1.5-4", + "Version": "1.7-0", "Source": "Repository", "Repository": "CRAN", "Requirements": [ "R", + "grDevices", "graphics", "grid", "lattice", @@ -57,7 +58,7 @@ "stats", "utils" ], - "Hash": "e779c7d9f35cc364438578f334cffee2" + "Hash": "1920b2f11133b12350024297d8a4ff4a" }, "R6": { "Package": "R6", @@ -168,7 +169,7 @@ }, "broom": { "Package": "broom", - "Version": "1.0.6", + "Version": "1.0.5", "Source": "Repository", "Repository": "CRAN", "Requirements": [ @@ -184,7 +185,7 @@ "tibble", "tidyr" ], - "Hash": "a4652c36d1f8abfc3ddf4774f768c934" + "Hash": "fd25391c3c4f6ecf0fa95f1e6d15378c" }, "bslib": { "Package": "bslib", @@ -350,25 +351,6 @@ ], "Hash": "1498e9c5b0f2a644d7c4a8534c9d20fe" }, - "crew.cluster": { - "Package": "crew.cluster", - "Version": "0.3.1", - "Source": "Repository", - "Repository": "CRAN", - "Requirements": [ - "R", - "R6", - "crew", - "lifecycle", - "ps", - "rlang", - "utils", - "vctrs", - "xml2", - "yaml" - ], - "Hash": "4a6a569e1f2b56ce2419d412598ab1b4" - }, "curl": { "Package": "curl", "Version": "5.2.1", @@ -473,14 +455,14 @@ }, "evaluate": { "Package": "evaluate", - "Version": "0.24.0", + "Version": "0.23", "Source": "Repository", "Repository": "CRAN", "Requirements": [ "R", "methods" ], - "Hash": "a1066cbc05caee9a4bf6d90f194ff4da" + "Hash": "daf4a1246be12c1fa8c7705a0935c1a0" }, "fansi": { "Package": "fansi", @@ -853,7 +835,7 @@ }, "knitr": { "Package": "knitr", - "Version": "1.48", + "Version": "1.46", "Source": "Repository", "Repository": "CRAN", "Requirements": [ @@ -865,7 +847,7 @@ "xfun", "yaml" ], - "Hash": "acf380f300c721da9fde7df115a5f86f" + "Hash": "6e008ab1d696a5283c79765fa7b56b47" }, "labeling": { "Package": "labeling", @@ -1030,7 +1012,7 @@ }, "nlme": { "Package": "nlme", - "Version": "3.1-165", + "Version": "3.1-164", "Source": "Repository", "Repository": "CRAN", "Requirements": [ @@ -1040,7 +1022,7 @@ "stats", "utils" ], - "Hash": "2769a88be217841b1f33ed469675c3cc" + "Hash": "a623a2239e642806158bc4dc3f51565d" }, "openssl": { "Package": "openssl", @@ -1144,14 +1126,14 @@ }, "ps": { "Package": "ps", - "Version": "1.7.7", + "Version": "1.7.6", "Source": "Repository", "Repository": "CRAN", "Requirements": [ "R", "utils" ], - "Hash": "878b467580097e9c383acbb16adab57a" + "Hash": "dd2b9319ee0656c8acf45c7f40c59de7" }, "purrr": { "Package": "purrr", @@ -1276,7 +1258,7 @@ }, "reprex": { "Package": "reprex", - "Version": "2.1.1", + "Version": "2.1.0", "Source": "Repository", "Repository": "CRAN", "Requirements": [ @@ -1294,7 +1276,7 @@ "utils", "withr" ], - "Hash": "97b1d5361a24d9fb588db7afe3e5bcbf" + "Hash": "1425f91b4d5d9a8f25352c44a3d914ed" }, "rlang": { "Package": "rlang", @@ -1415,7 +1397,7 @@ }, "stringi": { "Package": "stringi", - "Version": "1.8.4", + "Version": "1.8.3", "Source": "Repository", "Repository": "carpentries", "Requirements": [ @@ -1424,7 +1406,7 @@ "tools", "utils" ], - "Hash": "39e1144fd75428983dc3f63aa53dfa91" + "Hash": "058aebddea264f4c99401515182e656a" }, "stringr": { "Package": "stringr", @@ -1513,7 +1495,7 @@ }, "textshaping": { "Package": "textshaping", - "Version": "0.4.0", + "Version": "0.3.7", "Source": "Repository", "Repository": "carpentries", "Requirements": [ @@ -1522,7 +1504,7 @@ "lifecycle", "systemfonts" ], - "Hash": "5142f8bc78ed3d819d26461b641627ce" + "Hash": "997aac9ad649e0ef3b97f96cddd5622b" }, "tibble": { "Package": "tibble", @@ -1756,7 +1738,7 @@ }, "xfun": { "Package": "xfun", - "Version": "0.45", + "Version": "0.44", "Source": "Repository", "Repository": "CRAN", "Requirements": [ @@ -1764,7 +1746,7 @@ "stats", "tools" ], - "Hash": "ca59c87fe305b16a9141a5874c3a7889" + "Hash": "317a0538d32f4a009658bcedb7923f4b" }, "xml2": { "Package": "xml2", @@ -1781,10 +1763,10 @@ }, "yaml": { "Package": "yaml", - "Version": "2.3.9", + "Version": "2.3.8", "Source": "Repository", "Repository": "CRAN", - "Hash": "9cb28d11799d93c953f852083d55ee9e" + "Hash": "29240487a071f535f5e5d5a323b7afbd" } } } diff --git a/renv/profiles/lesson-requirements/renv/settings.json b/renv/profiles/lesson-requirements/renv/settings.json index 1a06760b..ffdbb320 100644 --- a/renv/profiles/lesson-requirements/renv/settings.json +++ b/renv/profiles/lesson-requirements/renv/settings.json @@ -7,6 +7,8 @@ "Depends", "LinkingTo" ], + "ppm.enabled": null, + "ppm.ignored.urls": [], "r.version": null, "snapshot.type": "implicit", "use.cache": true, From e012aafef4de7620a750e93606a0f930094ad766 Mon Sep 17 00:00:00 2001 From: joelnitta Date: Wed, 10 Jul 2024 11:59:19 +0900 Subject: [PATCH 31/34] Update renv.lock --- renv/profiles/lesson-requirements/renv.lock | 46 +++++++++++++++++---- 1 file changed, 38 insertions(+), 8 deletions(-) diff --git a/renv/profiles/lesson-requirements/renv.lock b/renv/profiles/lesson-requirements/renv.lock index ab89dac4..16b38a86 100644 --- a/renv/profiles/lesson-requirements/renv.lock +++ b/renv/profiles/lesson-requirements/renv.lock @@ -176,6 +176,7 @@ "R", "backports", "dplyr", + "ellipsis", "generics", "glue", "lifecycle", @@ -351,11 +352,30 @@ ], "Hash": "1498e9c5b0f2a644d7c4a8534c9d20fe" }, + "crew.cluster": { + "Package": "crew.cluster", + "Version": "0.3.1", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R", + "R6", + "crew", + "lifecycle", + "ps", + "rlang", + "utils", + "vctrs", + "xml2", + "yaml" + ], + "Hash": "4a6a569e1f2b56ce2419d412598ab1b4" + }, "curl": { "Package": "curl", "Version": "5.2.1", "Source": "Repository", - "Repository": "carpentries", + "Repository": "CRAN", "Requirements": [ "R" ], @@ -453,6 +473,17 @@ ], "Hash": "54ed3ea01b11e81a86544faaecfef8e2" }, + "ellipsis": { + "Package": "ellipsis", + "Version": "0.3.2", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R", + "rlang" + ], + "Hash": "bb0eec2fe32e88d9e2836c2f73ea2077" + }, "evaluate": { "Package": "evaluate", "Version": "0.23", @@ -1028,7 +1059,7 @@ "Package": "openssl", "Version": "2.2.0", "Source": "Repository", - "Repository": "carpentries", + "Repository": "CRAN", "Requirements": [ "askpass" ], @@ -1174,7 +1205,7 @@ "Package": "ragg", "Version": "1.3.2", "Source": "Repository", - "Repository": "carpentries", + "Repository": "CRAN", "Requirements": [ "systemfonts", "textshaping" @@ -1399,7 +1430,7 @@ "Package": "stringi", "Version": "1.8.3", "Source": "Repository", - "Repository": "carpentries", + "Repository": "CRAN", "Requirements": [ "R", "stats", @@ -1436,7 +1467,7 @@ "Package": "systemfonts", "Version": "1.1.0", "Source": "Repository", - "Repository": "carpentries", + "Repository": "CRAN", "Requirements": [ "R", "cpp11", @@ -1497,11 +1528,10 @@ "Package": "textshaping", "Version": "0.3.7", "Source": "Repository", - "Repository": "carpentries", + "Repository": "CRAN", "Requirements": [ "R", "cpp11", - "lifecycle", "systemfonts" ], "Hash": "997aac9ad649e0ef3b97f96cddd5622b" @@ -1752,7 +1782,7 @@ "Package": "xml2", "Version": "1.3.6", "Source": "Repository", - "Repository": "https://carpentries.r-universe.dev", + "Repository": "CRAN", "Requirements": [ "R", "cli", From 7548c496b1eda2e0c4bc2e83222eab4b17f0e476 Mon Sep 17 00:00:00 2001 From: Michael Milton Date: Wed, 10 Jul 2024 15:10:13 +1000 Subject: [PATCH 32/34] Reduce memory requirement --- episodes/files/plans/plan_slurm_memory.R | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/episodes/files/plans/plan_slurm_memory.R b/episodes/files/plans/plan_slurm_memory.R index 5056aadf..80f2ae13 100644 --- a/episodes/files/plans/plan_slurm_memory.R +++ b/episodes/files/plans/plan_slurm_memory.R @@ -9,12 +9,12 @@ source("R/functions.R") small_memory <- crew_controller_slurm( name = "small_memory", script_lines = "module load R", - slurm_memory_gigabytes_per_cpu = 10 + slurm_memory_gigabytes_per_cpu = 1 ) big_memory <- crew_controller_slurm( name = "big_memory", script_lines = "module load R", - slurm_memory_gigabytes_per_cpu = 20 + slurm_memory_gigabytes_per_cpu = 2 ) tar_option_set( From 2c9a19ad2639f5044f5bbeec97e17fa63f5ba22c Mon Sep 17 00:00:00 2001 From: Michael Milton Date: Fri, 13 Dec 2024 14:42:08 +1100 Subject: [PATCH 33/34] Re-run CI From 0ca5b0092ff74a0d6f723412ff764fae9d024e0e Mon Sep 17 00:00:00 2001 From: Michael Milton Date: Fri, 13 Dec 2024 17:19:52 +1100 Subject: [PATCH 34/34] Revert .github and renv changes --- .github/workflows/pr-close-signal.yaml | 5 +- .github/workflows/pr-comment.yaml | 11 +- .github/workflows/pr-post-remove-branch.yaml | 2 +- .github/workflows/pr-preflight.yaml | 2 +- .github/workflows/pr-receive.yaml | 17 +- .github/workflows/sandpaper-main.yaml | 18 +- .github/workflows/sandpaper-version.txt | 2 +- .github/workflows/update-cache.yaml | 12 +- .github/workflows/update-workflows.yaml | 6 +- renv/activate.R | 105 +++++++- renv/profiles/lesson-requirements/renv.lock | 237 +++++++++---------- 11 files changed, 257 insertions(+), 160 deletions(-) diff --git a/.github/workflows/pr-close-signal.yaml b/.github/workflows/pr-close-signal.yaml index 9b129d5d..b1303c26 100644 --- a/.github/workflows/pr-close-signal.yaml +++ b/.github/workflows/pr-close-signal.yaml @@ -8,7 +8,7 @@ on: jobs: send-close-signal: name: "Send closing signal" - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 if: ${{ github.event.action == 'closed' }} steps: - name: "Create PRtifact" @@ -16,8 +16,7 @@ jobs: mkdir -p ./pr printf ${{ github.event.number }} > ./pr/NUM - name: Upload Diff - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: pr path: ./pr - diff --git a/.github/workflows/pr-comment.yaml b/.github/workflows/pr-comment.yaml index bb2eb03c..f80d9d0c 100644 --- a/.github/workflows/pr-comment.yaml +++ b/.github/workflows/pr-comment.yaml @@ -20,7 +20,7 @@ jobs: # - no .github files were committed test-pr: name: "Test if pull request is valid" - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 if: > github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success' @@ -74,7 +74,7 @@ jobs: create-branch: name: "Create Git Branch" needs: test-pr - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 if: ${{ needs.test-pr.outputs.is_valid == 'true' }} env: NR: ${{ needs.test-pr.outputs.number }} @@ -82,7 +82,7 @@ jobs: contents: write steps: - name: 'Checkout md outputs' - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: ref: md-outputs path: built @@ -120,7 +120,7 @@ jobs: comment-pr: name: "Comment on Pull Request" needs: [test-pr, create-branch] - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 if: ${{ needs.test-pr.outputs.is_valid == 'true' }} env: NR: ${{ needs.test-pr.outputs.number }} @@ -150,7 +150,7 @@ jobs: comment-changed-workflow: name: "Comment if workflow files have changed" needs: test-pr - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 if: ${{ always() && needs.test-pr.outputs.is_valid == 'false' }} env: NR: ${{ github.event.workflow_run.pull_requests[0].number }} @@ -182,4 +182,3 @@ jobs: with: pr: ${{ env.NR }} body: ${{ env.body }} - diff --git a/.github/workflows/pr-post-remove-branch.yaml b/.github/workflows/pr-post-remove-branch.yaml index 62c2e98d..9419e2be 100644 --- a/.github/workflows/pr-post-remove-branch.yaml +++ b/.github/workflows/pr-post-remove-branch.yaml @@ -9,7 +9,7 @@ on: jobs: delete: name: "Delete branch from Pull Request" - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 if: > github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success' diff --git a/.github/workflows/pr-preflight.yaml b/.github/workflows/pr-preflight.yaml index d0d7420d..34ad7aed 100644 --- a/.github/workflows/pr-preflight.yaml +++ b/.github/workflows/pr-preflight.yaml @@ -11,7 +11,7 @@ jobs: test-pr: name: "Test if pull request is valid" if: ${{ github.event.action != 'closed' }} - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 outputs: is_valid: ${{ steps.check-pr.outputs.VALID }} permissions: diff --git a/.github/workflows/pr-receive.yaml b/.github/workflows/pr-receive.yaml index 371ef542..7fbff6cd 100644 --- a/.github/workflows/pr-receive.yaml +++ b/.github/workflows/pr-receive.yaml @@ -13,7 +13,7 @@ jobs: test-pr: name: "Record PR number" if: ${{ github.event.action != 'closed' }} - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 outputs: is_valid: ${{ steps.check-pr.outputs.VALID }} steps: @@ -25,7 +25,7 @@ jobs: - name: "Upload PR number" id: upload if: ${{ always() }} - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: pr path: ${{ github.workspace }}/NR @@ -48,7 +48,7 @@ jobs: build-md-source: name: "Build markdown source files if valid" needs: test-pr - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 if: ${{ needs.test-pr.outputs.is_valid == 'true' }} env: GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} @@ -58,10 +58,10 @@ jobs: MD: ${{ github.workspace }}/site/built steps: - name: "Check Out Main Branch" - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: "Check Out Staging Branch" - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: ref: md-outputs path: ${{ env.MD }} @@ -107,20 +107,21 @@ jobs: shell: Rscript {0} - name: "Upload PR" - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: pr path: ${{ env.PR }} + overwrite: true - name: "Upload Diff" - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: diff path: ${{ env.CHIVE }} retention-days: 1 - name: "Upload Build" - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: built path: ${{ env.MD }} diff --git a/.github/workflows/sandpaper-main.yaml b/.github/workflows/sandpaper-main.yaml index e17707ac..a1416977 100644 --- a/.github/workflows/sandpaper-main.yaml +++ b/.github/workflows/sandpaper-main.yaml @@ -21,7 +21,10 @@ on: jobs: full-build: name: "Build Full Site" - runs-on: ubuntu-latest + + # 2024-10-01: ubuntu-latest is now 24.04 and R is not installed by default in the runner image + # pin to 22.04 for now + runs-on: ubuntu-22.04 permissions: checks: write contents: write @@ -29,10 +32,18 @@ jobs: env: GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} RENV_PATHS_ROOT: ~/.local/share/renv/ + services: + mysql: + image: mysql:8.0 + env: + MYSQL_ROOT_PASSWORD: root + ports: + - "8888:3306" + options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 steps: - name: "Checkout Lesson" - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: "Set up R" uses: r-lib/actions/setup-r@v2 @@ -53,6 +64,9 @@ jobs: with: cache-version: ${{ secrets.CACHE_VERSION }} + - name: Setup slurm + uses: koesterlab/setup-slurm-action@v1 + - name: "Deploy Site" run: | reset <- "${{ github.event.inputs.reset }}" == "true" diff --git a/.github/workflows/sandpaper-version.txt b/.github/workflows/sandpaper-version.txt index 19270385..084c7bd0 100644 --- a/.github/workflows/sandpaper-version.txt +++ b/.github/workflows/sandpaper-version.txt @@ -1 +1 @@ -0.16.5 +0.16.10 diff --git a/.github/workflows/update-cache.yaml b/.github/workflows/update-cache.yaml index 676d7424..a011c0c0 100644 --- a/.github/workflows/update-cache.yaml +++ b/.github/workflows/update-cache.yaml @@ -14,7 +14,7 @@ on: jobs: preflight: name: "Preflight Check" - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 outputs: ok: ${{ steps.check.outputs.ok }} steps: @@ -36,14 +36,14 @@ jobs: check_renv: name: "Check if We Need {renv}" - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 needs: preflight if: ${{ needs.preflight.outputs.ok == 'true'}} outputs: needed: ${{ steps.renv.outputs.exists }} steps: - name: "Checkout Lesson" - uses: actions/checkout@v3 + uses: actions/checkout@v4 - id: renv run: | if [[ -d renv ]]; then @@ -52,7 +52,7 @@ jobs: check_token: name: "Check SANDPAPER_WORKFLOW token" - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 needs: check_renv if: ${{ needs.check_renv.outputs.needed == 'true' }} outputs: @@ -69,14 +69,14 @@ jobs: name: "Update Package Cache" needs: check_token if: ${{ needs.check_token.outputs.repo== 'true' }} - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 env: GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} RENV_PATHS_ROOT: ~/.local/share/renv/ steps: - name: "Checkout Lesson" - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: "Set up R" uses: r-lib/actions/setup-r@v2 diff --git a/.github/workflows/update-workflows.yaml b/.github/workflows/update-workflows.yaml index 288bcd13..6414cf28 100644 --- a/.github/workflows/update-workflows.yaml +++ b/.github/workflows/update-workflows.yaml @@ -18,7 +18,7 @@ on: jobs: check_token: name: "Check SANDPAPER_WORKFLOW token" - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 outputs: workflow: ${{ steps.validate.outputs.wf }} repo: ${{ steps.validate.outputs.repo }} @@ -31,12 +31,12 @@ jobs: update_workflow: name: "Update Workflow" - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 needs: check_token if: ${{ needs.check_token.outputs.workflow == 'true' }} steps: - name: "Checkout Repository" - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Update Workflows id: update diff --git a/renv/activate.R b/renv/activate.R index d13f9932..0eb51088 100644 --- a/renv/activate.R +++ b/renv/activate.R @@ -2,7 +2,7 @@ local({ # the requested version of renv - version <- "1.0.7" + version <- "1.0.11" attr(version, "sha") <- NULL # the project directory @@ -98,6 +98,66 @@ local({ unloadNamespace("renv") # load bootstrap tools + ansify <- function(text) { + if (renv_ansify_enabled()) + renv_ansify_enhanced(text) + else + renv_ansify_default(text) + } + + renv_ansify_enabled <- function() { + + override <- Sys.getenv("RENV_ANSIFY_ENABLED", unset = NA) + if (!is.na(override)) + return(as.logical(override)) + + pane <- Sys.getenv("RSTUDIO_CHILD_PROCESS_PANE", unset = NA) + if (identical(pane, "build")) + return(FALSE) + + testthat <- Sys.getenv("TESTTHAT", unset = "false") + if (tolower(testthat) %in% "true") + return(FALSE) + + iderun <- Sys.getenv("R_CLI_HAS_HYPERLINK_IDE_RUN", unset = "false") + if (tolower(iderun) %in% "false") + return(FALSE) + + TRUE + + } + + renv_ansify_default <- function(text) { + text + } + + renv_ansify_enhanced <- function(text) { + + # R help links + pattern <- "`\\?(renv::(?:[^`])+)`" + replacement <- "`\033]8;;ide:help:\\1\a?\\1\033]8;;\a`" + text <- gsub(pattern, replacement, text, perl = TRUE) + + # runnable code + pattern <- "`(renv::(?:[^`])+)`" + replacement <- "`\033]8;;ide:run:\\1\a\\1\033]8;;\a`" + text <- gsub(pattern, replacement, text, perl = TRUE) + + # return ansified text + text + + } + + renv_ansify_init <- function() { + + envir <- renv_envir_self() + if (renv_ansify_enabled()) + assign("ansify", renv_ansify_enhanced, envir = envir) + else + assign("ansify", renv_ansify_default, envir = envir) + + } + `%||%` <- function(x, y) { if (is.null(x)) y else x } @@ -142,7 +202,10 @@ local({ # compute common indent indent <- regexpr("[^[:space:]]", lines) common <- min(setdiff(indent, -1L)) - leave - paste(substring(lines, common), collapse = "\n") + text <- paste(substring(lines, common), collapse = "\n") + + # substitute in ANSI links for executable renv code + ansify(text) } @@ -305,8 +368,11 @@ local({ quiet = TRUE ) - if ("headers" %in% names(formals(utils::download.file))) - args$headers <- renv_bootstrap_download_custom_headers(url) + if ("headers" %in% names(formals(utils::download.file))) { + headers <- renv_bootstrap_download_custom_headers(url) + if (length(headers) && is.character(headers)) + args$headers <- headers + } do.call(utils::download.file, args) @@ -385,10 +451,21 @@ local({ for (type in types) { for (repos in renv_bootstrap_repos()) { + # build arguments for utils::available.packages() call + args <- list(type = type, repos = repos) + + # add custom headers if available -- note that + # utils::available.packages() will pass this to download.file() + if ("headers" %in% names(formals(utils::download.file))) { + headers <- renv_bootstrap_download_custom_headers(repos) + if (length(headers) && is.character(headers)) + args$headers <- headers + } + # retrieve package database db <- tryCatch( as.data.frame( - utils::available.packages(type = type, repos = repos), + do.call(utils::available.packages, args), stringsAsFactors = FALSE ), error = identity @@ -470,6 +547,14 @@ local({ } + renv_bootstrap_github_token <- function() { + for (envvar in c("GITHUB_TOKEN", "GITHUB_PAT", "GH_TOKEN")) { + envval <- Sys.getenv(envvar, unset = NA) + if (!is.na(envval)) + return(envval) + } + } + renv_bootstrap_download_github <- function(version) { enabled <- Sys.getenv("RENV_BOOTSTRAP_FROM_GITHUB", unset = "TRUE") @@ -477,16 +562,16 @@ local({ return(FALSE) # prepare download options - pat <- Sys.getenv("GITHUB_PAT") - if (nzchar(Sys.which("curl")) && nzchar(pat)) { + token <- renv_bootstrap_github_token() + if (nzchar(Sys.which("curl")) && nzchar(token)) { fmt <- "--location --fail --header \"Authorization: token %s\"" - extra <- sprintf(fmt, pat) + extra <- sprintf(fmt, token) saved <- options("download.file.method", "download.file.extra") options(download.file.method = "curl", download.file.extra = extra) on.exit(do.call(base::options, saved), add = TRUE) - } else if (nzchar(Sys.which("wget")) && nzchar(pat)) { + } else if (nzchar(Sys.which("wget")) && nzchar(token)) { fmt <- "--header=\"Authorization: token %s\"" - extra <- sprintf(fmt, pat) + extra <- sprintf(fmt, token) saved <- options("download.file.method", "download.file.extra") options(download.file.method = "wget", download.file.extra = extra) on.exit(do.call(base::options, saved), add = TRUE) diff --git a/renv/profiles/lesson-requirements/renv.lock b/renv/profiles/lesson-requirements/renv.lock index 6f1b5ac5..911588be 100644 --- a/renv/profiles/lesson-requirements/renv.lock +++ b/renv/profiles/lesson-requirements/renv.lock @@ -1,6 +1,6 @@ { "R": { - "Version": "4.4.0", + "Version": "4.4.2", "Repositories": [ { "Name": "carpentries", @@ -19,18 +19,18 @@ "Packages": { "DBI": { "Package": "DBI", - "Version": "1.2.2", + "Version": "1.2.3", "Source": "Repository", "Repository": "CRAN", "Requirements": [ "R", "methods" ], - "Hash": "164809cd72e1d5160b4cb3aa57f510fe" + "Hash": "065ae649b05f1ff66bb0c793107508f5" }, "MASS": { "Package": "MASS", - "Version": "7.3-60.2", + "Version": "7.3-61", "Source": "Repository", "Repository": "CRAN", "Requirements": [ @@ -41,11 +41,11 @@ "stats", "utils" ], - "Hash": "2f342c46163b0b54d7b64d1f798e2c78" + "Hash": "0cafd6f0500e5deba33be22c46bf6055" }, "Matrix": { "Package": "Matrix", - "Version": "1.7-0", + "Version": "1.7-1", "Source": "Repository", "Repository": "CRAN", "Requirements": [ @@ -58,7 +58,7 @@ "stats", "utils" ], - "Hash": "1920b2f11133b12350024297d8a4ff4a" + "Hash": "5122bb14d8736372411f955e1b16bc8a" }, "R6": { "Package": "R6", @@ -82,34 +82,34 @@ }, "Rcpp": { "Package": "Rcpp", - "Version": "1.0.12", + "Version": "1.0.13-1", "Source": "Repository", "Repository": "CRAN", "Requirements": [ "methods", "utils" ], - "Hash": "5ea2700d21e038ace58269ecdbeb9ec0" + "Hash": "6b868847b365672d6c1677b1608da9ed" }, "askpass": { "Package": "askpass", - "Version": "1.2.0", + "Version": "1.2.1", "Source": "Repository", "Repository": "CRAN", "Requirements": [ "sys" ], - "Hash": "cad6cf7f1d5f6e906700b9d3e718c796" + "Hash": "c39f4155b3ceb1a9a2799d700fbd4b6a" }, "backports": { "Package": "backports", - "Version": "1.4.1", + "Version": "1.5.0", "Source": "Repository", "Repository": "CRAN", "Requirements": [ "R" ], - "Hash": "c39fbec8a30d23e721980b8afb31984c" + "Hash": "e1e1b9d75c37401117b636b7ae50827a" }, "base64enc": { "Package": "base64enc", @@ -133,17 +133,17 @@ }, "bit": { "Package": "bit", - "Version": "4.0.5", + "Version": "4.5.0", "Source": "Repository", "Repository": "CRAN", "Requirements": [ "R" ], - "Hash": "d242abec29412ce988848d0294b208fd" + "Hash": "5dc7b2677d65d0e874fc4aaf0e879987" }, "bit64": { "Package": "bit64", - "Version": "4.0.5", + "Version": "4.5.2", "Source": "Repository", "Repository": "CRAN", "Requirements": [ @@ -153,7 +153,7 @@ "stats", "utils" ], - "Hash": "9fe98599ca456d6552421db0d6772d8f" + "Hash": "e84984bf5f12a18628d9a02322128dfd" }, "blob": { "Package": "blob", @@ -169,14 +169,13 @@ }, "broom": { "Package": "broom", - "Version": "1.0.5", + "Version": "1.0.7", "Source": "Repository", "Repository": "CRAN", "Requirements": [ "R", "backports", "dplyr", - "ellipsis", "generics", "glue", "lifecycle", @@ -186,11 +185,11 @@ "tibble", "tidyr" ], - "Hash": "fd25391c3c4f6ecf0fa95f1e6d15378c" + "Hash": "8fcc818f3b9887aebaf206f141437cc9" }, "bslib": { "Package": "bslib", - "Version": "0.7.0", + "Version": "0.8.0", "Source": "Repository", "Repository": "CRAN", "Requirements": [ @@ -208,7 +207,7 @@ "rlang", "sass" ], - "Hash": "8644cc53f43828f19133548195d7e59e" + "Hash": "b299c6741ca9746fb227debcb0f9fb6c" }, "cachem": { "Package": "cachem", @@ -279,7 +278,7 @@ }, "colorspace": { "Package": "colorspace", - "Version": "2.1-0", + "Version": "2.1-1", "Source": "Repository", "Repository": "CRAN", "Requirements": [ @@ -289,7 +288,7 @@ "methods", "stats" ], - "Hash": "f20c47fd52fae58b4e377c37bb8c335b" + "Hash": "d954cb1c57e8d8b756165d7ba18aa55a" }, "conflicted": { "Package": "conflicted", @@ -306,13 +305,13 @@ }, "cpp11": { "Package": "cpp11", - "Version": "0.4.7", + "Version": "0.5.0", "Source": "Repository", "Repository": "CRAN", "Requirements": [ "R" ], - "Hash": "5a295d7d963cc5035284dcdbaf334f4e" + "Hash": "91570bba75d0c9d3f1040c835cee8fba" }, "crayon": { "Package": "crayon", @@ -328,7 +327,7 @@ }, "crew": { "Package": "crew", - "Version": "0.9.5", + "Version": "0.10.2", "Source": "Repository", "Repository": "CRAN", "Requirements": [ @@ -350,28 +349,28 @@ "tools", "utils" ], - "Hash": "1498e9c5b0f2a644d7c4a8534c9d20fe" + "Hash": "40745863e75317c534992c4796af8c58" }, "curl": { "Package": "curl", - "Version": "5.2.1", + "Version": "6.0.1", "Source": "Repository", "Repository": "CRAN", "Requirements": [ "R" ], - "Hash": "411ca2c03b1ce5f548345d2fc2685f7a" + "Hash": "e8ba62486230951fcd2b881c5be23f96" }, "data.table": { "Package": "data.table", - "Version": "1.15.4", + "Version": "1.16.2", "Source": "Repository", "Repository": "CRAN", "Requirements": [ "R", "methods" ], - "Hash": "8ee9ac56ef633d0c7cab8b2ca87d683e" + "Hash": "2e00b378fc3be69c865120d9f313039a" }, "dbplyr": { "Package": "dbplyr", @@ -403,14 +402,14 @@ }, "digest": { "Package": "digest", - "Version": "0.6.36", + "Version": "0.6.37", "Source": "Repository", "Repository": "CRAN", "Requirements": [ "R", "utils" ], - "Hash": "fd6824ad91ede64151e93af67df6376b" + "Hash": "33698c4b3127fc9f506654607fb73676" }, "dplyr": { "Package": "dplyr", @@ -454,27 +453,15 @@ ], "Hash": "54ed3ea01b11e81a86544faaecfef8e2" }, - "ellipsis": { - "Package": "ellipsis", - "Version": "0.3.2", - "Source": "Repository", - "Repository": "CRAN", - "Requirements": [ - "R", - "rlang" - ], - "Hash": "bb0eec2fe32e88d9e2836c2f73ea2077" - }, "evaluate": { "Package": "evaluate", - "Version": "0.23", + "Version": "1.0.1", "Source": "Repository", "Repository": "CRAN", "Requirements": [ - "R", - "methods" + "R" ], - "Hash": "daf4a1246be12c1fa8c7705a0935c1a0" + "Hash": "3fd29944b231036ad67c3edb32e02201" }, "fansi": { "Package": "fansi", @@ -490,10 +477,10 @@ }, "farver": { "Package": "farver", - "Version": "2.1.1", + "Version": "2.1.2", "Source": "Repository", "Repository": "CRAN", - "Hash": "8106d78941f34855c440ddb946b8f7a5" + "Hash": "680887028577f3fa2a81e410ed0d6e42" }, "fastmap": { "Package": "fastmap", @@ -504,7 +491,7 @@ }, "fontawesome": { "Package": "fontawesome", - "Version": "0.5.2", + "Version": "0.5.3", "Source": "Repository", "Repository": "CRAN", "Requirements": [ @@ -512,7 +499,7 @@ "htmltools", "rlang" ], - "Hash": "c2efdd5f0bcd1ea861c2d4e2a883a67d" + "Hash": "bd1297f9b5b1fc1372d19e2c4cd82215" }, "forcats": { "Package": "forcats", @@ -532,14 +519,14 @@ }, "fs": { "Package": "fs", - "Version": "1.6.4", + "Version": "1.6.5", "Source": "Repository", "Repository": "CRAN", "Requirements": [ "R", "methods" ], - "Hash": "15aeb8c27f5ea5161f9f6a641fafd93a" + "Hash": "7f48af39fa27711ea5fbd183b399920d" }, "gargle": { "Package": "gargle", @@ -611,14 +598,14 @@ }, "glue": { "Package": "glue", - "Version": "1.7.0", + "Version": "1.8.0", "Source": "Repository", "Repository": "CRAN", "Requirements": [ "R", "methods" ], - "Hash": "e0b3a53876554bd45879e596cdb10a52" + "Hash": "5899f1eaa825580172bb56c08266f37c" }, "googledrive": { "Package": "googledrive", @@ -675,7 +662,7 @@ }, "gtable": { "Package": "gtable", - "Version": "0.3.5", + "Version": "0.3.6", "Source": "Repository", "Repository": "CRAN", "Requirements": [ @@ -684,9 +671,10 @@ "glue", "grid", "lifecycle", - "rlang" + "rlang", + "stats" ], - "Hash": "e18861963cbc65a27736e02b3cd3c4a0" + "Hash": "de949855009e2d4d0e52a844e30617ae" }, "haven": { "Package": "haven", @@ -711,14 +699,14 @@ }, "highr": { "Package": "highr", - "Version": "0.10", + "Version": "0.11", "Source": "Repository", "Repository": "CRAN", "Requirements": [ "R", "xfun" ], - "Hash": "06230136b2d2b9ba5805e1963fa6e890" + "Hash": "d65ba49117ca223614f71b60d85b8ab7" }, "hms": { "Package": "hms", @@ -793,7 +781,7 @@ }, "igraph": { "Package": "igraph", - "Version": "2.0.3", + "Version": "2.1.1", "Source": "Repository", "Repository": "CRAN", "Requirements": [ @@ -812,7 +800,7 @@ "utils", "vctrs" ], - "Hash": "c3b7d801d722e26e4cd888e042bf9af5" + "Hash": "c03878b48737a0e2da3b772d7b2e22da" }, "isoband": { "Package": "isoband", @@ -837,17 +825,17 @@ }, "jsonlite": { "Package": "jsonlite", - "Version": "1.8.8", + "Version": "1.8.9", "Source": "Repository", "Repository": "CRAN", "Requirements": [ "methods" ], - "Hash": "e1b9c55281c5adc4dd113652d9e26768" + "Hash": "4e993b65c2c3ffbffce7bb3e2c6f832b" }, "knitr": { "Package": "knitr", - "Version": "1.46", + "Version": "1.49", "Source": "Repository", "Repository": "CRAN", "Requirements": [ @@ -859,7 +847,7 @@ "xfun", "yaml" ], - "Hash": "6e008ab1d696a5283c79765fa7b56b47" + "Hash": "9fcb189926d93c636dea94fbe4f44480" }, "labeling": { "Package": "labeling", @@ -874,14 +862,14 @@ }, "later": { "Package": "later", - "Version": "1.3.2", + "Version": "1.4.1", "Source": "Repository", "Repository": "CRAN", "Requirements": [ "Rcpp", "rlang" ], - "Hash": "a3e051d405326b8b0012377434c62b37" + "Hash": "501744395cac0bab0fbcfab9375ae92c" }, "lattice": { "Package": "lattice", @@ -974,14 +962,14 @@ }, "mirai": { "Package": "mirai", - "Version": "1.1.1", + "Version": "1.3.1", "Source": "Repository", "Repository": "CRAN", "Requirements": [ "R", "nanonext" ], - "Hash": "f8b0cda5304cad0b629e98c09aad5471" + "Hash": "0746cbcb4e0a198d26b48fc64c61e710" }, "modelr": { "Package": "modelr", @@ -1014,17 +1002,17 @@ }, "nanonext": { "Package": "nanonext", - "Version": "1.1.1", + "Version": "1.4.0", "Source": "Repository", "Repository": "CRAN", "Requirements": [ "R" ], - "Hash": "1f884ea35086b957409a9d351eaa2d9e" + "Hash": "2f9a62823a91f75349099d95c182787c" }, "nlme": { "Package": "nlme", - "Version": "3.1-164", + "Version": "3.1-166", "Source": "Repository", "Repository": "CRAN", "Requirements": [ @@ -1034,17 +1022,17 @@ "stats", "utils" ], - "Hash": "a623a2239e642806158bc4dc3f51565d" + "Hash": "ccbb8846be320b627e6aa2b4616a2ded" }, "openssl": { "Package": "openssl", - "Version": "2.2.0", + "Version": "2.2.2", "Source": "Repository", "Repository": "CRAN", "Requirements": [ "askpass" ], - "Hash": "2bcca3848e4734eb3b16103bc9aa4b8e" + "Hash": "d413e0fef796c9401a4419485f709ca1" }, "palmerpenguins": { "Package": "palmerpenguins", @@ -1122,7 +1110,7 @@ }, "promises": { "Package": "promises", - "Version": "1.3.0", + "Version": "1.3.2", "Source": "Repository", "Repository": "CRAN", "Requirements": [ @@ -1134,18 +1122,18 @@ "rlang", "stats" ], - "Hash": "434cd5388a3979e74be5c219bcd6e77d" + "Hash": "c84fd4f75ea1f5434735e08b7f50fbca" }, "ps": { "Package": "ps", - "Version": "1.7.6", + "Version": "1.8.1", "Source": "Repository", "Repository": "CRAN", "Requirements": [ "R", "utils" ], - "Hash": "dd2b9319ee0656c8acf45c7f40c59de7" + "Hash": "b4404b1de13758dea1c0484ad0d48563" }, "purrr": { "Package": "purrr", @@ -1164,7 +1152,7 @@ }, "quarto": { "Package": "quarto", - "Version": "1.4", + "Version": "1.4.4", "Source": "Repository", "Repository": "CRAN", "Requirements": [ @@ -1180,18 +1168,21 @@ "utils", "yaml" ], - "Hash": "c94c271f9b998d116186a78b2a9b23c1" + "Hash": "af456d7a181750812bd8b2bfedb3ea4e" }, "ragg": { "Package": "ragg", - "Version": "1.3.2", + "Version": "1.3.3", "Source": "Repository", - "Repository": "CRAN", + "Repository": "https://carpentries.r-universe.dev", + "RemoteUrl": "https://github.com/r-lib/ragg", + "RemoteRef": "v1.3.3", + "RemoteSha": "6f2279ae8cd0e0d7e9d0e1ede2b742666f9f1d49", "Requirements": [ "systemfonts", "textshaping" ], - "Hash": "e3087db406e079a8a2fd87f413918ed3" + "Hash": "0595fe5e47357111f29ad19101c7d271" }, "rappdirs": { "Package": "rappdirs", @@ -1260,17 +1251,17 @@ }, "renv": { "Package": "renv", - "Version": "1.0.7", + "Version": "1.0.11", "Source": "Repository", "Repository": "CRAN", "Requirements": [ "utils" ], - "Hash": "397b7b2a265bc5a7a06852524dabae20" + "Hash": "47623f66b4e80b3b0587bc5d7b309888" }, "reprex": { "Package": "reprex", - "Version": "2.1.0", + "Version": "2.1.1", "Source": "Repository", "Repository": "CRAN", "Requirements": [ @@ -1288,7 +1279,7 @@ "utils", "withr" ], - "Hash": "1425f91b4d5d9a8f25352c44a3d914ed" + "Hash": "97b1d5361a24d9fb588db7afe3e5bcbf" }, "rlang": { "Package": "rlang", @@ -1303,7 +1294,7 @@ }, "rmarkdown": { "Package": "rmarkdown", - "Version": "2.27", + "Version": "2.29", "Source": "Repository", "Repository": "CRAN", "Requirements": [ @@ -1322,14 +1313,14 @@ "xfun", "yaml" ], - "Hash": "27f9502e1cdbfa195f94e03b0f517484" + "Hash": "df99277f63d01c34e95e3d2f06a79736" }, "rstudioapi": { "Package": "rstudioapi", - "Version": "0.16.0", + "Version": "0.17.1", "Source": "Repository", "Repository": "CRAN", - "Hash": "96710351d642b70e8f02ddeb237c46a7" + "Hash": "5f90cd73946d706cfe26024294236113" }, "rvest": { "Package": "rvest", @@ -1386,13 +1377,13 @@ }, "secretbase": { "Package": "secretbase", - "Version": "1.0.0", + "Version": "1.0.3", "Source": "Repository", "Repository": "CRAN", "Requirements": [ "R" ], - "Hash": "ac4ce0d0f56d126e63e7f01013f59ce2" + "Hash": "eaf84737a6da68c1e843979963c09a6b" }, "selectr": { "Package": "selectr", @@ -1409,16 +1400,19 @@ }, "stringi": { "Package": "stringi", - "Version": "1.8.3", + "Version": "1.8.4", "Source": "Repository", - "Repository": "CRAN", + "Repository": "https://carpentries.r-universe.dev", + "RemoteUrl": "https://github.com/gagolews/stringi", + "RemoteRef": "v1.8.4", + "RemoteSha": "f5a91d1869ec4c757d37519f710ec1fa4f74a1c8", "Requirements": [ "R", "stats", "tools", "utils" ], - "Hash": "058aebddea264f4c99401515182e656a" + "Hash": "39e1144fd75428983dc3f63aa53dfa91" }, "stringr": { "Package": "stringr", @@ -1439,10 +1433,10 @@ }, "sys": { "Package": "sys", - "Version": "3.4.2", + "Version": "3.4.3", "Source": "Repository", "Repository": "CRAN", - "Hash": "3a1be13d68d47a8cd0bfd74739ca1555" + "Hash": "de342ebfebdbf40477d0758d05426646" }, "systemfonts": { "Package": "systemfonts", @@ -1458,7 +1452,7 @@ }, "tarchetypes": { "Package": "tarchetypes", - "Version": "0.9.0", + "Version": "0.11.0", "Source": "Repository", "Repository": "CRAN", "Requirements": [ @@ -1475,11 +1469,11 @@ "vctrs", "withr" ], - "Hash": "094a74cdaa048b727d52aebf4c80ed7a" + "Hash": "cf140014f9d00f97f4bd22d961e20471" }, "targets": { "Package": "targets", - "Version": "1.7.1", + "Version": "1.9.0", "Source": "Repository", "Repository": "CRAN", "Requirements": [ @@ -1503,19 +1497,23 @@ "vctrs", "yaml" ], - "Hash": "d79b21ef513564351577d7064e1fc4dd" + "Hash": "788a40e60a695237faca8af4015fd703" }, "textshaping": { "Package": "textshaping", - "Version": "0.3.7", + "Version": "0.4.0", "Source": "Repository", - "Repository": "CRAN", + "Repository": "https://carpentries.r-universe.dev", + "RemoteUrl": "https://github.com/r-lib/textshaping", + "RemoteRef": "v0.4.0", + "RemoteSha": "76682df21dce8ef29e905a90dd05732a58b1249f", "Requirements": [ "R", "cpp11", + "lifecycle", "systemfonts" ], - "Hash": "997aac9ad649e0ef3b97f96cddd5622b" + "Hash": "5142f8bc78ed3d819d26461b641627ce" }, "tibble": { "Package": "tibble", @@ -1628,13 +1626,13 @@ }, "tinytex": { "Package": "tinytex", - "Version": "0.51", + "Version": "0.54", "Source": "Repository", "Repository": "CRAN", "Requirements": [ "xfun" ], - "Hash": "d44e2fcd2e4e076f0aac540208559d1d" + "Hash": "3ec7e3ddcacc2d34a9046941222bf94d" }, "tzdb": { "Package": "tzdb", @@ -1659,13 +1657,13 @@ }, "uuid": { "Package": "uuid", - "Version": "1.2-0", + "Version": "1.2-1", "Source": "Repository", "Repository": "CRAN", "Requirements": [ "R" ], - "Hash": "303c19bfd970bece872f93a824e323d9" + "Hash": "34e965e62a41fcafb1ca60e9b142085b" }, "vctrs": { "Package": "vctrs", @@ -1737,7 +1735,7 @@ }, "withr": { "Package": "withr", - "Version": "3.0.0", + "Version": "3.0.2", "Source": "Repository", "Repository": "CRAN", "Requirements": [ @@ -1745,19 +1743,20 @@ "grDevices", "graphics" ], - "Hash": "d31b6c62c10dcf11ec530ca6b0dd5d35" + "Hash": "cc2d62c76458d425210d1eb1478b30b4" }, "xfun": { "Package": "xfun", - "Version": "0.44", + "Version": "0.49", "Source": "Repository", "Repository": "CRAN", "Requirements": [ + "R", "grDevices", "stats", "tools" ], - "Hash": "317a0538d32f4a009658bcedb7923f4b" + "Hash": "8687398773806cfff9401a2feca96298" }, "xml2": { "Package": "xml2", @@ -1774,10 +1773,10 @@ }, "yaml": { "Package": "yaml", - "Version": "2.3.8", + "Version": "2.3.10", "Source": "Repository", "Repository": "CRAN", - "Hash": "29240487a071f535f5e5d5a323b7afbd" + "Hash": "51dab85c6c98e50a18d7551e9d49f76c" } } }