Recently I started getting into the world of Nix. First, to manage my user environment using Home Manager, and now to manage my ROS 2 development environment. On any Linux distribution.
TL;DR
Want to have a full ROS 2 development environment, completely from scratch on any Linux distribution? Just run these two commands:
sh <(curl --proto '=https' --tlsv1.2 -L https://nixos.org/nix/install) --daemon
nix develop 'github:lopsided98/nix-ros-overlay/master#example-ros2-desktop-jazzy'
Boom, you’re in a ROS 2 Jazzy development shell, where you can just immediately run something like:
ros2 launch demo_nodes_cpp talker_listener_launch.xml
How cool is that? Read to get started with a more detailed setup of an environment that fits your project.
What is Nix?
Nix is a package manager, like Apt for Debian/Ubuntu, Yum for Fedora, and Pacman for Arch. However, it does things a bit differently.
Firstly, it uses its own functional language to declare how packages are built, or environments are set up. Imagine something a bit like a Dockerfile, or perhaps an Ansible configuration, but then with the full power of a programming language.
Next, it keeps track of dependencies in full detail, including the exact versions. Actually it keeps track of a hash of everything in a dependency at a specific version. If different packages require different versions of the same dependency, then Nix will install both and give each package access only to the version it needs. This ensures that builds of packages are exactly reproducible and prevents dependency hell.
There are package managers in some programming language ecosystems that achieve something similar, such as NPM for Node.js. However, Nix does this for any package, any language, and without Docker or virtual environments. This makes Nix ideal for setting up consistent and reproducible development environments.
To learn more about Nix (and there is a lot more), look through the official learning material. In this post I will show how I used it to set up a development environment for ROS 2.
Getting started: installing and setting up Nix
Nix can be installed onto any Linux distribution. Actually, it can be installed on macOS and Windows (WSL2) as well, but I have not used it on those systems myself. You can follow the installation instructions here:
https://nix.dev/install-nix#install-nix
Next, you want to enable so called ‘Flakes’. Flakes are a standardised way of declaring the inputs (dependencies) and outputs of a build process. Everywhere you look for documentation on them you will be told that they are experimental, but it seems like they are becoming the de-facto standard way of using Nix.
Still, because they are technically experimental, you have to tell Nix it is OK to use them.
Do this by editing or creating the file ~/.config/nix/nix.conf, and add this line:
experimental-features = nix-command flakes
This will enable it for your own account.
You can also add the line to /etc/nix/nix.conf instead to enable it system wide.
Setting up your ROS 2 workspace
Now create a new directory for your ROS 2 workspace.
I will use ~/src/ros2_nix.
In there, create a file with the following contents:
{
inputs = {
nix-ros-overlay.url = "github:lopsided98/nix-ros-overlay/master";
nixpkgs.follows = "nix-ros-overlay/nixpkgs"; # IMPORTANT!!!
};
outputs = { self, nix-ros-overlay, nixpkgs }:
nix-ros-overlay.inputs.flake-utils.lib.eachDefaultSystem (system:
let
pkgs = import nixpkgs {
inherit system;
overlays = [ nix-ros-overlay.overlays.default ];
};
in {
devShells.default = pkgs.mkShell {
name = "Example project";
packages = [
pkgs.colcon
# ... other non-ROS packages
(with pkgs.rosPackages.jazzy; buildEnv {
paths = [
ros-core
# ... other ROS packages
];
})
];
};
});
nixConfig = {
extra-substituters = [ "https://ros.cachix.org" ];
extra-trusted-public-keys = [ "ros.cachix.org-1:dSyZxI8geDCJrwgvCOHDoAfOm5sV1wCPjBkKL+38Rvo=" ];
};
}
I know that may seem like a lot to take in. This is Nix code, specifying how to set up a full installation of ROS 2. Let me break down some of it.
inputs/outputsA Flake’s standard structure consists of these 2 entries:
inputsto provide dependencies, andoutputsto declare what should be the… output.nix-ros-overlay.url = "github:lopsided98/nix-ros-overlay/master"This points Nix to https://github.com/lopsided98/nix-ros-overlay, which is the project that actually provides everything required to install ROS 2 with Nix. The maintainers of this Nix ‘overlay’ deserve all the praise for making this possible.
nix-ros-overlay.inputs.flake-utils.lib.eachDefaultSystem( ... )This line and the next few set things up so Nix will use packages for your specific system.
devShells.default = pkgs.mkShell { ... }This line reflects that our goal is to create a development environment. Nix allows you to do that by creating a shell environment with everything you need. Such an enironment is a bit similar to preparing your shell with the ubiquitous
source install/setup.bashcommand for ROS 2. But rather than just setting up environment variables for your local workspace, Nix will install all dependencies and performs any other setup required to create a full development environment, without affecting your host machine.packages = [ ... ]Here you specify the packages you want to install into your dev environment. These include packages available in the standard Nix Packages collection (you can search for any package here). But, importantly, also…
(with pkgs.rosPackages.jazzy ...)ROS 2 packages. There are now between 1500 and 2000 packages that are generated by the ROS 2 overlay project. This is also where you specify the ROS 2 distribution you want to use; currently there is support for
humble,jazzy,kiltedandrolling.extra-substituters = [ "https://ros.cachix.org" ]By default, all ROS packages you list to be installed in the shell will be built from scratch the first time you start the environment. A ‘substituter’ is a binary cache that allows you to download pre-built packages instead. See the documentation on how to allow the use of this substituter by Nix.
Starting your environment
Let’s now get into the development environment.
But first: Nix requires that your flake.nix file is under version control, so let’s add it to Git first:
git init .
git add flake.nix
You don’t have to commit it, but of course it is good to keep track of your revisions.
Now run:
nix develop
and there you go! On the first try it will probably take quite some time to set up the environment, but other times will be more instant.
You can check the ROS 2 distribution with echo $ROS_DISTRO and check where some standard commands live with which ros2 and which colcon.
You will see that they are inside /nix/store/<some-hash>-<package-name>/bin.
The main effect of specifying and starting the environment with flake.nix is the installation of the outputs in the Nix ‘store’, away from your operating system’s main packages, and having the shell set up to put them on your $PATH.
The hash in the file path is how Nix ensures reproducibility: if the version on your machine shares the same hash to the version on somebody else’s machine, you know they are identical, to the final byte.
If you check your workspace now, you see that there is a new file called flake.lock.
This file records the exact versions (hashes) of the dependencies used, such as of the nix-ros-overlay, which in turn strictly determine the version (hashes) of the outputs.
You should add this lock file to git as well, so that anybody you share this setup with, indeed ends up with 100% exactly the same environment.
Where to go from here?
To get to a more useful environment, you will need to grow your flake.nix file.
As a starter, you probably want to use ros-base or desktop to get a more complete environment.
The latter will provider your standard visualisation tools like RQT and Rviz2.
However, when you try to run rviz2 inside of your dev shell, you will see a lot of red ERROR text…
it requires a little more setup to be able to run applications that use OpenGL, most commonly by using nixGL.
It wraps your commands such that they can properly find and use the graphics drivers from your host system.
You will need to:
Add the following to the
inputs { ... }section:nixgl.url = "github:nix-community/nixGL";Append the nixGL overlay as such:
overlays = [ nix-ros-overlay.overlays.default nixgl.overlay ];And add the following to
packages = [ ... ]:pkgs.nixgl.auto.nixGLDefault
With this, you will have to start your shell with nix develop --impure, because the auto.nixGLDefault wrapper installation will have to do some ‘impure’ actions, in the functional programming sense, depending on the host system.
Now you should be able to run Rviz2 successfully with:
nixGL rviz2
Instead of the auto.nixGLDefault wrapper you can also use Nvidia specific ones, or nixGLIntel for Intel, AMD or Nvidia nouveau driver installations.
This last one does not require the --impure flag when starting the shell.
Final words
There is still a lot more to discover about Nix. I am still only at the start of the journey myself! However, my experience so far tells me that using Nix to set up your development environment is quite powerful.
Ging the Nix way has the oportunity to give you:
- real parity beween development, CI/CD and production environments due to strictly enforced software and dependency versions;
- a path to generate Software Bills of Materials (SBOM)
- significant build time and update efficiency gains; see e.g. Clearpath’s 2022 presentation on building ROS packages with Nix
Happy hacking!
