CERN ROOT/RooFit GNU Makefile structure with GUI for macOS and Linux

Petr Stepanov
10 min readJan 30, 2019

Having issues with installing ROOT on your system? Check out my previous article which describes how to install ROOT from a binary distribution. To enable debugging functionality and be able to access the source refer to Compile CERN ROOT 6 from source.

It often happens that you start with a simple program ROOT tailored to perform a specific calculation or fit or whatever. It is usually a simple ROOT macro that one runs with ROOT’s interpreter.

Later on after adding more and more features the code starts stacking up and a single-file macro becomes quite messy. It is necessary to structure the program code to solve this problem. Program classes have to be separated into individual files. A certain folder structure of the project might help with the code organization. For instance, helper classes might go under ./helpers/ folder; custom RooFit PDFs will go under ./pdfs/ and so on.

Generally speaking a C++ GNU Makefile consists of two steps: individual compilation of the sources and linking the object files together. However some extra build steps are required if your C++ code takes advantage of the CERN ROOT libraries.

In this article we will create a universal GNU makefile for the CERN ROOT project that can be used on macOS and Linux systems.

Here is the diagram that represents the build steps of a program that makes use of such ROOT features like GUI, RooFit or ClassDef macros.

Specificity of building a ROOT based C++ program
  1. A list of program Class names that that require creating a Dictionary has to be specified in the file named LinkDef.h . Every program Class that inherits from RooAbsPdf or TQObject has to be in this list.
  2. A dictionary file (.dict) is created by passing the LinkDef.h file along with all the program header files (.h) to the rootcint interpreter.
  3. Shared library (.so) file needs to be compiled from the list of the source files and the dictionary.
  4. Just like for any other C++ application your sources are individually compiled into the object files.
  5. Finally the compiler links object files, shared library and other ROOT shared libraries together into an executable.

In this article we will write a GNU Makefile that takes care of the above steps. It took me quite a while to do so because the information was scattered around the Internet. I’ve also found some uncertainties in official ROOT documentation.

Let’s start from the project structure. We will use following conventional project structure:

  • /Makefile is located in the root project folder.
  • /src/ folder contains C++ source files organized in folders if necessary.
  • /build/ is a folder for binary object files generated during compilation.
  • /dist/ will contain the executable, shared library and dictionary PCM file after compilation and linking is done.
  • /src/LinkDef.h contains list of class names for dictionary generation.

1. Specifying Dictionary entries in LinkDef.h

As far as I understand there are two types of classes which class names have to be specified in the LinkDef.h file:

  • Classes that utilize ClassDef and ClassImp macros.
  • Classes with Signal/Slot mechanism inherited from TQObject .

Let’s discuss these types of classes in some more details.

Custom classes that have ClassDef and ClassImp macros

ROOT Macros ClassDef and ClassImp are used to register implementation and declaration of some static members of the class inherited from TObject.

You can read more about the functionality of these members here. Some of non-inline members generated by ClassDef do need an implementation. ROOT generates them automatically as part of the Dictionary. Without the Dictionary you will observe missing symbols for your class when linking.

For example, ClassDef and ClassImp macros are used in custom RooFit PDF classes inherited from RooAbsPdf. You can find more examples of use of ClassDef and ClassImp in official ROOT’s Users Guide.

Classes inherited from TQObject

CERN ROOT’s GUI has a special so-called Signal/Slot mechanism that allows communication between a signal (e.g. button click event) and slot (correspondent method that is called when event fires). ROOT’s class TQObject is the base class that implements this communication mechanism.

Every class name that utilizes Signal/Slot mechanism has to be specified in the LinkDef.h . Otherwise the communication won’t work.

For example, if you are writing a GUI application then your main class that inherits from TGMainFrame should be on the list.

LinkDef.h file syntax

The common structure of this file is following. First six lines are the regular header that has to be in every LinkDef.h file. You can copy-paste them from here:

Next, starting from line #9 class names that qualify for the above conditions should be listed. More information on LinkDef.h files on the official website.

2. Generating the dictionary

ROOT’s dictionary generator rootcling creates the requested app-dictionary.cxx and the app-dictionary_rdict.pcm file with the following command:

rootcling -f app-dictionary.cxx -c $(CXXFLAGS) -p Header1.h Header2.h ... LinkDef.h

Here LinkDef.h has to be the last parameter in the list of headers. According to the documentation, the *_rdict.pcm. file is needed for the dictionary functioning at runtime. It should be located in the directory where the shared library is installed in along with the compiled dictionary. More information on rootcling can be found on official website.

3. Compiling the Shared library

ROOT requires us to preform another intermediate step before we start compiling and linking our source files. We need to generate a shared library *.so file in order to be able to link the compiles sources against it later.

gcc -shared -o app-library.so $(LDFLAGS) $(CXXFLAGS) $(GLIBS) Source1.cpp Source2.cpp ...

Of course macOS users will use clang++ instead of g++. More information on shared library generation can be found here.

4. Compiling source files

Okay, this should be the most straightforward operation. We go through all the source files *.cpp and compile them separately:

gcc $(CXXFLAGS) -c Source1.cpp -o Source1.o
gcc $(CXXFLAGS) -c Source2.cpp -o Source2.o
...

5. Linking

Final step is linking all the object files against ROOT’s shared libraries and our application’s shared library that was generated previously:

gcc -o app-executable Source1.o Source2.o ... app-library.so $(GLIBS)

At this point we have a clear understanding of the main targets in our Makefile. Now let’s get familiar with the Makefile syntax.

Makefile targets

The makefile we are using in the application has a number of targets that serve different purposes:

  • The default target all builds the production version of the application. This executable is compiled with full optimizations. The search path of the Shared library in the executable is default ($DYLD_LIBRARY_PATH on macOS or $LD_LIBRARY_PATH on Linux).
  • In order to install the application we invoke install target. It copies production executable to /usr/bin. Shared library *.so and the dictionary .pcm file are copied to to the $ROOTSYS/lib .

To get familiar with the build and install of the production application version please refer to the following diagram:

Diagram of the file locations for the production version of the application.

It is important to understand the difference between the build of the production version of the executable versus the debug and release (development release for testing). Last ones are are designed for the application developers that debug and test the application in the Integrated Development Environment (IDE) of their choice.

In order to prevent interference between the debug and release versions of the application and the production build we do following:

  • The release and debug executables along with their shared library are located in the ./dist/ project child older. They are not installed in the system folders.
  • We link release and debug executables with their Shared library located in the same ./dist/ folder by specifying the relative search path ./ for it.

Please refer to the following build diagram to better understand the development targets:

Diagram showing file locations for the development version of the application build. Notice that the executable is linked to the shared library with relative path in the same folder.

To sum up, the default build target all (production) followed by the install target are used to build and install the production version of the application on the system. We will use the release and debug targets in Eclipse IDE in order to test the release and compile the binary with debug symbols respectively.

Makefile syntax

We will learn the Makefile syntax on a real example: a program SW Calculator that is published on my GitHub page. I will go line by line and comment on what’s going on. We start with defining the $CXX variable. We will use this variable to invoke the compiler.

CERN ROOT uses different compilers: on macOS it is clang++; on Linux ROOT is compiled with g++ . Here depending on the output of the uname shell command we assign the correspondent value to the $CXX variable.

Next we define the compiler flags, library search paths and the list of ROOT shared libraries names to be used on the linking stage.

Please notice that root-config —-glibs does not provide the list of all the necessary ROOT libraries required on the linking stage. Some of the library names need to be appended manually ( -lRooFit, -lRooFitCore etc).

Next we define some variables that correspond to project folder structure, file names of the dictionary, shared library and the binary executable:

Next, we use the shell’s find command to automatically create lists of header *.h and source *.cpp files. We save these lists of files in HEADERS and SOURCES variables respectively.

Notice that we exclude LinkDef.h from the HEADERS variable. We do so because the LinkDef.h has to be the last parameter in the list of the rootcling parameters when generating the dictionary. We will manually append it later.

The list of object files .o is created automatically from the Sources list by substituting the extension and the directory name.

We will declare some more variables before we start writing Makefile targets.

The dir_guard variable is a shorthand used for automatic directory creation when compiling object files. It is very convenient if your source files are organized in sub-directories.

The last two variables, namely DYNAMIC_LIBRARY_PATH and PREFIX are used by the install target to install the production executable and its shared library on the system.

Now we are done with variables declaration, and we approach with defining the Makefile targets.

For every build target — production, release and debug we set additional compiler flags and specify correspondent set of prerequisites.

The default target all is set to production; it is compiled with the highest -O3 optimization compiler flag. The executable and its shared library that are compiled in this target are need to be installed on the system with the install target later.

Executables compiled with release and debug targets have different name — EXECUTABLE_LOCAL . They will be linked with their shared libraries relatively in the same directory ./dist to avoid installing on the system. This simplifies the development process (no install needed). Also this local build will not interfere with the production version of the program that might be installed on the computer.

Not to mention that for the debug target we add the required -d compiler flag. Extra -Og flag can be added to the debug compiler flags for optimization level.

Next follows the target for creating the Dictionary .cxx and .pcm files. We remember the last prerequisite needs to be $(SRC_DIR)/LinkDef.h because it was excluded from the $(HEADERS) list previously.

In Makefile the $@ automatic variable stands for the file name of the target of the rule. Variable $^ corresponds to the names of all the prerequisites, with spaces between them. Learn more about automatic variables here.

Now we compile the shared library. Prerequisites are the dictionary .cxx file and list of sources.

The -shared flag tells the compiler to create shared object. Learn more about linking options on the official reference.

Next target defines a compilation of a single object file in the program.

First we make sure the directory to output object exists. Then simply compile the correspondent source file. The automatic variable $< corresponds to the first prerequisite.

Finally we link the executable binary against ROOT’s shared libraries and application shared library. Linking of the production executable is straightforward. We just add previously generated $(SHARED_LIBRARY) to the list of other ROOT libraries to link against:

The executable’s search path for the Shared Library will be the same as for other ROOT libraries, namely $DYLD_LIBRARY_PATH or $LD_LIBRARY_PATH.

The linking process for the development and testing release targets is a little more complex. There is no cross-platform way to to specify the relative search path of the Shared Library. On macOS we run the install_name_tool command after the regular linking. On Linux we provide the relative location of the shared library with -Wl,-rpath linker option. Find more info on the official GNU website.

After the compilation and linking is done we move the dictionary .pcm, shared library .so and shared library debug symbols .so.dSYM (generated on macOS only when compiling with debug symbols) to the ./dist/ folder. At this point the dictionary .cxx file can be removed from the filesystem:

Below goes the target to install the production executable and the shared library on the system:

On Linux there is a set of tools xdg-utils that simplify the integration of the applications into the desktop environment. We will take advantage of them to install the application icon and create the desktop launcher:

Last but not least, we will have the rest of PHONY targets in the makefile that speak for themselves and do not need additional comments.

We’ve reached the EOF of the Makefile. In order to compile and install the program with the above Makefile on macOS or Linux one has to run following code in the terminal:

make
make install

For more information please refer to the source code of one of my CERN ROOT projects on GitHub, SW Calculator. It has a complete project structure and a complete Makefile.

Summary

In this article we learned how to create a basic Makefile for a CERN ROOT program that consists of a multiple number of sources.

It all starts from specifying the list program class names — dictionary entries in the LinkDef.h file.

We figured how to generate dictionary for ROOT’s programs with GUI or custom RooFit PDFs, and compile a shared library for the project.

We learned how to compile and install the application on the macOS or Linux system. We figured how to build and link a development executable that is linked with its shared library via relative path.

In the next article I will discuss how to set up Eclipse IDE with CERN ROOT application with existing Makefile.

--

--

Petr Stepanov

Gamma-spectroscopy. Positron annihilation spectroscopy. M.S. in solid-state physics. PhD in photochemical sciences. Desktop, frontend and iOS developer.