Skip to content

Commit d35a727

Browse files
committed
Write blog post about building ROOT on macOS with Nix
Showing a streamlined platform-independent way to do ROOT development could help to attract more development talent.
1 parent e879ba6 commit d35a727

File tree

1 file changed

+222
-0
lines changed

1 file changed

+222
-0
lines changed
Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
---
2+
title: "Make ROOT your own on macOS with Nix"
3+
layout: archive
4+
author: Jonas Rembser
5+
---
6+
7+
In this blog post, you will learn how to reproducibly build ROOT from source with Nix on macOS or Linux, getting you started quickly with hacking the ROOT source code and participate in ROOTs open development.
8+
9+
## Introduction to Nix
10+
11+
If you’ve ever tried building a complex C++ project like ROOT on macOS, you’ve probably wrestled with dependencies that have to be installed with Homebrew, Xcode’s quirks, and subtle differences in compiler behavior.
12+
While these tools are powerful in their own domains, they aren’t always ideal for development environments that require fine-grained control, reproducibility, and consistency across systems.
13+
14+
That's where [Nix](https://nixos.org) shines.
15+
16+
Nix is a cross-platform package manager that sets itself apart from e.g. Conda, Homebrew, or apt by enforcing fully **reproducible environments** that **don't pollute your system**.
17+
With most package managers, one can install and delete packages at will and one has to manually configure the system on top of that.
18+
It's easy to lose track of how the environment looks like and how it can be reproduced.
19+
Nix solves this problem by allowing you to define *exactly* what dependencies, compilers, and configuration flags are used, all in a declarative `shell.nix` file.
20+
When someone else enters the same nix-shell, they get the same environment.
21+
22+
Also, installing development libraries globally with Homebrew or relying on Xcode's SDK can clutter your system and lead to conflicts.
23+
Nix isolates your development environment in a pure sandbox shell, keeping your system unpolluted.
24+
Packages are only loaded when you enter a given nix shell, with no trace after you exit the shell other than cached artifacts that can be garbage collected whenever you want.
25+
26+
Since Nix is cross platform, this post actually applies to **any** Linux distribution as well — including of course [NixOS](https://en.wikipedia.org/wiki/NixOS), where the whole system is declared in a single `configuration.nix` file.
27+
Still, it's marketed towards Mac users, because since there is no official package manager on macOS, building ROOT on that platform can be particularly challenging.
28+
29+
One downside of Nix is that it's not compliant with Linux Standard Base (LSB), so you might often have to patch code to use the paths that Nix expects.
30+
Fortunately, once you have figured out what the right patch is, you will never have to solve the same problem again because the solution is set in stone in your nix configuration files.
31+
Still, in the beginning there can be a steep learning curve, and even though building individual packages like ROOT with Nix as described in this blog post is easy, things can be more complicated when you want to build more interdependent packages in the same environment.
32+
33+
On a personal note: I have transitioned to Nix for all my development work about a year ago and don't have any reason to go back.
34+
Especially for working on ROOTs build system, the ability to test ROOT in different environments just by changing a line in my `shell.nix` without any manual bookkeeping of what packages are installed on my system is golden.
35+
Using Docker might also give you the reproducibility, but for development, Docker can be quite clunky because of the boundary between the host and the Docker image, and Nix gives you a faster turnaround by caching the environment at the package level and not the image level.
36+
37+
## Installing Nix
38+
39+
Installing Nix should always be done using the [official download instructions](https://nixos.org/download/), performing the recommended multi-user installation.
40+
41+
## Setting up your ROOT development environment
42+
43+
It's good to do your ROOT development in a separate directory, for example called `root`. You can create the directory from the terminal:
44+
45+
```bash
46+
mkdir root
47+
cd root
48+
```
49+
50+
Now, it's time to create the `shell.nix` file in that directory.
51+
52+
Here is a suggested `shell.nix` file for building ROOT with debug info and tests:
53+
54+
```nix
55+
# Content of root/shell.nix
56+
57+
{
58+
pkgs ? import <nixpkgs> { },
59+
}:
60+
61+
let
62+
63+
# We want to have these Python packages in our environment to use together
64+
# with ROOT and test the pythonizations.
65+
python3Packages = with pkgs.python3.pkgs; [
66+
matplotlib
67+
numba
68+
numpy
69+
pandas
70+
pytest
71+
scikit-learn
72+
xgboost
73+
];
74+
75+
in
76+
pkgs.mkShell {
77+
78+
# For all the build inputs, we inherit the package lists from the official
79+
# nix ROOT package, plus adding some packages that we need for testing
80+
# (gtest, etc.) and faster building (ccache).
81+
82+
nativeBuildInputs =
83+
with pkgs;
84+
[
85+
ccache
86+
]
87+
++ pkgs.root.nativeBuildInputs;
88+
89+
propagatedBuildInputs = pkgs.root.propagatedBuildInputs;
90+
91+
buildInputs =
92+
with pkgs;
93+
[
94+
gtest
95+
libuuid # required for testing
96+
]
97+
++ python3Packages
98+
++ pkgs.root.buildInputs;
99+
100+
# Define aliases for quick configuration and build
101+
shellHook = ''
102+
alias configure="cmake \
103+
-DClang_DIR=${pkgs.root.clang}/lib/cmake/clang/ \
104+
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
105+
-DCMAKE_CXX_FLAGS='-fno-omit-frame-pointer' \
106+
-DCMAKE_C_FLAGS='-fno-omit-frame-pointer' \
107+
-DCMAKE_INSTALL_PREFIX=../root_install \
108+
-Dbuiltin_clang=OFF \
109+
-Dbuiltin_llvm=OFF \
110+
-Dccache=ON \
111+
-Dfail-on-missing=ON \
112+
-Dfftw3=ON \
113+
-Dfitsio=OFF \
114+
-Dgnuinstall=ON \
115+
-Dmathmore=ON \
116+
-Droottest=ON \
117+
-Druntime_cxxmodules=${if pkgs.stdenv.isDarwin then "OFF" else "ON"} \
118+
-Dsqlite=OFF \
119+
-Dtesting=ON \
120+
-Dvdt=OFF \
121+
-Dwebgui=OFF \
122+
../root_src"
123+
124+
alias build-and-install="cmake --build . --target install -j12"
125+
'';
126+
}
127+
```
128+
129+
A few more explanations:
130+
131+
* In several places, we refer to `pkgs.root`. This points to the [official ROOT package in the nixpkgs repository](https://github.com/NixOS/nixpkgs/blob/master/pkgs/by-name/ro/root/package.nix). Think of `nixpkgs` of a single huge data structure that declares how to build any package, and your `shell.nix` environments are querying this huge data structure to instantiate specific environments. That means we can re-use any variables that are declared in the `nixpkgs` world at any time! In our `shell.nix`, we reuse the variables from `pkgs.root` that list the dependencies, also including `pkgs.root.clang`, which is the patched Clang version that ROOT requires. This means we don't have to rebuild the patched Clang ourselves, as it's already in the Nix cache.
132+
* *Side note*: the fact that all package recipes are in a single repository, written in the common domain-specific Nix language that is basically JSON on steroids is one of the great strengths of Nix.
133+
* The `nativeBuildInputs`, `propagatedBuildInputs`, and `buildInputs` serve slightly different purposes:
134+
* `nativeBuildInputs` lists the build-time dependencies
135+
* `buildInputs` lists the runtime dependencies
136+
* `propagatedBuildInputs` declares runtime dependencies that should automatically be passed to anything that depends on your package
137+
If you just use the `nix-shell` environment to build, test and use ROOT all in one, then there is not a practical difference, but it becomes important if you want to write `package.nix` files that declare individual packages that you plan to re-use.
138+
* The Python packages in the environment are not strictly needed for building ROOT, but these are the packages that are commonly used together with ROOT, also in the unit tests and tutorials. So it's good to have them.
139+
* The `shellHook` variable contains bash code that is run when opening the `nix-shell`. We use it to define aliases for configuring ROOT with the desired CMake configuration flags, and then later to build and install ROOT with the desired number of threads (12 threads in our example).
140+
* In the CMake command, we set `-DCMAKE_INSTALL_PREFIX=../root_install` such that the install directory sits nicely next to the `root_src` and `root_build` directories.
141+
* See the page about [installing ROOT from source](http://127.0.0.1:4000/base/install/build_from_source/#all-build-options) for more info on the ROOT-specific CMake flags. Depending on what ROOT feature you want to develop, you have to toggle, add, or remove flags.
142+
* This blog post is not updated. So if the environment doesn't work anymore, please reach out to us, optimally by opening a [GitHub issue](https://github.com/root-project/root/issues) requesing a working `shell.nix` example for ROOT development.
143+
144+
If your `shell.nix` file is in the `root` directory and you changed to that directory in your terminal,
145+
you activate the environment by running:
146+
```bash
147+
shell-nix
148+
```
149+
It will take some time to download the dependencies to the Nix cache if you enter the environment for the first time.
150+
To exit the environment later, run the `exit` command in the shell.
151+
152+
## Building ROOT
153+
154+
Have you entered the `nix-shell` environment with the configuration file above? Very good! If successful, you should now have the green `nix-shell` prompt in your terminal.
155+
156+
Time to clone the ROOT repository:
157+
```bash
158+
git clone https://github.com/root-project/root.git root_src
159+
```
160+
Create and enter the build directory:
161+
```bash
162+
mkdir root_build
163+
cd root_build
164+
```
165+
Configure the build with the command defined in the `shellHook`, which takes about half a minute:
166+
```bash
167+
configure
168+
```
169+
Finally, build and install ROOT, which takes about 15 minutes running the first time on my Mac Mini M1 with 16 GB:
170+
```bash
171+
build-and-install
172+
```
173+
Are you still in the build directory? Then you can source the ROOT installation as follows:
174+
```bash
175+
source ../root_install/bin/thisroot.sh
176+
```
177+
If you want to test the installation, you can try to start the `root` prompt, or maybe open a `python` interpreter and try to `import ROOT`. Or, if you are adventuruos, maybe run a ROOT Python tutorial:
178+
```bash
179+
python -i ../root_src/tutorials/roofit/roofit/rf101_basics.py
180+
```
181+
The `-i` flag means that the Python interpreter will keep running at the end of the script such that the plot remains opened, but it also means you have to manually quit the Python interpreter with `Ctrl+D`.
182+
183+
## Make ROOT your own by changing the source code
184+
185+
You have now a clean directory structure for ROOT development. The content of your ROOT directory should look like this:
186+
```
187+
root_src
188+
root_build
189+
root_install
190+
shell.nix
191+
```
192+
193+
We have spent quite some time in the `root_build`, but it's time to change the ROOT source code and see if you can rebuild ROOT after doing some modifications!
194+
195+
As an example, let's modify the [core/rint/src/TRint.cxx](https://github.com/root-project/root/blob/master/core/rint/src/TRint.cxx#L519) file in the source code (which is in `root_src`).
196+
You can use your preferred code editor, for example Visual Studio Code or Neovim.
197+
The file has a line that contains the string `"Welcome to ROOT"`, which is printed when starting the ROOT interpreter.
198+
You can try to modify ROOT by replacing the string with `"Welcome to my modified ROOT"`, as a test.
199+
200+
Back in the `root_build` directory, you can use again the `build-and-install` alias from our `shell.nix` to rebuild and install.
201+
Thanks to CMakes caching and `ccache`, the rebuild should take less than a minute.
202+
203+
If you now start the `root` command prompt you will see:
204+
```
205+
------------------------------------------------------------------
206+
| Welcome to my modified ROOT 6.37.01 https://root.cern |
207+
| (c) 1995-2025, The ROOT Team; conception: R. Brun, F. Rademakers |
208+
| Built for macosxarm64 on Jan 01 1980, 00:00:00 |
209+
| From heads/master@v6-37-01-6844-g7bfa4867a6 |
210+
| With clang version 19.1.7 |
211+
| Try '.help'/'.?', '.demo', '.license', '.credits', '.quit'/'.q' |
212+
------------------------------------------------------------------
213+
```
214+
215+
## Conclusion
216+
217+
**Congratulations** on building ROOT from source with Nix and making it your own by hacking the source code!
218+
219+
Of course, we didn't just write this blog post for the fun of it :-)
220+
Hopefully, this knowledge will reduce the barrier of entry to ROOT development for some interested users, so that they can make the changes that they always wanted to make to ROOT, following our [contributor guidelines](https://root.cern/contribute/) and opening a [pull request on GitHub](https://github.com/root-project/root/pulls).
221+
222+
We hope to see you over there!

0 commit comments

Comments
 (0)