From 023716e9184821372e200848b4afae0a92b87651 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Cs=C3=A1rdi?= Date: Thu, 1 Oct 2026 12:44:32 +0200 Subject: [PATCH 1/3] Inline scripts See docs. --- NEWS.md | 5 + src/args.rs | 1 + src/cache_cmd.rs | 18 +- src/data/rvenv/R.sh | 4 +- src/help-generated.in | 6 +- src/help/cache-clean.md | 3 +- src/help/cache-info.md | 5 +- src/help/run.md | 39 ++- src/main.rs | 2 + src/proj.rs | 215 ++++++++++++-- src/rvenv.rs | 41 ++- src/script_meta.rs | 561 +++++++++++++++++++++++++++++++++++ src/scrun.rs | 105 ++++++- src/stdout_redirect.rs | 109 +++++++ tests/test-linux.sh | 44 +++ tests/test-macos.sh | 44 +++ website/_partials/faq.md | 23 ++ website/assets/logo-mono.svg | 26 ++ website/assets/logo.svg | 45 +++ 19 files changed, 1256 insertions(+), 40 deletions(-) create mode 100644 src/script_meta.rs create mode 100644 src/stdout_redirect.rs create mode 100644 website/assets/logo-mono.svg create mode 100644 website/assets/logo.svg diff --git a/NEWS.md b/NEWS.md index 2fa57bcf4..122be64e7 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,10 @@ # Development version +* `rig run` now support self-contained scripts, that declare the packages + and the R version they need in a `# /// script` comment block. rig then + runs the script in its own environment, in the cache directory, and + installs R and the packages as needed. + * `rig add` now has a `--json` option. It prints information about the installed R version (or the already installed version that was kept), including whether it is a new installation and whether it is the default. diff --git a/src/args.rs b/src/args.rs index ca0c41674..79647ca44 100644 --- a/src/args.rs +++ b/src/args.rs @@ -1261,6 +1261,7 @@ pub fn rig_app() -> Command { "p3m", "git-mirrors", "url-pkgs", + "scripts", ]), ), ); diff --git a/src/cache_cmd.rs b/src/cache_cmd.rs index e99bb5888..645b4a55a 100644 --- a/src/cache_cmd.rs +++ b/src/cache_cmd.rs @@ -5,7 +5,7 @@ //! their own corner of `real_cache_dir()`, see [`crate::cache`]. Rather than //! teach this module every producer's file-naming scheme, categories are //! derived from the *name of the top-level entry* in the cache directory: -//! `built`, `packages`, `p3m`, `git-mirrors` and `url-pkgs` are their own +//! `built`, `packages`, `p3m`, `git-mirrors`, `url-pkgs` and `scripts` are their own //! category, `metadata` collects every other kind of package metadata //! (binary indexes, CRAN-like databases, package manifests, repo data), and //! anything unrecognized left over at the cache root (e.g. stray files from @@ -30,16 +30,18 @@ enum CacheCategory { P3m, GitMirrors, UrlPkgs, + Scripts, } impl CacheCategory { - const ALL: [CacheCategory; 6] = [ + const ALL: [CacheCategory; 7] = [ CacheCategory::Built, CacheCategory::Packages, CacheCategory::Metadata, CacheCategory::P3m, CacheCategory::GitMirrors, CacheCategory::UrlPkgs, + CacheCategory::Scripts, ]; fn label(self) -> &'static str { @@ -50,6 +52,7 @@ impl CacheCategory { CacheCategory::P3m => "P3M status", CacheCategory::GitMirrors => "Git mirrors", CacheCategory::UrlPkgs => "URL packages", + CacheCategory::Scripts => "Script environments", } } @@ -63,6 +66,7 @@ impl CacheCategory { CacheCategory::P3m => "p3m", CacheCategory::GitMirrors => "git-mirrors", CacheCategory::UrlPkgs => "url-pkgs", + CacheCategory::Scripts => "scripts", } } } @@ -76,6 +80,7 @@ fn classify_entry(name: &str) -> CacheCategory { "p3m" => CacheCategory::P3m, "git-mirrors" => CacheCategory::GitMirrors, "url-pkgs" => CacheCategory::UrlPkgs, + "scripts" => CacheCategory::Scripts, _ => CacheCategory::Metadata, } } @@ -120,8 +125,8 @@ fn dir_usage(path: &Path) -> Usage { usage } -fn cache_breakdown(cache_dir: &Path) -> [Usage; 6] { - let mut totals = [Usage::default(); 6]; +fn cache_breakdown(cache_dir: &Path) -> [Usage; CacheCategory::ALL.len()] { + let mut totals = [Usage::default(); CacheCategory::ALL.len()]; let entries = match fs::read_dir(cache_dir) { Ok(entries) => entries, Err(_) => return totals, @@ -180,6 +185,8 @@ struct CacheInfo { git_mirrors_count: u64, url_pkgs_size: u64, url_pkgs_count: u64, + scripts_size: u64, + scripts_count: u64, total_size: u64, total_count: u64, } @@ -208,6 +215,8 @@ pub fn sc_cache_info(args: &ArgMatches) -> Result<(), Box> { git_mirrors_count: totals[4].count, url_pkgs_size: totals[5].size, url_pkgs_count: totals[5].count, + scripts_size: totals[6].size, + scripts_count: totals[6].count, total_size: total.size, total_count: total.count, }; @@ -316,6 +325,7 @@ mod tests { assert_eq!(classify_entry("leftover-file"), CacheCategory::Metadata); assert_eq!(classify_entry("git-mirrors"), CacheCategory::GitMirrors); assert_eq!(classify_entry("url-pkgs"), CacheCategory::UrlPkgs); + assert_eq!(classify_entry("scripts"), CacheCategory::Scripts); } #[test] diff --git a/src/data/rvenv/R.sh b/src/data/rvenv/R.sh index fa95cfc95..ecbf1f5ce 100644 --- a/src/data/rvenv/R.sh +++ b/src/data/rvenv/R.sh @@ -8,7 +8,9 @@ # # RVENV is derived from this script's own location, so the project directory # can be moved or checked out anywhere. -RVENV=$(cd "$(dirname "$0")/.." && pwd) +# `${0%/*}` instead of `dirname`, which would be one more process to start. +case $0 in */*) RVENV=${0%/*} ;; *) RVENV=. ;; esac +RVENV=$(cd "$RVENV/.." && pwd) export RVENV # R_LIBS is empty, so that .libPaths()[1] stays the project library, and # R_LIBS_SITE points at a path that cannot exist, because an empty one does diff --git a/src/help-generated.in b/src/help-generated.in index a8617d613..7817c60f7 100644 --- a/src/help-generated.in +++ b/src/help-generated.in @@ -10,9 +10,9 @@ const HELP_ADD: &str = "\u{1b}[1m\u{1b}[34mDescription:\u{1b}[39m\u{1b}[22m\n D const ABOUT_AVAILABLE: &str = "List R versions available to install."; const HELP_AVAILABLE: &str = "\u{1b}[1m\u{1b}[34mDescription:\u{1b}[39m\u{1b}[22m\n List R versions available to install.\n\n By default some releases are omitted from the output:\n\n - Versions older than R 3.0.0 are omitted. The installation of these might\n not work at all.\n - Only the latest release is shown for each minor version. E.g. R 4.2.3 is\n listed, but other R 4.2.x versions are not. Use \u{1b}[32m--all\u{1b}[39m to list all\n versions.\n\n Use \u{1b}[32m--json\u{1b}[39m to return the output in JSON. JSON output includes the full time\n stamp (if available) and the download URL as well.\n\n With the \u{1b}[32m--list-distros\u{1b}[39m flag it lists supported Linux distributions.\n\n With the \u{1b}[32m--list-rtools-versions\u{1b}[39m flag it lists supported Rtools versions.\n Rtools contains tools to build R and R packages on Windows. Use \u{1b}[32m--all\u{1b}[39m to\n list all Rtools versions, even very old ones."; const ABOUT_CACHE_CLEAN: &str = "Delete cached files"; -const HELP_CACHE_CLEAN: &str = "\u{1b}[1m\u{1b}[34mDescription:\u{1b}[39m\u{1b}[22m\n Deletes files from rig's cache directory. With no options, deletes the\n whole cache. Use \u{1b}[32m--category\u{1b}[39m to delete only one category of cached files\n (\u{1b}[32mbuilt\u{1b}[39m, \u{1b}[32mpackages\u{1b}[39m, \u{1b}[32mmetadata\u{1b}[39m, \u{1b}[32mp3m\u{1b}[39m, \u{1b}[32mgit-mirrors\u{1b}[39m or \u{1b}[32murl-pkgs\u{1b}[39m),\n leaving the rest in place. See \u{1b}[32mrig cache info\u{1b}[39m\n for what each category contains."; +const HELP_CACHE_CLEAN: &str = "\u{1b}[1m\u{1b}[34mDescription:\u{1b}[39m\u{1b}[22m\n Deletes files from rig's cache directory. With no options, deletes the\n whole cache. Use \u{1b}[32m--category\u{1b}[39m to delete only one category of cached files\n (\u{1b}[32mbuilt\u{1b}[39m, \u{1b}[32mpackages\u{1b}[39m, \u{1b}[32mmetadata\u{1b}[39m, \u{1b}[32mp3m\u{1b}[39m, \u{1b}[32mgit-mirrors\u{1b}[39m, \u{1b}[32murl-pkgs\u{1b}[39m or\n \u{1b}[32mscripts\u{1b}[39m),\n leaving the rest in place. See \u{1b}[32mrig cache info\u{1b}[39m\n for what each category contains."; const ABOUT_CACHE_INFO: &str = "Show cache size and contents"; -const HELP_CACHE_INFO: &str = "\u{1b}[1m\u{1b}[34mDescription:\u{1b}[39m\u{1b}[22m\n Prints the total size of rig's cache directory, broken down into\n categories: packages rig built from source, downloaded package files,\n package metadata (binary package indexes, CRAN-like package databases,\n package manifests, and repository data), the cached PPM status document,\n the persistent git mirrors kept for git/GitHub dependency resolution\n (\u{1b}[32mrig proj lock\u{1b}[39m), and the downloaded archives kept for \u{1b}[32murl\u{1b}[39m dependency\n resolution.\n\n Use \u{1b}[32m--json\u{1b}[39m for machine-readable output."; +const HELP_CACHE_INFO: &str = "\u{1b}[1m\u{1b}[34mDescription:\u{1b}[39m\u{1b}[22m\n Prints the total size of rig's cache directory, broken down into\n categories: packages rig built from source, downloaded package files,\n package metadata (binary package indexes, CRAN-like package databases,\n package manifests, and repository data), the cached PPM status document,\n the persistent git mirrors kept for git/GitHub dependency resolution\n (\u{1b}[32mrig proj lock\u{1b}[39m), the downloaded archives kept for \u{1b}[32murl\u{1b}[39m dependency\n resolution, and the environments of scripts with inline dependencies\n (\u{1b}[32mrig run script.R\u{1b}[39m).\n\n Use \u{1b}[32m--json\u{1b}[39m for machine-readable output."; const ABOUT_CACHE: &str = "Manage rig's download cache"; const HELP_CACHE: &str = "\u{1b}[1m\u{1b}[34mDescription:\u{1b}[39m\u{1b}[22m\n rig caches files it downloads (R binary indexes, CRAN-like package\n databases, package DESCRIPTION manifests, downloaded package files, and\n packages rig built from source) in an OS-specific cache directory, so it\n does not have to re-download them on every run. See \u{1b}[32mrig system dirs --cache\u{1b}[39m\n for the cache directory path.\n\n The global \u{1b}[32m--no-cache\u{1b}[39m flag makes rig skip this cache entirely for the\n current run; it does not affect what \u{1b}[32mrig cache\u{1b}[39m reports or deletes. You can\n also set the \u{1b}[32mRIG_NO_CACHE\u{1b}[39m environment variable to \u{1b}[32mtrue\u{1b}[39m or the \u{1b}[32mno-cache\u{1b}[39m\n config option to \u{1b}[32mtrue\u{1b}[39m."; const ABOUT_CONFIG_CONFIG_FILE_PATH: &str = "Print the path to the rig config file"; @@ -126,7 +126,7 @@ const HELP_RTOOLS_RM: &str = "\u{1b}[1m\u{1b}[34mDescription:\u{1b}[39m\u{1b}[22 const ABOUT_RTOOLS: &str = "Manage Rtools installations"; const HELP_RTOOLS: &str = "\u{1b}[1m\u{1b}[34mDescription:\u{1b}[39m\u{1b}[22m\n Manage Rtools installations (Windows only).\n\n Rtools is the collection of build tools (compilers, \u{1b}[32mmake\u{1b}[39m, etc.) needed to\n build R packages from source on Windows. Each R version needs a matching\n Rtools version.\n\n \u{1b}[32mrig rtools list\u{1b}[39m lists the installed Rtools versions. \u{1b}[32mrig rtools add\u{1b}[39m\n installs Rtools, by default every version needed by the currently installed\n R versions. \u{1b}[32mrig rtools rm\u{1b}[39m removes Rtools versions.\n\n On non-Windows platforms this command does nothing and is hidden."; const ABOUT_RUN: &str = "Run R, an R script or an R project"; -const HELP_RUN: &str = "\u{1b}[1m\u{1b}[34mDescription:\u{1b}[39m\u{1b}[22m\n Run R, an R script or an R project, using the selected R version.\n\n All of these examples allow an \u{1b}[32m--r-version\u{1b}[39m argument, to use a specific R\n version.\n\n rig run # start R\n rig run -f # run an R script\n rig run -e # evaluate an R expression\n rig run ::