CMake in LHCb¶
Introduction¶
CMake is one of the most used configuration and build tools for C/C++ projects. There is plenty of resources available online to learn the basics of the CMake configuration language, like
As it’s a common practice in large projects to wrap low level CMake code into high level functions, we introduced some Gaudi and LHCb helpers to simplify the writing of the build configuration of our software projects.
It is also important to understand the difference between building instructions and packaging instructions. Although CMake gives you control on both of them, it is a good practice to refrain from dictating packaging and deployment rules so that the project is more portable. A great presentation given at CppCon 2018 explains the issue in details.
Environment¶
The standard LHCb User Environment includes a recent version of CMake, as the version available on CentOS 7 is too old for our software configuration (the minimum required version is 3.15).
Project Configuration¶
The configuration of a project is divided in multiple files called
CMakeLists.txt
, one in the top directory of the project and others in the
project subdirectories, plus other modules that can be imported to have access
to useful custom functions (CMake itself provide some commands as builtin
functions written in C++ and other as importable modules written in the CMake
language).
The top level CMakeLists.txt
file contains the global configuration options
for a project. A stripped down example of a top level configuration file is
(from the project Lbcom):
1cmake_minimum_required(VERSION 3.15)
2
3project(Lbcom VERSION 32.0 LANGUAGES CXX)
4
5include(CTest)
6
7list(PREPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)
8
9# -- Dependencies
10set(WITH_Lbcom_PRIVATE_DEPENDENCIES TRUE)
11include(LbcomDependencies)
12
13# -- Subdirectories
14lhcb_add_subdirectories(
15 Associators/Associators
16 Calo/CaloAssociators
17 # ...
18)
19
20lhcb_finalize_configuration(NO_EXPORT)
Lets’s break it down to understand what it does.
- Preamble (lines 1-5)
cmake_minimum_required(VERSION 3.15)
Make sure we use a version of CMake that supports all the features needed in our configuration, and make sure that newer versions of CMake are internally configured to be compatible with the behavior we expect (see
cmake_minimum_required()
documentation)project(Lbcom VERSION 32.0 LANGUAGES CXX)
Declare project name, version, languages used and other optional details. It is mandatory that it appears as early as possible in the top level configuration file as it set some internal CMake variables (see
project()
documentation for details)include(CTest)
The
CTest
module initializes the CMake subsystem for testing.
- Dependencies (lines 7-11)
Project dependencies are collected in a separate file that is installed together with the other build artifacts to be used by downstream projects. The special variable
WITH_<Project>_PRIVATE_DEPENDENCIES
set to true is used to declare that we want to use both public and private dependencies of the current project (see Project Dependencies for details).- Subdirectories (lines 13-18)
At this point we have to tell CMake which subdirectories of the current directory we want to consider for the configuration of the project. Each of the listed subdirectories must contain a
CMakeLists.txt
file for the subdirectory configuration. The commandlhcb_add_subdirectories()
is a thin helper wrapping multiple calls to the standardadd_subdirectory()
command.The order of the subdirectories in the list is not relevant, but it’s a good practice to keep them in alphabetical order.
- Finalization (line 20)
At the end of the configuration of all the subdirectories there are a few tedious tasks that have to be performed for a regular LHCb project. The command
lhcb_finalize_configuration()
takes care of those details in a semiautomated way.
Project Dependencies¶
A typical project uses some external projects (libraries and tools) which we refer to as dependencies. There are two main category of dependencies:
- public dependencies
These are the external projects that are needed to build the current project, but also to build projects that are using our project. For example Gaudi uses external projects like Boost to simplify the internal implementations, but some of Gaudi public headers require the inclusion of Boost headers so the project LHCb, to be able to use those Gaudi headers has to implicitly depend on Boost even if it does not use it explicitly.
- private dependencies
These are the external projects that are used only internally to the project and not needed by downstream projects. In this category fall both external projects that are not appearing in public headers and tools that are used for special tasks like code generation and documentation.
The declaration of all dependencies are collected in one file called
cmake/<Project>Dependencies.cmake
which is installed with the build
artifacts. This file may look like this (from the project Rec):
1if(NOT COMMAND lhcb_find_package)
2 # Look for LHCb find_package wrapper
3 find_file(LHCbFindPackage_FILE LHCbFindPackage.cmake)
4 if(LHCbFindPackage_FILE)
5 include(${LHCbFindPackage_FILE})
6 else()
7 # if not found, use the standard find_package
8 macro(lhcb_find_package)
9 find_package(${ARGV})
10 endmacro()
11 endif()
12endif()
13
14# -- Public dependencies
15lhcb_find_package(Lbcom 33.0 REQUIRED)
16
17find_package(ROOT REQUIRED
18 MathCore
19 RIO
20 Tree
21)
22
23find_data_package(ChargedProtoANNPIDParam 1.0 REQUIRED)
24find_data_package(TMVAWeights REQUIRED)
25
26# -- Private dependencies
27if(WITH_Rec_PRIVATE_DEPENDENCIES)
28 find_package(AIDA REQUIRED)
29 find_package(Boost REQUIRED
30 container
31 filesystem
32 headers
33 iostreams
34 )
35 find_package(Eigen3 REQUIRED)
36 find_package(fmt REQUIRED)
37 find_package(GSL REQUIRED)
38 find_package(Rangev3 REQUIRED)
39 find_package(ROOT REQUIRED
40 Core
41 GenVector
42 Hist
43 Matrix
44 TMVA
45 )
46 find_package(Vc REQUIRED)
47 find_package(VDT REQUIRED)
48 find_package(XGBoost REQUIRED)
49
50 if(BUILD_TESTING)
51 find_package(Boost REQUIRED unit_test_framework)
52 endif()
53endif()
- preamble (lines 1-12)
This section tries to find the module LHCbFindPackage to load the
lhcb_find_package()
command. If not found we add a trivial implementation wrapping the standard commandfind_package()
.- public dependencies (lines 14-24)
The declaration of dependencies has the form of a list of calls to
find_package()
,lhcb_find_package()
(for LHCb projects) orfind_data_package()
(for LHCb data packages).- private dependencies (lines 26-53)
The private dependencies section is not much different from the public one expect for the fact that it is protected in a if-endif block so that it is activated only when inside the project it belongs to, thanks to the conventional variable
WITH_<Project>_PRIVATE_DEPENDENCIES
. 1
Subdirectory Configuration¶
Each subdirectory (sometimes called package) is in general different from the others, but there are conventions it is better to follow and other that are mandatory.
Layout¶
A typical subdirectory contains the mandatory file CMakeLists.txt
(described
in the next section) and some of these directories:
- include
directory for public headers
- src
local source files
- dict
files to generate ROOT dictionaries
- python
Python modules
- tests
source and configuration files for tests
Configuration file¶
The structure of the CMakeLists.txt
file of a subdirectory follows roughly this model:
1# ... copyright ...
2#[========================================[.rst:
3Group/Subdirectory
4------------------
5Lorem ipsum dolor sit amet...
6#]========================================]
7
8gaudi_add_library(Subdirectory
9 SOURCES src/A.cpp src/B.cpp
10 LINK PUBLIC Gaudi::GaudiKernel
11 PRIVATE Boost::headers
12)
13
14gaudi_add_dictionary(SubdirectoryDict
15 HEADERFILES dict/dict.h
16 SELECTION dict/selection.xml
17 LINK Subdirectory
18)
19
20gaudi_install(PYTHON)
21
22if(BUILD_TESTING)
23 gaudi_add_tests(QMTest)
24 gaudi_add_executable(unit1
25 SOURCES tests/src/unit1.cpp
26 LINK Subdirectory
27 TEST
28 )
29endif()
At the top of the file (lines 1-6) we have the copyright statement followed by a comment block documenting the subdirectory features and roles. The title of the documentation section must be the path of the subdirectory relative to the project top directory.
The first non-comment part of the file (lines 1-20) contains instructions to build the various parts of the subdirectory and instructions to install public files like Python modules or scripts.
Following the instructions for the public part of the subdirectory we have the
section for the tests. It is a good practice to enclose this part in a
if(BUILD_TESTING)-endif() block to avoid wasting build time if the tests are
not needed (see CTest
module documentation).
Common commands¶
The full list of CMake commands con be found in the CMake manual and LHCb custom commands can be found in the index.
The most common commands found in a subdirectory configuration are:
gaudi_add_library()
creates a library of common classes and functions that can be reused in other libraries and modules
gaudi_add_module()
creates a plugin library, i.e. a shared object file containing Gaudi components like algorithms and tools
gaudi_add_header_only_library()
similar to gaudi_add_library(), but all the code is in header files (so there is no real build step)
gaudi_add_executable()
creates an executable command; it is most often used with the flag
TEST
to declare that the executable being built is a unit test and should be run with the other tests by thectest
commandgaudi_add_tests()
declare tests to be run with the
ctest
command; it knows how to handle QMTest, nosetest and pytest test suitesgaudi_install()
useful to install non C++ (or non compiled) files, like Python modules, CMake modules or scripts (shell, Python, etc.)