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.
- 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
TQObjecthas to be in this list.
- A dictionary file (.dict) is created by passing the
LinkDef.hfile along with all the program header files (.h) to the
- Shared library (.so) file needs to be compiled from the list of the source files and the dictionary.
- Just like for any other C++ application your sources are individually compiled into the object files.
- 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:
/Makefileis 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.hcontains 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
- Classes that utilize
- Classes with Signal/Slot mechanism inherited from
Let’s discuss these types of classes in some more details.
Custom classes that have
ClassImp are used to register implementation and declaration of some static members of the class inherited from
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.
ClassImp macros are used in custom RooFit PDF classes inherited from
RooAbsPdf. You can find more examples of use of
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
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
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.
The makefile we are using in the application has a number of targets that serve different purposes:
- The default target
allbuilds 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_PATHon macOS or
- In order to install the application we invoke
installtarget. It copies production executable to
/usr/bin. Shared library
*.soand the dictionary
.pcmfile are copied to to the
To get familiar with the build and install of the production application version please refer to the following diagram:
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
Please refer to the following build diagram to better understand the development targets:
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.
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
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 (
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
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.
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
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 —
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
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
.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.
-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
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:
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
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.