![](../logo.png)
Building reproducible development environments with rix
building-reproducible-development-environments-with-rix.Rmd
library(rix)
Introduction and installation
The goal of {rix}
is to provide an easy way to generate
default.nix
files. These files are used by the Nix package
manager to build an environment according to the instructions defined in
it. Users can specify which version of R, R packages, other needed
software, IDE etc should be available within that environment. Writing
such files can be daunting for newcomers, and so {rix}
provides a function called rix()
, which takes care of the
bulk of the work
{rix}
does not require Nix to be installed to generate
default.nix
files, so you could generate the file on one
machine, and then build and use the environment on another machine that
has Nix installed on it (or on a CI/CD service like Github Actions for
example). If you wish to fully take advantage of Nix, I suggest you use
Determinate System’s
installer. Nix can be installed on Linux, macOS and Windows (but on
Windows WSL2 must be enabled).
On Linux, once Nix is instaled, all the software that will be
installed will be saved to the /nix
directory on the root
partition. Complete development environments built with Nix can take up
much space, so if the available space on your root parttion is limited,
I advise you to mount the /nix
folder on another partition
with more space (for example, a secondary hard drive). For this, edit
/etc/fstab
and add the following line at the end:
/home/path_to/nix /nix none bind 0 0
This will map /nix
to /home/path_to/nix
which can be on a larger partition. If you have enough space on your
root partition, you can ignore the above instructions.
Nix environments
An environment built by Nix is not totally isolated from the rest of
the system. Suppose that you the program sl
installed on
your system, and suppose you build a Nix environment that also comes
with sl
. If you activate that environment, the version of
sl
that will run when called is the one included in the Nix
environment. If, however, you start sl
in a Nix environment
that does not come with it, then your system’s sl
will get
used instead. This can be useful when working interactively with a Nix
environment, because you can use your usual IDE to work with it. The
only exception is RStudio: RStudio looks for R in predefined paths and
cannot “see” the R provided by a Nix environment, it will instead use
the version installed on your machine. This means that if you use
RStudio to work interactively with R, you will need to install RStudio
inside that environment. rix::rix()
can generate a
default.nix
file that does that.
Day-to-day use of {rix}
The ideal workflow when using {rix}
is to create a new,
separate environment at the start of a project. Let’s say that you wish
to analyse some data set, and need dplyr and
ggplot2. Let’s also suppose that you use RStudio as your
IDE. With the rix::rix()
function, you can easily generate
the right default.nix
file. You need to provide the
following inputs to rix()
:
-
r_ver
: the version of R required. Use “latest” for the latest version; -
r_pkgs
: the required R packages. For example “dplyr”; -
system_pkgs
: the required system packages, if needed. For example “quarto”, or a Python interpreter; -
git_pkgs
: list of git packages to add. See the example below; -
ide
: the integrated development editor to use. For example “rstudio” if you want to use RStudio. Refer to the “Interactive work with {rix}” for more details; -
path
: the path where to save thedefault.nix
file. -
overwrite
: whether to overwrite thedefault.nix
file or not. -
print
: whether to print thedefault.nix
file to the console or not.
In such a situation, you could create an environment with the
following call to rix()
:
path_default_nix <- tempdir()
rix(r_ver = "latest",
r_pkgs = c("dplyr", "ggplot2"),
system_pkgs = NULL,
git_pkgs = NULL,
ide = "rstudio",
project_path = path_default_nix,
overwrite = TRUE,
print = TRUE)
#> # This file was generated by the {rix} R package v0.5.1.9000 on 2024-01-24
#> # with following call:
#> # >rix(r_ver = "5f5210aa20e343b7e35f40c033000db0ef80d7b9",
#> # > r_pkgs = c("dplyr",
#> # > "ggplot2"),
#> # > system_pkgs = NULL,
#> # > git_pkgs = NULL,
#> # > ide = "rstudio",
#> # > project_path = path_default_nix,
#> # > overwrite = TRUE,
#> # > print = TRUE)
#> # It uses nixpkgs' revision 5f5210aa20e343b7e35f40c033000db0ef80d7b9 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/5f5210aa20e343b7e35f40c033000db0ef80d7b9.tar.gz") {};
#> rpkgs = builtins.attrValues {
#> inherit (pkgs.rPackages) dplyr ggplot2;
#> };
#> system_packages = builtins.attrValues {
#> inherit (pkgs) R glibcLocales ;
#> };
#> rstudio_pkgs = pkgs.rstudioWrapper.override {
#> packages = [ rpkgs ];
#> };
#> 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 rstudio_pkgs ];
#> shellHook = "R --vanilla";
#> }
To start using this environment, open a console in the folder
containing default.nix
and use the following Nix
command:
nix-build
nix-build
is a Nix command that builds an environment
according to the specifications found in a default.nix
file. Once the environment is done building, you should find a new file
called result
next to the default.nix
file.
This file is a symlink to the software installed by Nix. To now use the
environment, type:
nix-shell
You can now start the RStudio provided by that environment by typing
rstudio
. This will start a version of RStudio specific to
this environment. If you already had RStudio installed by using your
operating system’s installer, that version of RStudio will not be able
to interact with Nix environments. This is because RStudio looks for R
in certain specific paths that don’t include any Nix environments. This
is not the case with other editors like Visual Studio Code or Emacs. If
you use Visual Studio Code, you can use the following call to
rix()
:
path_default_nix <- tempdir()
rix(r_ver = "latest",
r_pkgs = c("dplyr", "ggplot2"),
system_pkgs = NULL,
git_pkgs = NULL,
ide = "code",
project_path = path_default_nix,
overwrite = TRUE)
(note the value provided to the ide
argument).
This generates the following default.nix
file:
#> # This file was generated by the {rix} R package v0.5.1.9000 on 2024-01-24
#> # with following call:
#> # >rix(r_ver = "5f5210aa20e343b7e35f40c033000db0ef80d7b9",
#> # > r_pkgs = c("dplyr",
#> # > "ggplot2"),
#> # > system_pkgs = NULL,
#> # > git_pkgs = NULL,
#> # > ide = "code",
#> # > project_path = path_default_nix,
#> # > overwrite = TRUE)
#> # It uses nixpkgs' revision 5f5210aa20e343b7e35f40c033000db0ef80d7b9 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/5f5210aa20e343b7e35f40c033000db0ef80d7b9.tar.gz") {};
#> rpkgs = builtins.attrValues {
#> inherit (pkgs.rPackages) dplyr ggplot2 languageserver;
#> };
#> system_packages = builtins.attrValues {
#> inherit (pkgs) R glibcLocales ;
#> };
#> 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 ];
#> shellHook = "R --vanilla";
#> }
As you can see, specifying ide = "code"
adds the
languageserver package to the list of packages that must
be installed for this environment. This is because Visual Studio Code
requires this package to interact with R. This will not install a Nix
environment-specific version of Visual Studio Code. Now, instead of
typing rstudio
in the Nix shell of your environment, type
code
and this will start the Visual Studio Code you usually
use.
If you use another editor, like Emacs, then use
ide = "other"
, and start that editor inside an activated
Nix environment.
For more details on interactive use, read the “Interactive use” vignette.
Running old projects with {rix}
The example below shows how to create a default.nix
with
instructions to build an environment with R version 4.2.1, the
dplyr and janitor packages and no specific
IDE:
path_default_nix <- tempdir()
rix(r_ver = "4.2.1",
r_pkgs = c("dplyr", "janitor"),
system_pkgs = c("quarto"),
git_pkgs = NULL,
ide = "other",
project_path = path_default_nix,
overwrite = TRUE)
The file looks like this:
#> # This file was generated by the {rix} R package v0.5.1.9000 on 2024-01-24
#> # with following call:
#> # >rix(r_ver = "79b3d4bcae8c7007c9fd51c279a8a67acfa73a2a",
#> # > r_pkgs = c("dplyr",
#> # > "janitor"),
#> # > system_pkgs = c("quarto"),
#> # > git_pkgs = NULL,
#> # > ide = "other",
#> # > project_path = path_default_nix,
#> # > overwrite = TRUE)
#> # It uses nixpkgs' revision 79b3d4bcae8c7007c9fd51c279a8a67acfa73a2a for reproducibility purposes
#> # which will install R version 4.2.1
#> # Report any issues to https://github.com/b-rodrigues/rix
#> let
#> pkgs = import (fetchTarball "https://github.com/NixOS/nixpkgs/archive/79b3d4bcae8c7007c9fd51c279a8a67acfa73a2a.tar.gz") {};
#> rpkgs = builtins.attrValues {
#> inherit (pkgs.rPackages) dplyr janitor;
#> };
#> system_packages = builtins.attrValues {
#> inherit (pkgs) R glibcLocales quarto;
#> };
#> 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 ];
#> shellHook = "R --vanilla";
#> }
The first line is quite important, as it shows which
revision of nixpkgs
is being used for this
environment. The revision is the commit hash of that particular
release of nixpkgs
, here: 79b3d4bcae8
. This
revision of nixpkgs
is the one that shipped version 4.2.1
of R, so the dplyr and janitor packages
that will get installed will be the versions available in that revision
as well. This means that R versions and package versions are always
coupled when using Nix. However, if you need a specific version of R,
but also a specific version of a package that is not available in that
particular Nix revision, one solution is to install that package from
Github.
Installing old packages archived on CRAN
It is also possible to install an arbitrary version of a package that has been archived on CRAN:
rix(r_ver = "4.2.1",
r_pkgs = c("dplyr@0.8.0", "janitor@1.0.0"),
system_pkgs = NULL,
git_pkgs = NULL,
ide = "other",
project_path = path_default_nix,
overwrite = TRUE)
cat(readLines(paste0(path_default_nix, "/default.nix")), sep = "\n")
#> # This file was generated by the {rix} R package v0.5.1.9000 on 2024-01-24
#> # with following call:
#> # >rix(r_ver = "79b3d4bcae8c7007c9fd51c279a8a67acfa73a2a",
#> # > r_pkgs = c("dplyr@0.8.0",
#> # > "janitor@1.0.0"),
#> # > system_pkgs = NULL,
#> # > git_pkgs = NULL,
#> # > ide = "other",
#> # > project_path = path_default_nix,
#> # > overwrite = TRUE)
#> # It uses nixpkgs' revision 79b3d4bcae8c7007c9fd51c279a8a67acfa73a2a for reproducibility purposes
#> # which will install R version 4.2.1
#> # Report any issues to https://github.com/b-rodrigues/rix
#> let
#> pkgs = import (fetchTarball "https://github.com/NixOS/nixpkgs/archive/79b3d4bcae8c7007c9fd51c279a8a67acfa73a2a.tar.gz") {};
#> git_archive_pkgs = [ (pkgs.rPackages.buildRPackage {
#> name = "dplyr";
#> src = pkgs.fetchzip {
#> url = "https://cran.r-project.org/src/contrib/Archive/dplyr/dplyr_0.8.0.tar.gz";
#> sha256 = "sha256-f30raalLd9KoZKZSxeTN71PG6BczXRIiP6g7EZeH09U=";
#> };
#> propagatedBuildInputs = builtins.attrValues {
#> inherit (pkgs.rPackages) assertthat glue magrittr pkgconfig R6 Rcpp rlang tibble tidyselect BH plogr;
#> };
#> })
#> (pkgs.rPackages.buildRPackage {
#> name = "janitor";
#> src = pkgs.fetchzip {
#> url = "https://cran.r-project.org/src/contrib/Archive/janitor/janitor_1.0.0.tar.gz";
#> sha256 = "sha256-3NJomE/CNbOZ+ohuVZJWe2n1RVGUm8x8a0A0qzLmdN4=";
#> };
#> propagatedBuildInputs = builtins.attrValues {
#> inherit (pkgs.rPackages) dplyr tidyr snakecase magrittr purrr rlang;
#> };
#> })];
#> system_packages = builtins.attrValues {
#> inherit (pkgs) R glibcLocales ;
#> };
#> 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 = [ git_archive_pkgs system_packages ];
#> shellHook = "R --vanilla";
#> }
This feature should ideally be used sparingly. If you want to reconstruct an environment as it was around 2019, use the version of R that was current at the time. This will ensure that every package that gets installed is at a version compatible with that version of R, which might not be the case if you need to install a very old version of one particular package.
Installing packages from Github
It is also possible to install packages from Github:
rix(r_ver = "4.2.1",
r_pkgs = c("dplyr", "janitor"),
system_pkgs = c("quarto"),
git_pkgs = list(
list(package_name = "housing",
repo_url = "https://github.com/rap4all/housing/",
branch_name = "fusen",
commit = "1c860959310b80e67c41f7bbdc3e84cef00df18e"),
list(package_name = "fusen",
repo_url = "https://github.com/ThinkR-open/fusen",
branch_name = "main",
commit = "d617172447d2947efb20ad6a4463742b8a5d79dc")
),
ide = "other",
project_path = path_default_nix,
overwrite = TRUE)
cat(readLines(paste0(path_default_nix, "/default.nix")), sep = "\n")
#> # This file was generated by the {rix} R package v0.5.1.9000 on 2024-01-24
#> # with following call:
#> # >rix(r_ver = "79b3d4bcae8c7007c9fd51c279a8a67acfa73a2a",
#> # > r_pkgs = c("dplyr",
#> # > "janitor"),
#> # > system_pkgs = c("quarto"),
#> # > git_pkgs = list(list(package_name = "housing",
#> # > repo_url = "https://github.com/rap4all/housing/",
#> # > branch_name = "fusen",
#> # > commit = "1c860959310b80e67c41f7bbdc3e84cef00df18e"),
#> # > list(package_name = "fusen",
#> # > repo_url = "https://github.com/ThinkR-open/fusen",
#> # > branch_name = "main",
#> # > commit = "d617172447d2947efb20ad6a4463742b8a5d79dc")),
#> # > ide = "other",
#> # > project_path = path_default_nix,
#> # > overwrite = TRUE)
#> # It uses nixpkgs' revision 79b3d4bcae8c7007c9fd51c279a8a67acfa73a2a for reproducibility purposes
#> # which will install R version 4.2.1
#> # Report any issues to https://github.com/b-rodrigues/rix
#> let
#> pkgs = import (fetchTarball "https://github.com/NixOS/nixpkgs/archive/79b3d4bcae8c7007c9fd51c279a8a67acfa73a2a.tar.gz") {};
#> rpkgs = builtins.attrValues {
#> inherit (pkgs.rPackages) dplyr janitor;
#> };
#> git_archive_pkgs = [(pkgs.rPackages.buildRPackage {
#> name = "housing";
#> src = pkgs.fetchgit {
#> url = "https://github.com/rap4all/housing/";
#> branchName = "fusen";
#> rev = "1c860959310b80e67c41f7bbdc3e84cef00df18e";
#> sha256 = "sha256-s4KGtfKQ7hL0sfDhGb4BpBpspfefBN6hf+XlslqyEn4=";
#> };
#> propagatedBuildInputs = builtins.attrValues {
#> inherit (pkgs.rPackages) dplyr ggplot2 janitor purrr readxl rlang rvest stringr tidyr;
#> };
#> })
#> (pkgs.rPackages.buildRPackage {
#> name = "fusen";
#> src = pkgs.fetchgit {
#> url = "https://github.com/ThinkR-open/fusen";
#> branchName = "main";
#> rev = "d617172447d2947efb20ad6a4463742b8a5d79dc";
#> sha256 = "sha256-TOHA1ymLUSgZMYIA1a2yvuv0799svaDOl3zOhNRxcmw=";
#> };
#> propagatedBuildInputs = builtins.attrValues {
#> inherit (pkgs.rPackages) attachment cli desc devtools glue here magrittr parsermd roxygen2 stringi tibble tidyr usethis yaml;
#> };
#> }) ];
#> system_packages = builtins.attrValues {
#> inherit (pkgs) R glibcLocales quarto;
#> };
#> 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 = [ git_archive_pkgs rpkgs system_packages ];
#> shellHook = "R --vanilla";
#> }
This will install two packages from Github: the
{housing}
package and more specifically the code as it is
in the fusen
branch. The commit is also provided, to pin
the exact version of the package needed. The fusen
package is also installed, from the main branch at commit
d617172447d
.
A complete example
This example shows how all features of {rix}
can work
together:
rix(r_ver = "4.2.1",
r_pkgs = c("dplyr", "janitor", "AER@1.2-8"),
system_pkgs = c("quarto"),
git_pkgs = list(
list(package_name = "housing",
repo_url = "https://github.com/rap4all/housing/",
branch_name = "fusen",
commit = "1c860959310b80e67c41f7bbdc3e84cef00df18e"),
list(package_name = "fusen",
repo_url = "https://github.com/ThinkR-open/fusen",
branch_name = "main",
commit = "d617172447d2947efb20ad6a4463742b8a5d79dc")
),
ide = "rstudio",
project_path = path_default_nix,
overwrite = TRUE)
cat(readLines(paste0(path_default_nix, "/default.nix")), sep = "\n")
#> # This file was generated by the {rix} R package v0.5.1.9000 on 2024-01-24
#> # with following call:
#> # >rix(r_ver = "79b3d4bcae8c7007c9fd51c279a8a67acfa73a2a",
#> # > r_pkgs = c("dplyr",
#> # > "janitor",
#> # > "AER@1.2-8"),
#> # > system_pkgs = c("quarto"),
#> # > git_pkgs = list(list(package_name = "housing",
#> # > repo_url = "https://github.com/rap4all/housing/",
#> # > branch_name = "fusen",
#> # > commit = "1c860959310b80e67c41f7bbdc3e84cef00df18e"),
#> # > list(package_name = "fusen",
#> # > repo_url = "https://github.com/ThinkR-open/fusen",
#> # > branch_name = "main",
#> # > commit = "d617172447d2947efb20ad6a4463742b8a5d79dc")),
#> # > ide = "rstudio",
#> # > project_path = path_default_nix,
#> # > overwrite = TRUE)
#> # It uses nixpkgs' revision 79b3d4bcae8c7007c9fd51c279a8a67acfa73a2a for reproducibility purposes
#> # which will install R version 4.2.1
#> # Report any issues to https://github.com/b-rodrigues/rix
#> let
#> pkgs = import (fetchTarball "https://github.com/NixOS/nixpkgs/archive/79b3d4bcae8c7007c9fd51c279a8a67acfa73a2a.tar.gz") {};
#> rpkgs = builtins.attrValues {
#> inherit (pkgs.rPackages) dplyr janitor;
#> };
#> git_archive_pkgs = [(pkgs.rPackages.buildRPackage {
#> name = "housing";
#> src = pkgs.fetchgit {
#> url = "https://github.com/rap4all/housing/";
#> branchName = "fusen";
#> rev = "1c860959310b80e67c41f7bbdc3e84cef00df18e";
#> sha256 = "sha256-s4KGtfKQ7hL0sfDhGb4BpBpspfefBN6hf+XlslqyEn4=";
#> };
#> propagatedBuildInputs = builtins.attrValues {
#> inherit (pkgs.rPackages) dplyr ggplot2 janitor purrr readxl rlang rvest stringr tidyr;
#> };
#> })
#> (pkgs.rPackages.buildRPackage {
#> name = "fusen";
#> src = pkgs.fetchgit {
#> url = "https://github.com/ThinkR-open/fusen";
#> branchName = "main";
#> rev = "d617172447d2947efb20ad6a4463742b8a5d79dc";
#> sha256 = "sha256-TOHA1ymLUSgZMYIA1a2yvuv0799svaDOl3zOhNRxcmw=";
#> };
#> propagatedBuildInputs = builtins.attrValues {
#> inherit (pkgs.rPackages) attachment cli desc devtools glue here magrittr parsermd roxygen2 stringi tibble tidyr usethis yaml;
#> };
#> }) (pkgs.rPackages.buildRPackage {
#> name = "AER";
#> src = pkgs.fetchzip {
#> url = "https://cran.r-project.org/src/contrib/Archive/AER/AER_1.2-8.tar.gz";
#> sha256 = "sha256-OqxXcnUX/2C6wfD5fuNayc8OU+mstI3tt4eBVGQZ2S0=";
#> };
#> propagatedBuildInputs = builtins.attrValues {
#> inherit (pkgs.rPackages) car lmtest sandwich survival zoo Formula;
#> };
#> })];
#> system_packages = builtins.attrValues {
#> inherit (pkgs) R glibcLocales quarto;
#> };
#> rstudio_pkgs = pkgs.rstudioWrapper.override {
#> packages = [ git_archive_pkgs rpkgs ];
#> };
#> 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 = [ git_archive_pkgs rpkgs system_packages rstudio_pkgs ];
#> shellHook = "R --vanilla";
#> }
To learn more about using {rix}
on a daily basis, read
the Interactive
use vignette.