EvoX Genesis
EvoX Genesis는 고수준 목표를 재귀적 소프트웨어 세계로 전환해, 유한 에이전트가 개발하고 승인된 결과만 유지됩니다
An extremely fast R package manager, written in Rust. The R equivalent of uv for Python.
저장소 README에서 가져옴
README 전체를 불러오는 중입니다.
README 전문 보기 ↗A fast R package and project manager, written in Rust.
uvr brings uv-style project management to R: a uvr.toml manifest, a reproducible uvr.lock lockfile, and a per-project isolated library. Packages install from pre-built P3M binaries by default — no compilation, no waiting — with automatic fallback to CRAN source. R versions are managed per-project with no sudo required.
Linux / MacOS
curl -fsSL https://raw.githubusercontent.com/nbafrank/uvr/main/install.sh | sh
Windows
irm https://raw.githubusercontent.com/nbafrank/uvr/main/install.ps1 | iex
Here's a following demo of uvr:
$ uvr init my-analysis
$ uvr add ggplot2 dplyr tidymodels
$ uvr sync # installs from lockfile, idempotent
$ uvr run analysis.R
(Checksum-verified install to ~/.local/bin; Windows and other options under Installation.)
Prefer working from the R console? The uvr R package wraps the CLI for use from R/RStudio/Positron — no terminal needed:
pak::pak("nbafrank/uvr-r")
library(uvr)
init() # uvr init
add("ggplot2") # uvr add ggplot2
sync() # uvr sync
run("analysis.R") # uvr run analysis.R
R has several package management tools — renv, pak, rv, rig — each solving a different slice of the problem. After 10+ years of R development, the workflow I kept wanting was the one uv brought to Python: a single tool that handles the full lifecycle, from installing R itself to adding packages to reproducible installs in CI, with no configuration sprawl.
Here is how existing tools compare and where the gaps are:
pak::lockfile_create() / pak::lockfile_install(), aimed at CI), but no R version management, and it is an installer rather than a project workflow — in practice paired with renv, not a replacement for it.rv run, rv sysdeps, and rv sync --locked for CI. It selects among the R versions already installed on the machine — including ones rig put there — but does not install R itself, which is the gap uvr closes.uvr is the combination of all of the above in one tool, with a single config file (uvr.toml) and a single lockfile (uvr.lock). The design goals are:
uvr.toml declares both the R version and package dependencies.uvr.lock is the source of truth. uvr sync is always reproducible and idempotent.uvr r install, uvr r use, uvr r pin work the same way uv python does, because needing a separate tool for this is friction.uvr sync --frozen is a first-class command, not an afterthought.If you are happy with renv + rig, that is a perfectly good setup. uvr is for people who want the uv experience in R.
| uvr | renv | pak | rv | rig | pixi | |
|---|---|---|---|---|---|---|
| Declarative manifest | Y | Y† | Y† | Y | - | Y |
| Lockfile | Y | Y | Y | Y | - | Y |
| R version selection / pinning | Y | - | - | Y | Y | Y |
| Installs R itself | Y | - | - | - | Y | Y |
| Run scripts in isolated env | Y | Y | - | Y | - | Y |
| CRAN packages | Y | Y | Y | Y | - | Y* |
| Bioconductor packages | Y | Y | Y | Y | - | Y* |
| GitHub packages | Y | Y | Y | Y | - | - |
| Pre-built binaries (P3M) | Y | - | Y | Y | - | - |
| System dep detection (Linux) | Y | - | Y | Y‡ | - | Y |
| CI mode (fail on stale lock) | Y | Y | - | Y | - | Y |
| No admin rights required | Y | Y | Y | Y | -** | Y |
| Standalone CLI (no R required) | Y | - | - | Y | Y | Y |
| Windows support | Y | Y | Y | Y | Y | Y |
* pixi installs R packages from conda-forge, not CRAN/Bioconductor directly.
** Per rig's own FAQ, rig cannot install R without admin permissions.
† Via DESCRIPTION-based workflow, not a dedicated manifest format.
‡ Per rv sysdeps' own help, coverage is currently Ubuntu/Debian.
Install wall time (empty library, index caches warm). All tools use P3M as CRAN mirror. Median of 5 runs on Apple Silicon (arm64), R 4.6.0, uvr 0.4.6. pak was not installed on the bench machine for this run; container numbers including pak are on the website.
| Scenario | Packages | uvr sync | renv | install.packages |
|---|---|---|---|---|
| jsonlite | 1 | 0.52s | 0.56s | 2.95s |
| ggplot2 | 17 | 0.51s | 0.61s | 5.1s |
| tidyverse | 100 | 0.57s | 0.81s | 14.92s |
uvr pre-resolves dependencies into a lockfile (
uvr lock); onlyuvr sync(install) is timed. The other tools resolve dependencies inline. renv uses its default global cache (symlinks).Reproduce on your own machine:
bash benchmarks/bench.sh. Reproduce in a clean container:bash benchmarks/run-in-docker.shbuildsbenchmarks/Dockerfileand runs the bench inside it. The Dockerfile pins R version, debian base, Rust toolchain, and the CRAN-mirror PPM snapshot — so numbers from a CI run today are directly comparable to a CI run a month from now and to a local docker-build by anyone who wants to verify the published numbers (per #40). The same image runs on every tag push via.github/workflows/benchmark.yml; the workflow uploadsbench-results.jsonas an artifact and surfaces the meta block in the GH Actions step summary.
uvr.lock is the source of truth; uvr sync is always idempotent.uvr/library/, never touching system Ruvr r install 4.4.2, uvr r use >=4.3, uvr r pin 4.4.2uvr add DESeq2 --bioc, uvr add user/repo@main# /// script header and uvr run script.R anywhere, no project neededuvr sync --frozen fails fast if the lockfile is stale; respects NO_COLORcurl -fsSL https://raw.githubusercontent.com/nbafrank/uvr/main/install.sh | sh
This auto-detects your platform, downloads the binary, verifies the SHA256 checksum, and installs to ~/.local/bin. Override the install directory with UVR_INSTALL_DIR:
curl -fsSL https://raw.githubusercontent.com/nbafrank/uvr/main/install.sh | UVR_INSTALL_DIR=/usr/local/bin sh
You can quick install on Windows as well with the following Powershell command:
irm https://raw.githubusercontent.com/nbafrank/uvr/main/install.ps1 | iex
Download the latest release for your platform from GitHub Releases:
# macOS (Apple Silicon)
curl -fsSL https://github.com/nbafrank/uvr/releases/latest/download/uvr-aarch64-apple-darwin.tar.gz | tar xz
sudo mv uvr /usr/local/bin/
# macOS (Intel)
curl -fsSL https://github.com/nbafrank/uvr/releases/latest/download/uvr-x86_64-apple-darwin.tar.gz | tar xz
sudo mv uvr /usr/local/bin/
# Linux (x86-64)
curl -fsSL https://github.com/nbafrank/uvr/releases/latest/download/uvr-x86_64-unknown-linux-gnu.tar.gz | tar xz
sudo mv uvr /usr/local/bin/
# Linux (ARM64)
curl -fsSL https://github.com/nbafrank/uvr/releases/latest/download/uvr-aarch64-unknown-linux-gnu.tar.gz | tar xz
sudo mv uvr /usr/local/bin/
On Windows, download uvr-x86_64-pc-windows-msvc.zip from the releases page and add uvr.exe to your PATH.
The companion R package can install the binary for you:
# Install the R package from GitHub (uvr-r is not on CRAN yet)
pak::pak("nbafrank/uvr-r")
# or: remotes::install_github("nbafrank/uvr-r")
# Download and install the uvr binary
uvr::install_uvr()
# Pre-built binary
yay -S uvr-bin
# Or build from source
yay -S uvr
Packages maintained by @novica. See uvr and uvr-bin on the AUR.
cargo install --git https://github.com/nbafrank/uvr
# Create a new project
mkdir my-project && cd my-project
uvr init --r-version ">=4.3.0"
# Add packages (CRAN, Bioconductor, GitHub)
uvr add ggplot2 dplyr
uvr add DESeq2 --bioc
uvr add tidymodels@>=1.0.0
uvr add user/repo@main
uvr add 'user/monorepo@main#subdirectory=packages/nestedPkg'
uvr add user/monorepo/packages/nestedPkg@main # same as the line above
uvr add git::https://git.example.com/team/[email protected] # any git host
uvr add https://example.org/builds/mypkg_1.2.0.tar.gz
uvr add ../mypkg # a package directory next to the project
# Install everything from the lockfile
uvr sync
# Run a script in the isolated environment
uvr run analysis.R -- --input data.csv
# See what you have
uvr tree
uvr add accepts a GitHub package directory in three forms:
owner/repo[@ref]#subdirectory=subdir, owner/repo/subdir[@ref] and
owner/repo:subdir[@ref]. The ref always goes last, so owner/repo@feat/x is
the branch feat/x, not a directory. GitHub package directories also propagate
through supported DESCRIPTION Remotes: entries, including
owner/repo/subdir@ref and owner/repo:subdir@ref. This traversal follows the source chain: only a
package already selected from a manifest Git or path source can introduce another
remote source; ordinary registry packages cannot inject remote URLs. Bound
aliases and subdirectory targets fail rather than falling back to a registry
or to the repository root.
| Command | Description |
|---|---|
uvr init [name] | Create uvr.toml and .uvr/library/ in the current directory |
uvr init --ide=positron | Also write .vscode/settings.json for Positron |
uvr init --no-ide | Skip IDE config even when an IDE is detected |
uvr init --bare | Create uvr.toml + .uvr/library/ + .gitignore (use via uvr run) |
uvr add <pkg...> | Add packages, update manifest + lockfile, install |
uvr remove <pkg...> | Remove packages from manifest and re-lock |
uvr sync | Install all packages from the lockfile |
uvr sync -v | Show the resolved install plan first — each package's source and whether it installs from binary or source |
uvr sync --frozen | Like sync, but fail if the lockfile is stale (CI mode) |
uvr sync --no-binary | Build everything from source, ignoring pre-built binaries |
uvr sync --no-companion | Skip the uvr companion R package install |
uvr sync --unattended | CI mode: skip IDE config, companion, and working-tree writes |
uvr sysdeps | Report missing system dependencies without installing anything |
uvr sysdeps --all | List every system dependency, installed or not |
uvr sync --prune-all | Also remove packages that are not in uvr.lock and that uvr did not install (by default, sync keeps packages that install.packages() or other tools put into the project library) |
uvr update [pkg...] | Upgrade packages to latest allowed versions |
uvr update --dry-run | Show what would change without installing |
uvr lock | Re-resolve all deps and update without installing |
By default uvr init / uvr sync write a .Rprofile block so any R session
started from the project root links .uvr/library/. IDE-specific config is
opt-in: uvr detects Positron (POSITRON=1) from its integrated terminal and
writes .vscode/settings.json. Override detection with --ide=positron or
--no-ide. --ide is the extension point for other editors, which can be
added once there is config worth writing.
For CI/automation, --unattended (or UVR_UNATTENDED=1) disables IDE config,
the companion R package, and every working-tree write (.Rprofile,
.gitignore, activation shims) in one switch — so a checked-out repository
stays byte-identical and the library is reached through uvr run or
R_LIBS_USER. --no-companion (UVR_NO_COMPANION=1) skips just the
companion. uvr init --bare is the persistent, interactive form of the same
minimal project: uvr.toml, .uvr/library/, and a protective .gitignore
only — reach it through uvr run.
A script can declare its own dependencies in a header comment and run
anywhere — no project, no uvr.toml, no lockfile:
# /// script
# dependencies = [
# "jsonlite",
# "praise",
# ]
# ///
cat(praise::praise(), "\n")
$ cd /anywhere && uvr run analysis.R
> Installing 2 package(s): 2 binary
v Installed 2 package(s) in 1.75s
You are epic!
Each entry takes the same specs as uvr add, so a shared script can pin
what it needs:
# /// script
# dependencies = [
# "ggplot2>=3.4", # version constraint (or ggplot2@>=3.4)
# "DESeq2 (bioc)", # Bioconductor — the header's --bioc
# "rladies/[email protected]", # GitHub, optional @ref
# "forgejo::codeberg.org/owner/pkg", # Forgejo
# "gitlab::gitlab.com/group/pkg@main", # GitLab
# ]
# ///
You can keep the header up to date from the command line, as with
uv add --script:
$ uvr add jsonlite 'ggplot2>=3.4' --script analysis.R
$ uvr add DESeq2 --bioc --script analysis.R
$ uvr remove ggplot2 --script analysis.R
uvr add --script creates the header if the file has none (after the shebang
line, if there is one). It replaces the spec of a package that is already
listed, adds new entries in the spellings shown above, and keeps a sorted list
sorted. Only the dependencies lines change: other keys, comments, line
endings and the rest of the file stay as they are. uvr remove --script
deletes the header when nothing is left in it. Neither command reads or writes
uvr.toml or uvr.lock, and neither installs anything: the next uvr run
builds the environment. --bioc applies as usual; the project-only flags
(--dev, --source, --no-lock, --no-install and the install options)
are refused.
The dependencies install into a cached environment keyed by the R version and the full specs, so the second run of that script — or any other script wanting the same packages — starts immediately, while two headers that differ only by a version or ref get separate environments. Nothing is written next to the script.
The header is the R analogue of Python's PEP 723
inline script metadata, which uv run uses. It must start at column zero and
may follow a shebang or banner comment. A malformed or duplicated header, or
an entry uvr add would refuse, is an error naming the file and the problem,
never silently ignored.
Scripts run isolated from any project you happen to be standing in: the
project library, its .r-version pin, and its .Rprofile are all bypassed,
so a script behaves the same wherever it is invoked from.
A header can also say which R the script needs:
# /// script
# r = "~4.4"
# dependencies = ["jsonlite"]
# ///
r takes the same constraint grammar as r_version in uvr.toml. uvr runs
the script with the newest installed R that satisfies it. If none does, uvr
installs the newest matching R that is published for your platform (the same
download as uvr r install, with its progress on stderr) and continues. As in
uvr.toml, a bare version is a pin: r = "4.4" means any 4.4.x and
r = "4.4.3" means that release. Write >=4.4 (or ^4.4) to accept newer
minors. uvr run --r-version <constraint> overrides the header, and never
installs anything.
To stop uvr from downloading R, for example on a locked-down machine, set
UVR_R_DOWNLOADS=never (the default is auto). A script whose r no
installed R satisfies then fails with an error that names the constraint.
A script can also carry a shebang and run as a plain executable:
#!/usr/bin/env -S uvr run
# /// script
# dependencies = ["praise"]
# ///
cat(praise::praise(), "\n")
$ chmod +x hooray
$ ./hooray
You are wondrous!
The -S flag needs GNU coreutils 8.30+ on Linux; macOS and the BSDs have
supported it for years. Windows has no shebang mechanism, so the line is
inert there and the script runs as uvr run hooray.
Prefer working in a plain R console over prefixing everything with uvr run?
Activate the project and a bare R or Rscript uses it:
source .uvr/activate # bash, zsh, sh
source .uvr/activate.fish # fish
. .uvr/activate.ps1 # PowerShell
R # uses the project's R and .uvr/library/
deactivate # restore your shell
If another tool already owns the name deactivate — a Python virtual
environment, for example — uvr leaves that function alone and says so when you
activate. Use uvr_deactivate there; it always works, whichever shell you use,
and it restores only what uvr changed.
uvr init writes these files (uvr activate --write-shim recreates them).
They contain no paths: each one asks uvr to recompute the environment as
it is sourced, so changing the project's R version with uvr r use or
uvr r pin never leaves a stale activation behind.
To show the project name in your prompt while activated — off by default:
# uvr.toml
[activate]
prompt = true
or per-shell, which overrides the manifest either way:
export UVR_ACTIVATE_PROMPT=1 # or 0 to opt out of a project that opts in
.Renvironuvr run and a sourced activation both shadow ~/.Renviron, so a
~/.Renviron that sets R_LIBS_USER cannot silently replace the project
library. That also hides everything else the file holds — API tokens,
GITHUB_PAT, proxy settings, locale. To read it again:
export UVR_USER_ENVIRON=1
The site-wide Renviron.site stays shadowed either way. With the opt-out on,
a ~/.Renviron that sets R_LIBS_USER does override the project library —
that is the trade the switch makes. R CMD INSTALL has always read the user
file, so uvr add and uvr sync are unaffected by this setting.
Generate and install completions for your shell:
# Zsh
uvr completions zsh > ~/.zfunc/_uvr
# Bash
uvr completions bash > /etc/bash_completion.d/uvr
# Fish
uvr completions fish > ~/.config/fish/completions/uvr.fish
# PowerShell
uvr completions powershell > $HOME\Documents\PowerShell\Completions\uvr.ps1
uvr can install and manage multiple R versions without sudo or admin rights:
# Install R 4.4.2
uvr r install 4.4.2
# See what's available
uvr r list --all
# Set project constraint (writes to uvr.toml)
uvr r use ">=4.3.0"
# Pin exact version (writes .r-version file)
uvr r pin 4.4.2
R versions are installed to ~/.uvr/r-versions/ and managed independently of any system R installation. uvr downloads portable, relocatable R builds from the rstudio/r-builds project (cdn.posit.co/r): each is a self-contained archive that detects its own location at runtime — no system-wide install, no admin/sudo, and no post-install patching. This makes it ideal for corporate and university environments where users cannot install software system-wide.
# GitHub Actions example
- name: Install uvr
run: |
curl -fsSL https://github.com/nbafrank/uvr/releases/latest/download/uvr-x86_64-unknown-linux-gnu.tar.gz | tar xz
sudo mv uvr /usr/local/bin/
- name: Install R
run: uvr r install 4.4.2
- name: Install packages (frozen = fail if lockfile is stale)
run: uvr sync --frozen
- name: Run tests
run: uvr run tests/run_tests.R
A --frozen sync validates the lockfile before writing any project
scaffolding, so a stale lockfile fails without dirtying the checkout; add
--unattended to also skip the scaffolding writes on success.
my-project/
├── uvr.toml # manifest (commit this)
├── uvr.lock # lockfile (commit this)
├── .r-version # optional exact R pin (commit this)
└── .uvr/
└── library/ # isolated package library (.gitignore this)
uvr.toml[project]
name = "my-project"
r_version = ">=4.3.0"
[dependencies]
ggplot2 = ">=3.0.0"
dplyr = "*"
DESeq2 = { bioc = true }
myPkg = { git = "user/repo", rev = "main" }
nestedPkg = { git = "user/monorepo", rev = "main", subdirectory = "packages/nestedPkg" }
anyPkg = { git = "git::https://git.example.com/team/anyPkg.git", rev = "v1.0" }
tarPkg = { url = "https://example.org/builds/tarPkg_1.2.0.tar.gz" }
mypkg = { path = "../mypkg" }
[dev-dependencies]
testthat = "*"
r_version takes a requirement (">=4.3.0", "^4.5", "==4.5.3") or a bare
version, which pins R like .r-version does: "4.5.3" selects exactly R
4.5.3, and "4.5" selects the newest installed 4.5.x.
A url dependency is a source package tarball (.tar.gz or .tgz) at a fixed
URL, such as an internal build artifact or an archived release. uvr lock
downloads it, checks that it is an R source package (not a built binary), and
records its sha256 in uvr.lock. If the file at that URL changes later,
uvr sync stops with a checksum error; run uvr lock to accept the change.
After re-locking, sync reinstalls changed content even if Version: is unchanged.
Installed packages and cached builds are reused only when their URL and checksum
match the lock; older unmarked installs are rebuilt once. sync --frozen rejects
a changed checksum without updating the lock.
The entry name must match the tarball's DESCRIPTION Package: field. uvr sends
no credentials for these downloads.
A path is relative to uvr.toml (or absolute). uvr add needs explicit
path syntax (./x, ../x, /x), so a bare name stays a CRAN or GitHub spec.
uvr sync rebuilds a path package from its directory on every run, so edits
arrive without a version bump. The lockfile records only the path and the
DESCRIPTION version, so it installs only where that directory exists, and
uvr sync --frozen warns about it.
Generated or imported git entries may also carry exact = true, which preserves an explicit DESCRIPTION PackageName= alias and requires the fetched DESCRIPTION Package: field to match the manifest dependency name.
git:: names a package by the clone URL of its repository, so it works for any git host: Bitbucket, a self-hosted GitLab or Gitea, or a company git server. uvr runs the git program for these packages. uvr doctor shows whether git is installed.
uvr add git::https://git.example.com/team/[email protected] # a tag, a branch, or a full commit SHA
uvr add git::[email protected]:team/anyPkg.git # ssh; no ref = the default branch
https://, ssh:// and user@host:path URLs. It also accepts http:// and file:// URLs, with a warning: http has no transport security, and a file path works only on your machine. A URL must not contain credentials (see Private git repositories below).uvr lock records the commit (checksum = "git:<sha>"). uvr sync installs that commit from source, and never a binary package of the same name. The commit stays in the download cache, so uvr fetches it only once.#subdirectory=), and git:: entries in the Remotes: field of a package.A [[sources]] entry adds a CRAN-like repository (for example, a private Posit Package Manager or an internal mirror). uvr.toml names the repository. It never holds the secret: uvr reads the credential from the environment, keyed by the repository name.
[[sources]]
name = "internal-ppm"
url = "https://ppm.corp.example/cran/latest"
export UVR_REPO_TOKEN_INTERNAL_PPM=... # sent as "Authorization: Bearer ..."
# or HTTP basic auth:
export UVR_REPO_USER_INTERNAL_PPM=alice
export UVR_REPO_PASSWORD_INTERNAL_PPM=...
<NAME> is the source name with any :port suffix removed, in upper case, and with each character that is not a letter or digit changed to _ (internal-ppm → INTERNAL_PPM). For uvr add --source <url> and UVR_REPOS, the name is the URL host (ppm.corp.example:8443 → PPM_CORP_EXAMPLE).
If the token is set, uvr uses it and ignores the user and password. uvr removes spaces at the start and end of each value. An empty value counts as not set.
Pin the host. The variables are keyed by the repository name, and a project you clone chooses its own names and URLs. Set UVR_REPO_HOST_<NAME> to the repository's host, and uvr sends the variables only to URLs on that host:
export UVR_REPO_HOST_INTERNAL_PPM=ppm.corp.example # or ppm.corp.example:8443 to pin the port too
A uvr.toml that points internal-ppm at another host then gets nothing from these variables, and uvr warns. Without the pin, any project that uses the name internal-ppm gets the credential. ~/.netrc entries are keyed by host already.
uvr sends the credential with the index (PACKAGES.gz) request and with each package download, but only to URLs under the source url. When a redirect goes to a different host or port, uvr does not send it. uvr never shows it in output (-v included), and does not pass it to R CMD INSTALL.
If none of these variables is set, uvr uses the ~/.netrc entry for the repository host, with HTTP basic auth:
machine ppm.corp.example
login alice
password ...
NETRC if it is set. On Windows, uvr reads %USERPROFILE%\_netrc when there is no .netrc.machine is the host name only. It never includes a port, so an entry applies to all ports on that host.default entry, because it would send the same credential to every repository and git host.644 or 640), uvr shows a warning and does not use the file. The run continues. To fix this, run chmod 600 ~/.netrc.~/.netrc. See below.Private git repositories. For git dependencies, uvr uses the first of these variables that is set as the access token:
| Host | Variables, in order |
|---|---|
| GitHub | GITHUB_PAT, GITHUB_TOKEN |
| GitLab | UVR_GITLAB_TOKEN_<HOST>, then UVR_GITLAB_TOKEN for gitlab.com only |
| Forgejo | UVR_FORGEJO_TOKEN_<HOST>, then UVR_FORGEJO_TOKEN for codeberg.org only |
Any other host (git::) | UVR_GIT_TOKEN_<HOST> |
<HOST> is the host name without its port, in capitals, with each . written as _ and each - written as __: git.local:3000 → GIT_LOCAL, my-gitlab.example → MY__GITLAB_EXAMPLE. One variable name matches exactly one host, so a project that names a lookalike host (git-corp.com for git.corp.com) cannot get your token. A host name with other characters (an IPv6 address, _) has no variable; use ~/.netrc.UVR_GITLAB_TOKEN and UVR_FORGEJO_TOKEN go only to gitlab.com and codeberg.org. Every other host is named by the dependency, so a cloned project could otherwise collect the token by naming a host of its own. For a self-hosted instance, set its own <HOST> variable. uvr warns when one of these variables is set but not used for a host.password of the ~/.netrc entry for the host (for GitHub, machine github.com). The password must be an access token, not your account password.DESCRIPTION request and the tarball download (GitHub and GitLab: Authorization: Bearer, Forgejo: Authorization: token). uvr sends it only to that host: for GitHub, api.github.com and raw.githubusercontent.com. uvr never sends it to CRAN, P3M, a [[sources]] repository, or a different git host.401, or 404 from raw.githubusercontent.com), uvr shows a warning and does not use that entry again in the same run. uvr then continues without credentials, so public repositories still work. uvr never ignores a token from a variable: if the host refuses it, uvr stops with an error.git:: dependency, git servers take HTTP basic auth, so uvr sends the token as the password. The user name is UVR_GIT_USER_<HOST>, or x-token-auth if that is not set (Bitbucket Cloud access tokens need that name; GitLab accepts any name). From ~/.netrc, uvr uses the login and password of the entry. uvr gives the header to git in GIT_CONFIG_* environment variables, which need git 2.31 or later, and never on the command line. git sends it only to the https:// origin of the URL. There is no variable for all git:: hosts, because it would send one token to every host that a dependency names.[resolution]
strategy = "lowest-direct" # "highest" (default), "lowest", or "lowest-direct"
The strategy selects which end of each allowed version range uvr uses:
highest (the default) uses the newest release.lowest uses the oldest release that each constraint allows, for all
packages. Thus ggplot2 = ">=3.4.0" resolves to ggplot2 3.4.0, and you can
test that floor. A package that no constraint limits resolves to its first
CRAN release, which frequently does not build on a current R.lowest-direct uses the oldest release for the dependencies in uvr.toml,
and the newest release for the packages that they pull in. Use this mode to
check your own floors in CI.--resolution <strategy> on uvr lock, uvr add, and uvr update overrides
the setting for one run. uvr does not store the override. The next resolution
(after a change to uvr.toml, such as uvr add, or with uvr lock --upgrade)
uses the uvr.toml setting again. Thus, to keep a strategy, write it in
uvr.toml. uvr sync --frozen compares the lockfile with uvr.toml, so a
change of the setting there makes the lockfile stale.
CRAN's package index lists only current releases. For the older releases,
uvr gets the metadata from crandb (METACRAN),
keeps it in ~/.uvr/cache/cran-history/, and downloads the tarballs from the
CRAN archive. There are no P3M binaries for old releases, thus uvr sync
builds them from source. uvr does not use a release that needs a package that
is no longer on CRAN. Bioconductor packages and custom repositories resolve
from their index without change (a Bioconductor release has one version of
each package). Git dependencies stay at the commit that they name.
[resolution]
exclude-newer = "2024-01-01" # YYYY-MM-DD
exclude-newer resolves CRAN packages as they were on that date, so that you
can reproduce an analysis as it was then. uvr reads the CRAN index from the
Posit Package Manager snapshot for that day
(https://packagemanager.posit.co/cran/<DATE>), and records the date in
uvr.lock as resolved_as_of. uvr sync downloads the source tarballs and
the P3M binaries from the same snapshot. The first snapshot is 2017-10-10, and
a date in the future is an error.
--exclude-newer <DATE> on uvr lock sets the date without editing
uvr.toml. The date is kept in uvr.lock: later uvr add, uvr remove,
uvr lock -P and uvr update <pkg> runs resolve at the same date, until
uvr.toml names a date or uvr lock --upgrade resolves against today's
CRAN. To share the date with collaborators, write it in uvr.toml. When
uvr.toml names a date, uvr sync --frozen fails if uvr.lock was
resolved at another one.--resolution lowest, releases that are older than the snapshot's
release download from the CRAN archive, where their MD5 checksum applies.[[sources]] repositories, and git dependencies have
no dated snapshot. uvr shows one warning that names them, and resolves them
from their current state.UVR_REPOS has no effect on the date: lock time does not read it, and at
sync time a mirror supplies only a binary of the exact locked version.[override-dependencies]
rlang = "1.0.6" # use this version, whatever a package requires
[constraint-dependencies]
cli = "<3.6.6" # applies only if a dependency pulls in cli
An override is an exact version, in R's form ("1.6-5" is correct). It
replaces every requirement on that package, including the requirement in
[dependencies]. Use it when a package requires a version that you cannot
use. uvr lock -v shows the version that each override selects and each
requirement that it ignores:
DEBUG override rlang = "1.0.6" selects rlang 1.0.6
DEBUG override rlang = "1.0.6" ignores lifecycle 1.0.5's requirement rlang (>=1.1.0)
R also checks the versions of imports when it loads a package, so a package can fail to load with an overridden dependency.
A constraint is a version range that a package must also satisfy. It does not add the package to the project: a constraint on a package that nothing pulls in has no effect. If no version satisfies both the constraint and the requirements, the lock fails and shows the combined range.
Both tables work with all resolution strategies, and an override can select
an old CRAN release (uvr gets it as described above). They apply to CRAN,
Bioconductor, and custom-repository packages. A Bioconductor release has one
version of each package, thus an override there can only select that
version. An override on a git
dependency is an error: pin a git dependency with rev. A constraint on a git
dependency is checked like any other requirement.
For git:: dependencies, installed packages and cached builds must match the
locked clone URL and commit, even when the package version is unchanged. Older
unmarked installs are rebuilt once. sync --frozen rejects a changed commit
without updating the lock.
On Linux, uvr sync automatically checks for missing system libraries and
prints the install command for your distro's package manager (apt-get,
dnf, zypper, or apk):
! Missing system dependencies for 2 package(s):
textshaping requires: libharfbuzz-dev, libfribidi-dev
ragg requires: libfreetype6-dev, libpng-dev
Install with: sudo apt-get install -y libharfbuzz-dev libfribidi-dev libfreetype6-dev libpng-dev
To ask the question without syncing — writing a Dockerfile, adding a CI setup step, or just checking before you commit to an install:
uvr sysdeps # what is missing here; exits 1 when anything is
uvr sysdeps --all # everything the project needs, installed or not; exits 0
uvr sysdeps reads uvr.lock, so run uvr lock first. Use --all when
building an image: the image has nothing installed yet, so "what is missing
on this host" is the wrong question.
Pass --install-system-deps (or set UVR_INSTALL_SYSREQS=1) and uvr runs
the commands itself, showing each one and where it came from before
anything executes as root. Requirements are resolved from the
r-system-requirements
rules vendored into uvr, cross-checked against Posit's sysreqs API when
reachable.
Run uvr doctor to check your setup:
> uvr doctor
Platform
v OS / architecture macos/aarch64
v P3M binary packages available
R installations
v R 4.5.3 ~/.uvr/r-versions/4.5.3/bin/R - managed
v R 4.4.2 ~/.uvr/r-versions/4.4.2/bin/R - managed
-> active 4.5.3 ~/.uvr/r-versions/4.5.3/bin/R
Build tools
v cargo (Rust toolchain) found
v Xcode command line tools found
v Homebrew found
Project
v Manifest uvr.toml
v Lockfile 42 package(s), R 4.5.3
Cache
- 166 file(s), 204.6 MB
v No issues found
| Platform | Binary packages | Source install | R version management |
|---|---|---|---|
| macOS ARM64 (Apple Silicon) | P3M | Y | Y (R 4.1.0+) |
| macOS x86-64 | P3M | Y | Y (R 4.1.0+) |
| Linux x86-64 (glibc ≥ 2.34) | P3M (Ubuntu, Debian, RHEL, openSUSE) | Y | Y |
| Linux ARM64 (glibc ≥ 2.34) | P3M (Ubuntu, Debian, RHEL, openSUSE) | Y | Y |
| Linux (musl / Alpine) | source | Y | Y |
| Windows x86-64 | P3M | Y (with Rtools) | Y (R 4.1.0+, no admin required) |
P3M binary packages are sourced from Posit Package Manager. R itself is installed from the portable, relocatable builds published by rstudio/r-builds on Posit CDN — manylinux_2_34 tarballs on glibc Linux (requires glibc ≥ 2.34; excludes Ubuntu 20.04, RHEL 8, Debian 11), musllinux_1_2 on Alpine, ad-hoc-signed .tar.gz on macOS (R 4.1.0+), and .zip on Windows (R 4.1.0+). The portable Linux builds bundle their own libraries but expect ca-certificates and fontconfig (plus ttf-dejavu on Alpine) to be present on the host.
uvr is shaped by the people who use it and report back. Special thanks to:
And to everyone who has filed an issue, tested a fix, or suggested a direction — thank you; the last several releases were built from your reports.
uvr is free and MIT-licensed. If it saves you time, you can support its development on Ko-fi.
MIT — see LICENSE.
uvr.lockuvr lock --upgrade | Upgrade all packages to their latest allowed versions |
uvr lock --upgrade-package <pkg> | Upgrade only <pkg> in uvr.lock (repeatable, short -P), holding every other package at its locked version; installs nothing |
uvr lock --resolution lowest | Resolve to the oldest versions the constraints allow, to test declared floors (also on add and update; see [resolution]) |
uvr lock --exclude-newer 2024-01-01 | Resolve CRAN packages as they were on a date (see Resolve as of a date) |
uvr tree | Show the dependency tree |
uvr tree --depth 1 | Show only direct dependencies |
uvr run [script.R] | Run a script (or interactive R) with the project library active |
uvr run --with pkg | Run with extra packages available (not added to manifest) |
uvr run script.R | Run a standalone script from its inline # /// script dependency header — outside any project |
uvr add <pkg...> --script script.R | Add packages to a script's inline header, creating it if needed (installs nothing) |
uvr remove <pkg...> --script script.R | Remove packages from a script's inline header, dropping it once empty |
uvr activate | Print how to activate the project in your shell (source .uvr/activate) |
uvr r install <ver> | Download and install a specific R version to ~/.uvr/r-versions/ (override the location with --install-dir) |
uvr r install devel | Install a rolling channel — devel or next, rebuilt continuously and marked [unstable] (not reproducible; don't pin one) |
uvr r list | Show installed R versions |
uvr r list --all | Show all available R versions (fetched from the portable build index) |
uvr r use <ver> | Set R version constraint in uvr.toml |
uvr r pin <ver> | Write exact version to .r-version |
uvr r dir | Print the managed R install directory (~/.uvr/r-versions/, or UVR_R_INSTALL_DIR) |
uvr r find [constraint] | Print the path to the newest R that satisfies the constraint; with no constraint, the R the project uses. Exits non-zero if none matches |
uvr export | Export lockfile to renv.lock format |
uvr export -o renv.lock | Export to a file |
uvr import | Import packages from an renv.lock file |
uvr import --lock | Import and immediately resolve + install |
uvr upgrade | Update uvr itself to the latest GitHub release (alias: uvr self-update) |
uvr doctor | Diagnose environment issues (R, build tools, project status) |
uvr completions <shell> | Generate shell completions (bash, zsh, fish, powershell) |
uvr cache clean | Remove all cached package downloads |
uvr cache clean --package <name> | Remove cache entries for specific packages (repeatable, comma-separated) |
uvr cache clean --r-version <minor> | Remove extracted-package entries built for an R minor version (e.g. 4.5) |
uvr cache dir | Print the download cache directory (~/.uvr/cache/, or UVR_CACHE_DIR) |
uvr cache size | Print the total size of the download and extracted-package caches |
uvr cache prune | Remove only unused entries: leftovers of interrupted installs, and extracted packages for R minor versions that uvr cannot find on this machine (load an R module first if uvr cannot see that R). --dry-run lists them; --ci also removes the raw downloads and keeps the extracted packages |
A 401 or 403 response gives an error that names the repository and the variables or netrc entry to set.
Credentials written into the URL (https://user:pass@host/...) still work, and uvr hides them in its output. But they are also saved in uvr.lock, so use the variables. uvr add --source does not accept such a URL.
git:: host, git uses its own credential helpers. For ssh:// and user@host:path URLs, git uses your ssh keys and agent, and uvr sends nothing. uvr turns off the terminal prompts of git, so a private repository without credentials fails instead of waiting for input.