Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
1 change: 1 addition & 0 deletions src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1261,6 +1261,7 @@ pub fn rig_app() -> Command {
"p3m",
"git-mirrors",
"url-pkgs",
"scripts",
]),
),
);
Expand Down
18 changes: 14 additions & 4 deletions src/cache_cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 {
Expand All @@ -50,6 +52,7 @@ impl CacheCategory {
CacheCategory::P3m => "P3M status",
CacheCategory::GitMirrors => "Git mirrors",
CacheCategory::UrlPkgs => "URL packages",
CacheCategory::Scripts => "Script environments",
}
}

Expand All @@ -63,6 +66,7 @@ impl CacheCategory {
CacheCategory::P3m => "p3m",
CacheCategory::GitMirrors => "git-mirrors",
CacheCategory::UrlPkgs => "url-pkgs",
CacheCategory::Scripts => "scripts",
}
}
}
Expand All @@ -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,
}
}
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
}
Expand Down Expand Up @@ -208,6 +215,8 @@ pub fn sc_cache_info(args: &ArgMatches) -> Result<(), Box<dyn Error>> {
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,
};
Expand Down Expand Up @@ -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]
Expand Down
4 changes: 3 additions & 1 deletion src/data/rvenv/R.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions src/help-generated.in

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion src/help/cache-clean.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ Delete cached files

Deletes files from rig's cache directory. With no options, deletes the
whole cache. Use `--category` to delete only one category of cached files
(`built`, `packages`, `metadata`, `p3m`, `git-mirrors` or `url-pkgs`),
(`built`, `packages`, `metadata`, `p3m`, `git-mirrors`, `url-pkgs` or
`scripts`),
leaving the rest in place. See [`rig cache info`](cache.qmd#rig-cache-info)
for what each category contains.
5 changes: 3 additions & 2 deletions src/help/cache-info.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ categories: packages rig built from source, downloaded package files,
package metadata (binary package indexes, CRAN-like package databases,
package manifests, and repository data), the cached PPM status document,
the persistent git mirrors kept for git/GitHub dependency resolution
(`rig proj lock`), and the downloaded archives kept for `url` dependency
resolution.
(`rig proj lock`), the downloaded archives kept for `url` dependency
resolution, and the environments of scripts with inline dependencies
(`rig run script.R`).

Use `--json` for machine-readable output.
39 changes: 38 additions & 1 deletion src/help/run.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ version.

```sh
rig run # start R
rig run <script.R> # run an R script
rig run -f <script-file> # run an R script
rig run -e <expression> # evaluate an R expression
rig run <pkg>::<script> # run a script from a package's exec directory
Expand Down Expand Up @@ -52,6 +53,42 @@ the project's own environment instead of the default R version: it runs
Use `--no-project` to ignore a project, or `--r-version` to select an R
version directly.

## Scripts with inline dependencies

`rig run -f script.R` runs an R script (the `-f` can be omitted). A
script can declare the packages and the R version it needs in a comment
block at its top, so it runs anywhere without a project:

```r
# /// script
# [dependencies]
# R = ">= 4.4"
# cli = "*"
# dplyr = ">= 1.1"
#
# [tool.rig]
# exclude-newer = "2026-06-01"
# ///
library(dplyr)
```

The block starts with a `# /// script` line and ends with a `# ///` line.
In between, every line is a comment, and without the leading `#` they form
a TOML document. The block takes the `[dependencies]`, `[[repository]]` and
`[tool.rig]` tables of `rproj.toml`, with the same meaning.

rig creates an environment for the script in its cache directory, and runs
the script there, isolated from your own package library. It picks an
installed R version that fits the `R` requirement, or installs one, then
locks and installs the packages, the same way `rig proj sync` does for a
project. Later runs reuse the environment. Scripts with the same block
share one environment, and changing the block creates a new one. A script
with a block always uses its own environment, even inside a project.
`--r-version` selects the R version, which must fit the `R` requirement.

`rig cache clean --category scripts` deletes all script environments. A
script can start with `#!/usr/bin/env -S rig run`, to run it directly.

## Project scripts

A project can give its own scripts a name, in the `[[bin]]` tables of its
Expand Down Expand Up @@ -93,6 +130,6 @@ library and repositories too, not just its version.
Unlike `R -e`/`R -f`, `Rscript` never echoes back the code it runs, which
fits scripts and pipelines better than `R`'s interactive-style echo.

`--rscript` works with `-e`/`-f`, project scripts, apps and `--activate`,
`--rscript` works with `-e`/`-f`, scripts, project scripts, apps and `--activate`,
but not with `--cmd` (`R CMD` only exists as part of the `R` front-end) or
`--shell` (which never runs the R binary at all).
2 changes: 2 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ use tabular::*;
mod args;
use args::*;

mod script_meta;
mod scrun;
mod stdout_redirect;
use scrun::*;

#[cfg(target_os = "macos")]
Expand Down
Loading
Loading