We are thrilled to announce rig 0.11.0 (and 0.10.0), with lots of new functionalities: user mode R installations, R package and project management and much more. This is the third of three blog posts about the new rig release. This post is about managing R projects.
The first post covered user mode R installations, the second focused on R packages.
Full changelog on the rig web site.
Introduction#
The new rig proj command lets you manage R projects and virtual
environments. An R project lets you share and run R code in a standardized
way. rig’s R projects use an rproj.toml manifest, which is still
under development.
rig proj is new and less battle-tested than other parts of rig.
Please report any issues you find on the rig GitHub repository.
rig’s R project implementation largely works the same way as other project management tools for Python, Rust, and other languages.
The project lifecycle#
A project lifecycle typically involves
rig proj init(orrig proj import) to create a new project.rig proj add(andrig proj remove) to manage project dependencies.rig proj lockto resolve how the project is installed over a range of R versions in a range of platforms.rig proj syncto install a project according to the lock file.rig runto execute R code within the context of the project.
Creating projects#
There are three ways to create an R project. One is to create a brand new
project with rig proj init. This creates a minimal rproj.toml manifest
file that you can either edit manually or via rig proj add:
❯ rig proj init
✓ Created rproj.toml
✓ Created .Renviron
✓ Created .gitignore
✓ Created .rvenvlib/rvenv
ℹ Project set up for R 4.6.1. Next: add dependencies to rproj.toml, then
run `rig proj lock` and `rig proj sync`.❯ cat rproj.toml
[project]
name = "x"
version = "0.1.0"
type = "project"
[dependencies]
R = ">= 4.6"Another way is to convert an existing project to an rproj.toml manifest.
Start with an existing R project with a DESCRIPTION
file and run rig proj import to create an rproj.toml manifest from it.
Or start with an renv lock file and call rig proj renv import.
rig proj import keeps all fields of DESCRIPTION, including various
types of dependencies. For R packages you can use rproj.toml as the
source of truth for package metadata and dependencies and (re)generate
DESCRIPTION from it as needed, e.g. for CRAN submission.
A third way to create a project is to write an rproj.toml manifest manually.
Project types#
There are two types of R manifests: projects and packages. In general we use project as a generic name for both types and consider a package as a specialized project.
For a package, create the R/, src/, etc. directories as usual. See below
for a possible way to use the devtools based development tools for a rig
project.
For projects rig does not have a suggested way for organizing R and other
code, documentation and tests. If in doubt, follow the conventions of
R packages. Use [[bin]] to specify executable scripts within the project,
relative to the project root. You’ll be able to run these scripts with
rig run.
Locking a project#
rig proj lock reads rproj.toml and works out which exact packages
should be downloaded and installed to satisfy all requirements from
rproj.toml. All requirements always include the optional and soft
dependencies. The result is a lock file called rproj.lock. This is also a
TOML file, typically much bigger than the original rproj.toml project
manifest.
Universal lock files#
The lock can and usually does include packages for multiple platforms.
The exact set of platforms and R versions to include in the lock file can
be specified in rproj.toml or at the command line. By default rig includes
the current R release and five platforms: the platform of the current
machine, macOS arm64, Windows x86_64, generic glibc Linux and source
(four platforms if the current platform coincides with one of the others).
A multi-platform lock file means that it is much easier to plan the cross-platform deployment of a project: a team can include all relevant platforms and R versions in the lock file and commit a lock file to version control.
rig also records system requirements in the lock file, for platforms that need them.
Stable URLs#
Universal lock files only really work well if they contain stable URLs for binary packages for all platforms and R versions. This is why it is important to use P3M (or another Posit Package Manager instance) with extended metadata about all older package versions and all their corresponding binary builds. With P3M rig is typically able to generate lock files with binary packages for the last 3-4 R versions on all common platforms.
Sticky lock files#
Adding new dependencies to a project runs rig proj lock, but it keeps the
package versions already in rproj.lock, unless they conflict with the new
requirements. This is important to be able to maintain a stable set of
package versions while still adding new dependencies. To update an already
existing dependency to a newer version, you need to run
rig proj lock --upgrade-package. Or run rig proj lock --upgrade to
upgrade all packages.
Preferring binary packages#
By default rig does not choose an older binary package over a newer source
package. You can change that with the prefer-binary setting in rproj.toml.
E.g. prefer-binary = true means that rig will choose an older binary
package over a newer source package, if the older package also satisfies
the dependency requirements.
Excluding newer package versions#
You can prevent rig from automatically upgrading to newer package versions
by using the exclude-newer setting in rproj.toml. For example,
exclude-newer = "2026-09-30" will exclude any package versions that were
published after the specified date. This works for P3M’s CRAN and
Bioconductor repositories, but not for generic CRAN-like repositories
which typically do not have release dates.
Repositories#
Projects ignore the global repository settings configured via rig repos.
By default projects use P3M’s CRAN and Bioconductor repositories.
This can be configured with [[repository]] entries in rproj.toml.
You can turn off P3M’s CRAN and Bioconductor repositories and you can
also add your own custom repositories.
rig’s solver and universal lock files work best with P3M’s CRAN and Bioconductor metadata, so we strongly recommend using them whenever possible.
Syncing a project#
rig proj sync installs the packages listed in rproj.lock into the
project library in .rvenv/lib, inside the project’s virtual environment.
It locks first if the project manifest has changed since creating the
lock file.
If the current project is a package (type = "package"), then
rig proj sync will also install the package itself into the project
library.
rig proj sync also installs the R version requested in the lock file,
if it is not already installed. This makes it very easy to deploy a
project in an empty container or virtual machine: only rig and the lock
file are needed and rig installs everything required for the project: R,
system requirements and R packages.
rig proj sync installs the hard requirements and the dev dependency
group by default. For a production deployment you can use the --no-dev
option to skip installing development dependencies. Use --group and
--extra to install optional dependency groups and extra dependencies.
Fast and lean libraries#
rig clones the package files when installing packages into the project library, whenever possible. This means that installation is very fast and space-efficient. Having the same package version installed for ten projects on the same disk only takes up the space of a single copy.
The virtual environment#
A rig project has two kinds of files. rig proj init creates the ones that
belong in version control: rproj.toml, .Renviron, .rvenvlib/rvenv and
.gitignore. Commit these, together with rproj.lock, and a fresh clone of
the project works after a rig proj sync, or right away with rig run.
rig proj sync creates the rest in the .rvenv directory: the project
library in .rvenv/lib, R and Rscript wrapper scripts in .rvenv/bin,
and some configuration files. .rvenv is specific to the machine, it is in
.gitignore, and you can delete it any time, rig proj sync creates it
again.
The .Renviron file and the small rvenv package in .rvenvlib make sure
that R uses the project library whenever it starts in the project directory:
from a terminal, or from RStudio, Positron or VS Code. You do not need to
activate the project explicitly, although .rvenv/bin has activation
scripts for the common shells if you prefer that.
Inside the project R only sees the project library, the tools library
(see below) and the packages that come with R. Packages from your user or
site library do not leak into the project.
If the project library is out of date compared to rproj.lock, e.g. after
a git pull, R prints a warning at startup, and rig proj status tells
you what changed. If you start R with rig run in the project directory,
then it automatically runs rig proj sync as needed. If you use Positron,
VS Code, RStudio or another IDE, then R will warn at startup if the virtual
environment is out of date and suggest that you run rig proj sync and
restart R.
Managing dependencies#
R package dependencies are specified in the rproj.toml manifest under three
types of tables. The [dependencies] table includes packages that are
always installed. These are considered as R packages required for
the project. Packages in [dependency-groups.<name>] are groups of
development dependencies and they are only installed if requested, except
for the dev group which is installed by default. For example, the packages
that are needed for building the pkgdown website can go into a
[dependency-groups.website] table. dependency-groups is similar to
Config/Needs/<name> in DESCRIPTION files of R packages.
Dependencies of optional features go into [optional-dependencies.<name>]
tables and are only installed if requested. For example, if your package
has functions for visualization, but these are not required for the core
functionality, you may put them in an [optional-dependencies.viz] table
and other packages using yours may opt in to the viz feature.
(The infrastructure for depending on optional features in packages is not
built yet, and package repositories need to support it as well.)
You can edit the rproj.toml manifest manually to add dependencies or
use rig proj commands to do it. rig proj add adds packages to
rproj.toml, then updates rproj.lock and installs the new packages into
the project library:
❯ rig proj add dplyr cli❯ cat rproj.toml
[project]
name = "x"
version = "0.1.0"
type = "project"
[dependencies]
R = ">= 4.6"
cli = "*"
dplyr = "*"Without a version requirement rig adds a package as "*", any version. The
exact version is recorded in rproj.lock, so you only need a version
requirement if the project really needs a certain version. Version
requirements use the same syntax as for rig pkg install, e.g.
'cli@>= 3.6' or 'dplyr@^1.1.0'. rig proj remove removes packages.
Use --dev to add a development dependency, e.g. testthat. These go into
the [dependency-groups.dev] table.
Packages from GitHub, GitLab, git repositories, URLs and local paths use the
same syntax as rig pkg install, see the second post. Bioconductor or
other packages coming from the configured repositories work the same way as
CRAN packages. To pin a package to a certain repository, e.g. an R universe,
use --with-repos:
❯ rig proj add --with-repos rlib=https://r-lib.r-universe.dev cliThis adds the repository to rproj.toml and pins cli to it:
[dependencies]
cli = { version = "*", repository = "rlib" }
[[repository]]
name = "rlib"
url = "https://r-lib.r-universe.dev"If rig cannot resolve the new dependencies, e.g. because of a typo in a
package name, it restores rproj.toml.
Running a project#
rig run starts R in the project’s environment, from anywhere inside the
project. It locks and syncs the project first, if needed, so day to day you
mostly need rig proj add, rig proj remove and rig run.
rig run script.R runs a script in the project’s environment. Arguments
after the script name go to the script, and commandArgs(TRUE) picks them
up. Use --no-project to run the script without the project.
A project can give names to its scripts, in [[bin]] tables of
rproj.toml:
[[bin]]
name = "report"
path = "scripts/report.R"
description = "Build the report"Then rig run report --format pdf runs scripts/report.R with the
--format pdf arguments, and rig run --list lists all named scripts of
the project.
You can also run the .rvenv/bin/R and .rvenv/bin/Rscript wrappers
directly, or start R from your IDE in the project directory, see the
virtual environment above.
Package development with devtools#
To develop an R package as a rig project, use type = "package" in
rproj.toml, or run rig proj import on an existing package.
rig proj sync then installs the package itself into the project library,
and creates DESCRIPTION from rproj.toml (if missing), so R CMD build,
R CMD check and other tools keep working.
Development tools like devtools, usethis and roxygen2 are not dependencies
of the package, so they should not be in rproj.toml. rig has a special
global package library for them, called tools, and adds it to the library
path of every project. Install your tools there once:
❯ rig pkg install -l tools devtools usethis roxygen2and then they are available in all projects:
❯ rig run
> devtools::load_all()
> devtools::document()
> devtools::test()Packages that the package’s tests and documentation need, e.g. testthat or
knitr, are different. Add these with rig proj add --dev, so they are
locked together with the other dependencies.
Inline scripts#
Sometimes a project is just a single R script. For these rig supports inline
dependencies: the script declares the R version and the packages it needs in
a special comment block with a # /// script header and a # /// footer:
# /// script
# [dependencies]
# R = ">= 4.4"
# cli = "*"
# dplyr = ">= 1.1"
# ///
library(dplyr)
cli::cli_text("Hello from {.pkg cli}!")Without the leading # characters, the block is the same TOML as in
rproj.toml.
rig run script.R installs the right R version if needed, installs the
packages into a separate environment in rig’s cache, and then runs the
script. Your own package libraries do not change. The next run reuses the
environment, so it starts right away. You can share a single .R file, and
it runs the same way on every machine that has rig.
You can write the block by hand, or let rig manage it:
❯ rig proj init --script script.R
❯ rig proj add --script script.R cli dplyr
❯ rig proj remove --script script.R dplyrTo pin the exact R and package versions, lock the script:
❯ rig proj lock --script script.RThis writes script.R.lock next to the script. Keep the two files together,
and rig run will use the versions from the lock file.
On macOS and Linux, start the script with #!/usr/bin/env -S rig run and
make it executable, to run it like any other command. On Windows,
rig system script-assoc lets you run .R files from cmd and PowerShell.
See the scripts guide for more.
Caches#
rig caches the files it downloads: repository metadata, package files,
packages it built from source, git repositories of git and GitHub
dependencies, and the environments of inline scripts. rig pkg,
rig proj and rig run share the same cache, and so do all projects, so
rig downloads or builds a package only once. rig refreshes the repository
metadata once a day by default.
rig cache info shows how much space the cache takes, by category, and
rig cache clean deletes it, or only one category of it, with
--category:
❯ rig cache info
❯ rig cache clean --category scriptsUse --no-cache to skip the cache for a single command, and
rig system dirs --cache to see where the cache is.
Your feedback is welcome!#
We would love to hear your thoughts, suggestions, and any issues you encounter while using rig. Please use the issue tracker for bug reports and feature requests, and the discussion forum or Posit community for general questions and discussions.

