Reproducible data science with Nix, part 9 -- rix is looking for testers!
R NixAfter 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!