f - Converting renv projects to Nix projects

Introduction

{renv} is very likely the most popular package to set up per project libraries of R packages. {renv} generates and manages so-called renv.lock files at the level of a project, and using these files it is thus possible to have different versions of the same packages on the same system, without any interference. However, {renv} doesn’t snapshot R itself, so different projects with different libraries of packages will end up using the same version of R, which could lead to issues, especially if you’re trying to restore an old project which relied on an old version of R. Also, in some cases, it might be impossible to restore a project due to incompatibilities at the level of the system-level dependencies, as these are not managed by {renv}.

As explained extensively in this documentation already, Nix handles all the different pieces of the reproducibility puzzle: versions of R packages, versions of R, and versions of system-level dependencies are all handled by Nix. In other words, it’s possible to have per project complete development environments that are completely reproducible.

While Nix has been around for 20 years, it was never widely adopted by R users, who instead have already invested a lot of effort into {renv} and Docker. The goal of {rix} is to ease adoption of Nix for R users for new projects, but there is also a need to convert historical {renv} projects into Nix.

This is where renv2nix() can help.

Converting an historical renv project

There are two ways that you can convert an historical project: the recommended way is to copy the historical renv.lock file into a new, empty folder. Do not copy any of the other generated {renv} files nor folders. From there, call renv2nix() like so:

renv2nix(
  renv_lock_path = "path/to/rix_project/renv.lock",
  project_path = "path/to/rix_project"
)

This will generate a default.nix and .Rprofile files for your Nix project.

The other way to achieve this is to point the renv_lock_path argument to the historical renv.lock file without copying it to a new folder:

renv2nix(
  renv_lock_path = "path/to/original/renv_project/renv.lock",
  project_path = "path/to/rix_project"
)

But this is not the recommended approach. Instead keep the original renv.lock file and the generated default.nix files together (ideally with the call to renv2nix() in an R script) and commit everything into version control.

Starting a new project

If you start a new project, you don’t need to use renv2nix(), as you could directly use rix() to generate a default.nix file. However, you could start with generating an renv.lock file using renv::snapshot() and then call renv2nix(), to generate the appropriate default.nix file. Because renv::snapshot() does not call renv::init() this will not generate any other files that could interfere with {rix}. Doing this could be useful if you have the habit of starting writing code and then would like to generate a default.nix file later. We don’t recommend working like this and instead urge you to start from an environment and work from the very beginning from that environment.

Caveats

Package versions are not exactly the same between the renv.lock and default.nix files

renv2nix() has two methods, "fast" and "accurate". "fast" simply lists the version of R and R packages, and generates a default.nix expression without trying to match package versions exactly. We believe that this is acceptable for most use cases. The planned "accurate" method will instead try to do its best to match exact package versions. However, due to how differently Nix and {renv} handle snapshotting, it will not be possible to match package versions exactly even with the planned "accurate" method. If you absolutely need a very specific version of a package, there are other ways to achieve this, by providing for example the required version to the r_pkgs argument of rix(), like this: rix(r_pkgs = "[email protected]"). This however tries to build the pacage from source, which can fail. If this is the case, don’t hesitate to open an issue. Better handling of individual package versions is planned for a future release.

Don’t use the same folder for your Nix and {renv} projects

As stated previously, don’t convert an renv.lock file into a default.nix file from a folder that contains the renv.lock file and the {renv} generated .Rprofile file and renv/ folder. Instead, work from a new empty folder in which you copy the renv.lock file.

Mind the R version

Many R users do not update R very often, so when they generate an renv.lock file, the renv.lock will list an old version of R, but quite recent packages. The way {rix} generates default.nix files is by looking at the version of R and then install packages that were current at that time. So if the renv.lock file lists an old version of R, the packages that will be included in the default.nix file will also be old. You could even end up in a situation where a package is not available because it only recently got released on CRAN. To avoid problems, use the override_r_ver argument of renv2nix() to provide a more recent version of R, that matches roughly when the packages were released.