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.RR 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:
- renv — the de-facto standard for reproducibility. It snapshots an existing library into a lockfile, but it does not pin R versions ("renv tracks, but doesn't help with, the version of R used") and it works library-first: the lockfile records what your library already has rather than driving what gets installed. Install speed is a property of your mirror, not of renv — pointed at a binary repo like P3M it is fast (see the benchmarks below).
- pak — fast parallel installs and good system dependency detection. It does have lockfiles (
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 — the closest prior art: Rust-based, declarative, fast, with P3M binaries,
rv run,rv sysdeps, andrv sync --lockedfor CI. It selects among the R versions already installed on the machine — including onesrigput there — but does not install R itself, which is the gapuvrcloses. - rig — excellent R version manager. No package management or lockfile. Per its own FAQ it cannot install R without admin permissions.
- pixi — conda-based multi-language environment manager. Supports R via conda-forge, but packages come from conda-forge rather than CRAN/Bioconductor/P3M natively. Language-agnostic by design; not R-first.
- rix — Nix-based, with extreme reproducibility including system-level dependencies. Right tool if you need bit-for-bit reproducibility across machines. Requires Nix; a different philosophy than a fast pragmatic workflow.
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:
- One tool, one config — no juggling renv + rig + pak.
uvr.tomldeclares both the R version and package dependencies. - Lockfile-first —
uvr.lockis the source of truth.uvr syncis always reproducible and idempotent. - Fast by default — P3M pre-built binaries on macOS, Windows, and Linux; source fallback only when needed.
- R version management built in —
uvr r install,uvr r use,uvr r pinwork the same wayuv pythondoes, because needing a separate tool for this is friction. - CI-native —
uvr sync --frozenis 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.
- Fast — parallel downloads, native binary extraction, no R process overhead
- Reproducible —
uvr.lockis the source of truth;uvr syncis always idempotent - Project-isolated — every project gets its own
.uvr/library/, never touching system R - Full R version management —
uvr r install 4.4.2,uvr r use >=4.3,uvr r pin 4.4.2 - CRAN + Bioconductor + GitHub —
uvr add DESeq2 --bioc,uvr add user/repo@main - Standalone scripts — declare dependencies in a
# /// scriptheader anduvr run script.Ranywhere, no project needed - CI-ready —
uvr sync --frozenfails fast if the lockfile is stale; respectsNO_COLOR - Cross-platform — macOS, Linux, and Windows with pre-built binaries for all three
- Written in Rust — single static binary, no R or Python required to install
curl -fsSL https://raw.githubusercontent.com/nbafrank/uvr/main/install.sh | shThis 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 shYou can quick install on Windows as well with the following Powershell command:
irm https://raw.githubusercontent.com/nbafrank/uvr/main/install.ps1 | iexDownload 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 uvrPackages maintained by @novica. See uvr and uvr-bin on the AUR.
From source (requires Rust)
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 git::https://git.example.com/team/anyPkg.git@v1.0 # any git host
uvr add https://example.org/builds/mypkg_1.2.0.tar.gz
# 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 treeGitHub 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 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 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 uvr.lock without installing |
uvr lock --upgrade |
Upgrade all packages to their latest allowed versions |
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 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 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) |
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!The dependencies install into a cached environment keyed by the dependency set, so the second run of that script — or any other script wanting the same packages — starts immediately. 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,
may follow a shebang or banner comment, and takes plain package names today
(version constraints, Bioconductor and git sources are planned). A malformed
or duplicated header 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 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 shelluvr 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 = trueor per-shell, which overrides the manifest either way:
export UVR_ACTIVATE_PROMPT=1 # or 0 to opt out of a project that opts inGenerate 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.ps1uvr 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.2R 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.RA --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)
[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" }
[dev-dependencies]
testthat = "*"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.
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/anyPkg.git@v1.0 # a tag, a branch, or a full commit SHA
uvr add git::git@bitbucket.org:team/anyPkg.git # ssh; no ref = the default branch- uvr accepts
https://,ssh://anduser@host:pathURLs. It also acceptshttp://andfile://URLs, with a warning:httphas no transport security, and afilepath works only on your machine. A URL must not contain credentials (see Private git repositories below). uvr lockrecords the commit (checksum = "git:<sha>").uvr syncinstalls 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.- To pin a commit, give its full SHA. The host lists branches and tags, so uvr cannot find an abbreviated SHA.
- The host must let git fetch a commit by its SHA (git protocol v2). GitHub, GitLab, Bitbucket and Codeberg do.
- Not supported yet: a package in a subdirectory (
#subdirectory=), andgit::entries in theRemotes: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 sourcenamewith any:portsuffix removed, in upper case, and with each character that is not a letter or digit changed to_(internal-ppm→INTERNAL_PPM). Foruvr add --source <url>andUVR_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.
-
uvr sends the credential with the index (
PACKAGES.gz) request and with each package download, but only to URLs under the sourceurl. When a redirect goes to a different host or port, uvr does not send it. uvr never shows it in output (-vincluded), and does not pass it toR CMD INSTALL. -
If none of these variables is set, uvr uses the
~/.netrcentry for the repository host, with HTTP basic auth:machine ppm.corp.example login alice password ...- uvr reads the file in
NETRCif it is set. On Windows, uvr reads%USERPROFILE%\_netrcwhen there is no.netrc. machineis the host name only. It never includes a port, so an entry applies to all ports on that host.- uvr does not use a
defaultentry, because it would send the same credential to every repository and git host. - On Unix, if the file gives any access to users other than you (for example, mode
644or640), uvr shows a warning and does not use the file. The run continues. To fix this, runchmod 600 ~/.netrc. - Git hosts also use
~/.netrc. See Private git repositories below.
- uvr reads the file in
-
A
401or403response 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 inuvr.lock, so use the variables.uvr add --sourcedoes not accept such a URL.
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>, UVR_GITLAB_TOKEN |
| Forgejo | UVR_FORGEJO_TOKEN_<HOST>, UVR_FORGEJO_TOKEN |
Any other host (git::) |
UVR_GIT_TOKEN_<HOST> |
<HOST>is the host, changed as<NAME>is above (git.local:3000→GIT_LOCAL).- If none of these variables is set, uvr uses the
passwordof the~/.netrcentry for the host (for GitHub,machine github.com). The password must be an access token, not your account password. - uvr sends the token with the API requests, the
DESCRIPTIONrequest and the tarball download (GitHub and GitLab:Authorization: Bearer, Forgejo:Authorization: token). uvr sends it only to that host: for GitHub,api.github.comandraw.githubusercontent.com. uvr never sends it to CRAN, P3M, a[[sources]]repository, or a different git host. - If a host refuses a netrc password (
401, or404fromraw.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. - For a
git::dependency, git servers take HTTP basic auth, so uvr sends the token as the password. The user name isUVR_GIT_USER_<HOST>, orx-token-authif that is not set (Bitbucket Cloud access tokens need that name; GitLab accepts any name). From~/.netrc, uvr uses theloginandpasswordof the entry. uvr gives the header to git inGIT_CONFIG_*environment variables, which need git 2.31 or later, and never on the command line. git sends it only to thehttps://origin of the URL. There is no variable for allgit::hosts, because it would send one token to every host that a dependency names. - If uvr has no token for a
git::host, git uses its own credential helpers. Forssh://anduser@host:pathURLs, 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.
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
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:
- @B-Nilson — a steady stream of field reports and requests that became core behavior: cache-preserving R switches (#85), filtered cache cleaning (#92), and more.
- @bsirak — the trampoline and symlink integration RFC (#109).
- @gdevenyi — a systematic 46-issue audit of the entire codebase (#127–#172), with file-and-line precision, that drove the v0.4.1 and v0.4.2 fix batches (and a code contribution on top).
- @pat-s — the Alpine/musl system-requirements groundwork, and candid feedback that improved how this project is run.
- @hongyuanjia — suggested building on Posit's r-builds (#96), which became the foundation of the current R install backend.
- @zorbax — the precise diagnosis of the macOS GNU-tar install failure (#125).
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.
