Econometrics and Free Software by Bruno Rodrigues.
RSS feed for blog post updates.
Follow me on Mastodon, twitter, or check out my Github.
Check out my package that adds logging to R functions, {chronicler}.
Or read my free ebooks, to learn some R and build reproducible analytical pipelines..
You can also watch my youtube channel or find the slides to the talks I've given here.
Buy me a coffee, my kids don't let me sleep.

Reproducible data science with Nix, part 9 -- rix is looking for testers!

R Nix

After 5 months of work, Philipp Baumann and myself are happy to announce that our package, {rix} is getting quite close to being in a state we consider “done” (well, at least, for a first release). We plan on submit it first to rOpenSci for review, and later to CRAN. But in the meantime, if you could test the package, we’d be grateful! We are especially interested to see if you find the documentation clear, and if you are able to run the features that require an installation of Nix, the nix_build() and with_nix() functions. And I would truly recommend you read this blog post to the end, because I guarantee you’ll have your mind blown! If that’s not the case, send an insult my way on social media.

What is rix?

{rix} is an R package that leverages Nix, a powerful package manager focusing on reproducible builds. With Nix, it is possible to create project-specific environments that contain a project-specific version of R and R packages (as well as other tools or languages, if needed). You can use {rix} and Nix to replace renv and Docker with one single tool. Nix is an incredibly useful piece of software for ensuring reproducibility of projects, in research or otherwise, or for running web applications like Shiny apps or plumber APIs in a controlled environment. The advantage of using Nix over Docker is that the environments that you define using Nix are not isolated from the rest of your machine: you can still access files and other tools installed on your computer.

For example, here is how you could use {rix} to generate a file called default.nix, which can then be used by Nix to actually build that environment for you:

library(rix)

path_default_nix <- tempdir()

rix(r_ver = "latest",
    r_pkgs = c("dplyr", "ggplot2"),
    system_pkgs = NULL,
    git_pkgs = NULL,
    ide = "code",
    shell_hook = NULL,
    project_path = path_default_nix,
    overwrite = TRUE,
    print = TRUE)
## # This file was generated by the {rix} R package v0.5.1.9000 on 2024-02-02
## # with following call:
## # >rix(r_ver = "5ad9903c16126a7d949101687af0aa589b1d7d3d",
## #  > r_pkgs = c("dplyr",
## #  > "ggplot2"),
## #  > system_pkgs = NULL,
## #  > git_pkgs = NULL,
## #  > ide = "code",
## #  > project_path = path_default_nix,
## #  > overwrite = TRUE,
## #  > print = TRUE,
## #  > shell_hook = NULL)
## # It uses nixpkgs' revision 5ad9903c16126a7d949101687af0aa589b1d7d3d for reproducibility purposes
## # which will install R version latest
## # Report any issues to https://github.com/b-rodrigues/rix
## let
##  pkgs = import (fetchTarball "https://github.com/NixOS/nixpkgs/archive/5ad9903c16126a7d949101687af0aa589b1d7d3d.tar.gz") {};
##  rpkgs = builtins.attrValues {
##   inherit (pkgs.rPackages) dplyr ggplot2 languageserver;
## };
##    system_packages = builtins.attrValues {
##   inherit (pkgs) R glibcLocales nix ;
## };
##   in
##   pkgs.mkShell {
##     LOCALE_ARCHIVE = if pkgs.system == "x86_64-linux" then  "${pkgs.glibcLocales}/lib/locale/locale-archive" else "";
##     LANG = "en_US.UTF-8";
##     LC_ALL = "en_US.UTF-8";
##     LC_TIME = "en_US.UTF-8";
##     LC_MONETARY = "en_US.UTF-8";
##     LC_PAPER = "en_US.UTF-8";
##     LC_MEASUREMENT = "en_US.UTF-8";
## 
##     buildInputs = [  rpkgs  system_packages  ];
##       
##   }

You don’t need to have Nix installed to use {rix} and generate this expression! This is especially useful if you want to generate an expression that should then be used in a CI/CD environment for example.

But if you do have Nix installed, then you can use two great functions that Philipp implemented, which we are really excited to tell you about!

nix_build() and with_nix()

When you have a default.nix file that was generated by rix::rix(), and if you have Nix installed on your system, you can build the corresponding environment using the command line tool nix-build. But you can also build that environment straight from an R session, by using rix::nix_build()!

But the reason nix_build() is really useful, is because it gets called by with_nix(). with_nix() is a very interesting function, because it allows you to evaluate a single function within a so-called subshell. That subshell can have a whole other version of R and R packages than your main session, and you can use it to execute an arbitrary function (or a whole, complex expression), and then get the result back into your main session. You could use older versions of packages to get a result that might not be possible to get in a current version. Consider the following example: on a recent version of {stringr}, stringr::str_subset(c("", "a"), "") results in an error, but older versions would return "a". Returning an error is actually what this should do, but hey, if you have code that relies on that old behaviour you can now execute that old code within a subshell that contains that older version of {stringr}. Start by creating a folder to contain everything needed for your subshell:

path_env_stringr <- file.path(".", "_env_stringr_1.4.1")

Then, it is advised to use rix::rix_init() to generate an .Rprofile for that subshell, which sets a number of environment variables. This way, when the R session in that subshell starts, we don’t have any interference between that subshell and the main R session, as the R packages that must be available to the subshell are only taken from the Nix store. The Nix store is where software installed by Nix is… stored, and we don’t want R to be confused and go look for R packages in the user’s library, which could happen without this specific .Rprofile file:

rix_init(
  project_path = path_env_stringr,
  rprofile_action = "overwrite",
  message_type = "simple"
)
## 
## ### Bootstrapping isolated, project-specific, and runtime-pure R setup via Nix ###
## 
## ==> Created isolated nix-R project folder:
##  /home/cbrunos/six_to/dev_env/b-rodrigues.github.com/content/blog/_env_stringr_1.4.1 
## ==> R session running via Nix (nixpkgs)
## * R session not running from RStudio
## ==> Added `.Rprofile` file and code lines for new R sessions launched from:
## /home/cbrunos/six_to/dev_env/b-rodrigues.github.com/content/blog/_env_stringr_1.4.1
## 
## * Added the location of the Nix store to `PATH` environmental variable for new R sessions on host/docker RStudio:
## /nix/var/nix/profiles/default/bin

We now generate the default.nix file for that subshell:

rix(
  r_ver = "latest",
  r_pkgs = "stringr@1.4.1",
  overwrite = TRUE,
  project_path = path_env_stringr
)

Notice how we use the latest version of R (we could have used any other), but {stringr} on version 1.4.1. Finally, we use with_nix() to evaluate stringr::str_subset(c("", "a"), "") inside that subshell:

out_nix_stringr <- with_nix(
  expr = function() stringr::str_subset(c("", "a"), ""),
  program = "R",
  exec_mode = "non-blocking",
  project_path = path_env_stringr,
  message_type = "simple"
)
## * R session not running from RStudio
## ### Prepare to exchange arguments and globals for `expr` between the host and Nix R sessions ###
## * checking code in `expr` for potential problems:
##  `codetools::checkUsage(fun = expr)`
## 
## * checking code in `expr` for potential problems:
## 
## * checking code in `globals_exprs` for potential problems:
## 
## ==> Running deparsed expression via `nix-shell` in non-blocking mode:
## 
## 
## ==> Process ID (PID) is 19688.
## ==> Receiving stdout and stderr streams...
## 
## ==> `expr` succeeded!
## ### Finished code evaluation in `nix-shell` ###
## 
## * Evaluating `expr` in `nix-shell` returns:
## [1] "a"

Finally, we can check if the result is really "a" or not:

identical("a", out_nix_stringr)
## [1] TRUE

with_nix() should work whether you installed your main R session using Nix, or not, but we’re not sure this is true for Windows (or rather, WSL2): we don’t have a Windows license to test this on Windows, so if you’re on Windows and use WSL2 and want to test this, we would be very happy to hear from you!

If you’re interested into using project-specific, and reproducible development environments, give {rix} and Nix a try! Learn more about {rix} on its Github repository here or website. We wrote many vignettes that are conveniently numbered, so don’t hesitate to get started!

Hope you enjoyed! If you found this blog post useful, you might want to follow me on Mastodon or twitter for blog post updates and buy me an espresso or paypal.me, or buy my ebooks. You can also watch my videos on youtube. So much content for you to consoom!

Buy me an EspressoBuy me an Espresso