This documentation is a documentation for Gaudi-based project maintainers.
If you are only a user or a developer, you should have a look at
GaudiCMake315Configuration
This file is not here to describe what can be done with the configuration but
rather why the configuration is like this and what you should be aware of before
trying to modify it.
This documentation is being written right after the modernization of Gaudi itself.
At that time CMake 3.15 was just released.
Introduction
In this twiki page we are going to explain in details what the configuration of Gaudi is made of.
For details about how to use CMake or how to configure a project that uses CMake,
see
GaudiCMake315Configuration.
If you need information on CMake you can either use the
official latest documentation
or the
memento in appendix.
Future work (to be updated)
- In Gaudi
- try GaudiRelease in Gaudi
- generate doxygen documentation
- add a job in GitLab CI to automate it
- set correct sonames for shared objects (libraries) depending on ABI changes. see Wikipedia
and CMake
. set_property(TARGET PROPERTY SOVERSION <x.y.z>)
would tell CMake to set the soname of the .so
file but will also build a .so.x.y.z
file and a .so
symlink that points to it (no hardcoded path, the one in the current directory is pointed)
- remove no longer needed stuff in the
ci-utils/
folder
- add a job in GitLab CI to check the formatting (with clang-format). Current error of the script is "sed: can't read cmake/GaudiProjectConfig.cmake: No such file or directory" because the file has been removed.
- From Gaudi to CVMFS
- move the folder
cmake/toolchains/
and the modules CcacheOption.cmake
and DeveloperBuildType.cmake
somewhere on CVMFS. Any project could use these toolchains and modules, not only Gaudi.
- Refactored all LHCb's Gaudi-based projects: LHCb, Lbcom, Rec, Phys, Analysis Brunel, Moore, DaVinci...
- with a script (see appendix) or by hand or anything else
Wishlist from third-party players
- From Boost
-
BoostConfig.cmake
handles Boost_USE_STATIC_LIBS
correctly * remove set(BUILD_SHARED_LIBS ON)
from GaudiDependencies.cmake
if it happens * BoostConfig.cmake
takes QUIET argument into account * BoostConfig.cmake
has a way to set the version of the python
component
- apparently only 2.7 is available at the moment (Boost 1.70) *
BoostConfig.cmake
marks Boost_DIR
and boost__DIR
as advanced
- remove all
mark_as_advanced()
for them in GaudiDependencies.cmake
if it happens
- From ROOT
-
ROOTConfig.cmake
exposes the C++ standard used to compile ROOT * in ROOT_CXX_STANDARD for example, then remove the parsing of ROOT_CXX_FLAGS
in CMakeLists.txt
at the top-level of Gaudi * ROOTConfig.cmake
displays a message when successfully found
- remove the
message()
in GaudiDependencies.cmake
if it happens
- From LCG
- when installing TBB, also install (or do not remove) the official
TBBConfig.cmake
* remove FindTBB.cmake
from Gaudi if it happens
Conventions and good practices
- In this twiki page "we" will refer to the authors (package maintainers) and "you" to the reader (developer/user). "I" refers to the first author.
- A CMake option or cached variable (may be set by the user at configure time) must be all capital. e.g.
GAUDI_USE_CPPUNIT
- Always keep in mind that a project can be built standalone or as a part of a stack of projects. Be careful when using (
CMAKE_SOURCE_DIR
, PROJECT_SOURCE_DIR
, CMAKE_CURRENT_SOURCE_DIR
, CMAKE_PROJECT_NAME
, PROJECT_NAME
...)
- If you do not know which conventions to follow when editing a file, look at another file that has the same purpose and follow the same style.
- CMake files and functions should be documented in rst. It is the format chosen by Kitware to document CMake itself. You may have a look at how CMake modules are documented
cat $(dirname $(which cmake))/../share/cmake-3.15/Modules/FeatureSummary.cmake
A web documentation is generated from this comment with sphinx
.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
#[========================================================================[.rst:
Title
-----
Description...
Sub-title
^^^^^^^^^
Stuff...
#]========================================================================]
- In files that can be used in any projects, it is a good idea to make sur that the CMake policy is recent enough.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
cmake_policy(PUSH)
cmake_policy(VERSION 3.15)
# ... code ...
cmake_policy(POP)
- In CMake tests, SRC_DIR and BIN_DIR refer to the values of PROJECT_SOURCE_DIR and PROJECT_BINARY_DIR in Gaudi. They do not have the same name because these variables will exists and have different values in the scope of the test.
- Test names should be
PackageName.TestName
- Dependencies of a target should only be direct dependencies of it. Let CMake handle the transitivity.
- Unfortunately there can only be one directory that contains QMTest tests, thus
.qmt
files cannot be configure files (.qmt.in
).
Important files and directories
build.$BINARY_TAG/
is the usual name given to the
build tree
cmake/
contains all the files related to CMake
modules/
contains all the find-module-files (
Find???.cmake
)
needed in Gaudi and Gaudi-based prjects. In principle this file
should also contain the modules (used with
include()
in
CMakeLists.txt=s). This way the =CMAKE_MODULE_PATH
can contain one less entry. In Gaudi's case, the two modules should
be moved so it is temporary. See
Future work.
In principle these find-module files could also be moved elsewhere (on CVMFS),
this way they could be used by any project (not only Gaudi-based projects).
That is what CMake does with the folder
cmake_install_folder/share/cmake-3.15/Modules/
which contains all the modules and find-module files.
If you are already worrying because
CMAKE_MODULE_PATH
is not yet
and environment variable like
CMAKE_PREFIX_PATH
, the solution is
to add at the beginning of
GaudiDependencies.cmake
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
file(TO_CMAKE_PATH "$ENV{CMAKE_MODULE_PATH}" _module_path_list)
list(APPEND CMAKE_MODULE_PATH ${_module_path_list}) # because in CMake environment variables are used after variables
FindCppUnit.cmake
a good example of a find-module file
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
# - Locate CppUnit library
# Defines:
#
# CPPUNIT_FOUND
# CPPUNIT_INCLUDE_DIR
# CPPUNIT_INCLUDE_DIRS (not cached)
# CPPUNIT_LIBRARY
# CPPUNIT_LIBRARIES (not cached)
Original documentation
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
# Imports:
#
# CppUnit::cppunit
#
# Usage of the target instead of the variables is advised
Updated documentation
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
# Find quietly if already found before
if(DEFINED CACHE{CPPUNIT_INCLUDE_DIR})
set(${CMAKE_FIND_PACKAGE_NAME}_FIND_QUIETLY YES)
endif()
This is new. The reason behind this it that once the package has been found
all the other configurations will not redo the lookup so the package will be "found"
in the same location each and every time so there is no need to show it to
the developer.
The following lines are the old content of this file.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
find_path(CPPUNIT_INCLUDE_DIR cppunit/Test.h)
find_library(CPPUNIT_LIBRARY NAMES cppunit)
We look for the include directory and the libraries we will link against.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
set(CPPUNIT_INCLUDE_DIRS ${CPPUNIT_INCLUDE_DIR})
# handle the QUIETLY and REQUIRED arguments and set CPPUNIT_FOUND to TRUE if
# all listed variables are TRUE
INCLUDE(FindPackageHandleStandardArgs)
FIND_PACKAGE_HANDLE_STANDARD_ARGS(CppUnit DEFAULT_MSG CPPUNIT_INCLUDE_DIR CPPUNIT_LIBRARY)
Using
FindPackageHandleStandardArgs
is the best thing that can be done in a
find-module file.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
mark_as_advanced(CPPUNIT_FOUND CPPUNIT_INCLUDE_DIR CPPUNIT_LIBRARY)
set(CPPUNIT_LIBRARIES ${CPPUNIT_LIBRARY} ${CMAKE_DL_LIBS})
Marking variables as advanced is also a good thing because it will not
pollute the user's options in
ccmake
or
cmake-gui
.
The end of the file is new.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
# Modernisation: create an interface target to link against
if(TARGET CppUnit::cppunit)
return()
endif()
If an imported target already exists we cannot create an other one.
It may be created by a previous call to
find_package(CppUnit)
.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
if(CPPUNIT_FOUND)
add_library(CppUnit::cppunit IMPORTED INTERFACE)
The target can either be a
SHARED IMPORTED
target with its
IMPORTED_LOCATION
set or be an
INTERFACE
target that will
have INTERFACE property for transitivity of libraries and include directories.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
target_include_directories(CppUnit::cppunit SYSTEM INTERFACE "${CPPUNIT_INCLUDE_DIRS}")
Headers are SYSTEM to silence their warnings.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
target_link_libraries(CppUnit::cppunit INTERFACE "${CPPUNIT_LIBRARIES}")
# Display the imported target for the user to know
if(NOT ${CMAKE_FIND_PACKAGE_NAME}_FIND_QUIETLY)
message(STATUS " Import target: CppUnit::cppunit")
endif()
endif()
Here tell the user which imported targets are available.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
if(COMMAND __deprecate_var_for_target)
We may or may not use this find-module file in a Gaudi-based project which means
that we may or may not have access to functions defined in
GaudiToolbox.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
foreach(v IN ITEMS CPPUNIT_INCLUDE_DIR CPPUNIT_INCLUDE_DIRS CPPUNIT_LIBRARY CPPUNIT_LIBRARIES)
variable_watch(${v} __deprecate_var_for_target)
endforeach()
endif()
This will prompt the user not to use variables because an imported target is now
provided.
FindTBB.cmake
should be removed in a near future. It was taken from the internet and then modified.
It is in the public domain.
tests/
contains all the unit tests related to CMake and Gaudi's
configuration itself.
dummyGaudiDownstreamProject/
is a "hello world!" project built
on the top of Gaudi. It tries
#include <Gaudi/PluginService.h>
which comes from the package
GaudiPluginService
and
#include <DUMMYGAUDIDOWNSTREAMPROJECT_VERSION.h>
and
#include <MainVersion.h>
which are generated.
Explanation of its
CMakeLists.txt
:
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
if(NOT TARGET Gaudi::GaudiPluginService)
find_package(Gaudi REQUIRED) # Imports all the targets and functions and find the dependencies
endif()
This is here to be able to build a downstream project on top of an
installed Gaudi or as part of a full stack.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
# Set the C++ standard
set(CMAKE_CXX_STANDARD ${GAUDI_CXX_STANDARD})
set(CMAKE_CXX_STANDARD_REQUIRED TRUE)
set(CMAKE_CXX_EXTENSIONS OFF)
This is here to use the same C++ standard as the one used to build
Gaudi.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
gaudi_add_executable(mainexe SOURCES src/main.cpp LINK Gaudi::GaudiPluginService)
Links against a target defined by Gaudi (a shared library) either
imported or built in the full stack (in this case the alias is used).
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
gaudi_generate_version_header_file() # generate DUMMYGAUDIDOWNSTREAMPROJECT_VERSION.h
gaudi_generate_version_header_file(Main) # generate MainVersion.h
target_include_directories(mainexe PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/include)
Here we check that we can use a
gaudi_*()
function and in
src/main.cpp
we check that we have access to what they define.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
###################### Simple unit tests beyond this line ######################
# Check validity of GAUDI_CXX_STANDARD
if(NOT GAUDI_CXX_STANDARD)
message(FATAL_ERROR "GAUDI_CXX_STANDARD is not set or empty.")
endif()
# Check that we have a run target
if(NOT TARGET run)
message(FATAL_ERROR "There is no run target.")
endif()
# Targets needed for gaudi_add_module()
foreach(target IN ITEMS Gaudi::listcomponents Gaudi::genconf)
# Check they are targets
if(NOT TARGET ${target})
message(FATAL_ERROR "${target} is not a target.")
endif()
# Check they exist on the disk if not build by the project
get_target_property(target_imported ${target} IMPORTED)
if(target_imported)
get_target_property(loc ${target} LOCATION)
if(NOT EXISTS "${loc}")
message(FATAL_ERROR "${target} is imported from ${loc} but the file does not exist.")
endif()
endif()
endforeach()
This
CMakeLists.txt
also contains some unit tests because in
the end it is the only purpose of this project. We test if the
C++ standard used to compile Gaudi is exported, to check that an
IMPORTED target called "run" exist (it wraps the call to the run
script), to check that executable targets needed by
gaudi_add_module()
are imported and exist at the expected location.
dummyLHCbFullStack/
contains a
CMakeLists.txt
that describe
the build of a stack composed of Gaudi and dummyGaudiDownstreamProject.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
cmake_minimum_required(VERSION 3.15)
project(dummyLHCbFullStack)
# Add the projects of the stack (from bottom to top)
add_subdirectory(${SRC_DIR} ${BIN_DIR}/dummyLHCbFullStack/Gaudi)
add_subdirectory(${SRC_DIR}/cmake/tests/dummyGaudiDownstreamProject ${BIN_DIR}/dummyLHCbFullStack/dummyGaudiDownstreamProject)
In this file, we can see the minimal stuff needed for a CMake project
configuration. There is a
cmake_minimum_required()
followed by
a
project()
and the list of the projects of the stack from the
bottom to the top (the order of the configuration). Here we use
the signature
add_subdirectory(source_dir binary_dir)
because
the source directory is given with an absolute pass because it
is not a subdirectory of the directory of the
CMakeLists.txt
.
We can notice that, the binary_dir of Gaudi is different to the
"real" binary_dir of Gaudi. Since the configuration for a full
stack differ from the configuration standalone, we use another
build tree not to mess up with the "real" build tree. The drawback
though, is that it takes way longer, because everything needs to
be recompiled (using
ccache
is advised).
dummyProject/
contains a project designed to test as much as possible
the
gaudi_*()
outside Gaudi. The goal is that if the tests defined
in this folder fail, one or more
gaudi_*()
function is bugged.
If the tests defined in this folder pass but the configuration
fails, it means that the configuration (the code in one or more
CMakeLists.txt
) does not use the functions correctly. The
architecture of the directory is standard.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
add_executable(Gaudi::listcomponents IMPORTED)
set_target_properties(Gaudi::listcomponents PROPERTIES IMPORTED_LOCATION "${CMAKE_CURRENT_SOURCE_DIR}/scripts/fakelistcomponents.sh")
add_executable(Gaudi::genconf IMPORTED)
set_target_properties(Gaudi::genconf PROPERTIES IMPORTED_LOCATION "${CMAKE_CURRENT_SOURCE_DIR}/scripts/fakegenconf.sh")
The
CMakeLists.txt
contains calls to the main
gaudi_*()
functions
(we would have wished it contain calls to all without any modification
but in the end we would have ended up rewriting another Gaudi).
Here we emulate some programs defined in Gaudi because the dummy
project is not built on top of Gaudi (and that is the purpose).
Should the arguments given to these programs (listcomponents and genconf)
change, their fake implementation would need to be updated.
testGaudiDownstream.cmake
, testGaudiInstallation.cmake
, testGaudiToolbox.cmake
are modules that add unit tests to
CTest when included. They all follow the same principle: they
have a big switch (if, elseif...) in them that will launch a
different test depending on the definition of a variable. This
enables us to have all the tests related to a given purpose declared
(with
add_test()
) and defined (the cmake code of the test) in one place.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
if(DEFINED TEST_SOMETHING)
# code of one test
elseif(DEFINED TEST_SOMETHING_ELSE)
# code of another test
else()
# declaration of the tests with add_test()
add_test(NAME cmake.test_something
COMMAND ${CMAKE_COMMAND} -D TEST_SOMETHING:BOOL=TRUE
# other options
-P ${CMAKE_CURRENT_LIST_FILE}) # this file
set_tests_properties(cmake.test_something PROPERTIES
DEPENDS cmake.test_a
FIXTURES_REQUIRED cmake.test_a
FIXTURES_SETUP cmake.test_something
LABELS CMake)
endif()
We register a test called "cmake.test_something". To run this test,
CTest must run CMake in script mode on the current file and defined
the boolean variable TEST_SOMETHING to TRUE enabling the first if
to be true, thus running its code.
DEPENDS
and
FIXTURES_REQUIRED
prevent tests to be run concurrently
and mess with the other.
FIXTURES_SETUP
will enable other tests to do the same.
LABELS
enables the use of
-L
when running CTest.
testGaudiDownstream.cmake
cmake.test_dummyGaudiDownstreamProject
tries to build and run
the dummyGaudiDownstreamProject from an installed Gaudi.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
file(REMOVE_RECURSE ${BIN_DIR}/dummyGaudiDownstreamProject)
This removes a previously built dummyGaudiDownstreamProject.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
if(CMAKE_TOOLCHAIN_FILE)
set(toolchain "-DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE}")
endif()
This is because a toolchain may or may not have been passed to the
test (the tests command uses a generator expression).
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
execute_process(COMMAND ${CMAKE_COMMAND} -E env CMAKE_PREFIX_PATH=${GAUDI_INSTALL_DIR}:$ENV{CMAKE_PREFIX_PATH}
${CMAKE_COMMAND}
-S ${CMAKE_CURRENT_LIST_DIR}/dummyGaudiDownstreamProject
-B ${BIN_DIR}/dummyGaudiDownstreamProject
-G ${CMAKE_GENERATOR}
-D CMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}
${toolchain}
RESULT_VARIABLE returned_value
COMMAND_ECHO STDOUT)
if(NOT returned_value EQUAL "0")
message(FATAL_ERROR "Configuration of the downstream project failed.")
endif()
Here we try to build the dummy project. We do not use the
run
script.
We only specify the install location of Gaudi and its build environment.
The build tree is <GaudiBuildTree>/dummyGaudiDownstreamProject.
We use the same compiler and generator as the ones used for Gaudi.
We use a toolchain if one was specified.
The configure command will be printed on stdout (can be seen if
we use CTest in verbose mode) for an easier debugging.
Finally, we test the returned value to check that the configuration
is OK.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
execute_process(COMMAND ${CMAKE_COMMAND} --build
${BIN_DIR}/dummyGaudiDownstreamProject
RESULT_VARIABLE returned_value
COMMAND_ECHO STDOUT)
if(NOT returned_value EQUAL "0")
message(FATAL_ERROR "Compilation of the downstream project failed.")
endif()
Here we build. Same thing with the returned value.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
# Try to execute
execute_process(COMMAND ${BIN_DIR}/dummyGaudiDownstreamProject/run
${BIN_DIR}/dummyGaudiDownstreamProject/mainexe
RESULT_VARIABLE returned_value
COMMAND_ECHO STDOUT)
if(NOT returned_value EQUAL "0")
message(FATAL_ERROR "Execution of the downstream project failed.")
endif()
In the end, we try to run the built software with its own
run
script. Same thing with the returned value.
cmake.test_dummyGaudiDownstreamProject_from_build_tree
reconfigure Gaudi with GAUDI_BUILD_TREE_AS_INSTALL_AREA=ON
and does not recompile Gaudi because there is no need to do so,
then tries to build and run the dummyGaudiDownstreamProject
from the build tree of Gaudi.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
# Save the cache
file(MAKE_DIRECTORY ${BIN_DIR}/bck)
file(COPY ${BIN_DIR}/CMakeCache.txt DESTINATION ${BIN_DIR}/bck)
file(COPY ${BIN_DIR}/CTestTestfile.cmake DESTINATION ${BIN_DIR}/bck)
file(COPY ${BIN_DIR}/cmake_install.cmake DESTINATION ${BIN_DIR}/bck)
file(COPY ${BIN_DIR}/GaudiConfig.cmake DESTINATION ${BIN_DIR}/bck)
if(CMAKE_TOOLCHAIN_FILE)
set(toolchain "-DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE}")
endif()
Here we save all the files that will be modified by the reconfiguration
of Gaudi. We reconfigure because we do not want to rebuilt Gaudi
from scratch for this test but we do not want to modify the
configuration of the user. These files will be restored at the
end of the test. WARNING: if the test unexpectedly end (with
Ctrl-C for instance), these files are not restored. Thus the
configuration would be a mess. You would need to reconfigure.
Same thing with the toolchain as in the previous test.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
execute_process(COMMAND ${CMAKE_COMMAND}
-D GAUDI_BUILD_TREE_AS_INSTALL_AREA=ON
-S ${SRC_DIR}
-B ${BIN_DIR}
-G ${CMAKE_GENERATOR}
-D CMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}
${toolchain}
RESULT_VARIABLE returned_value
COMMAND_ECHO STDOUT)
if(NOT returned_value EQUAL "0")
file(RENAME ${BIN_DIR}/bck/CMakeCache.txt ${BIN_DIR}/CMakeCache.txt) # revert to old cache
file(RENAME ${BIN_DIR}/bck/CTestTestfile.cmake ${BIN_DIR}/CTestTestfile.cmake)
file(RENAME ${BIN_DIR}/bck/cmake_install.cmake ${BIN_DIR}/cmake_install.cmake)
file(RENAME ${BIN_DIR}/bck/GaudiConfig.cmake ${BIN_DIR}/GaudiConfig.cmake)
message(FATAL_ERROR "Reconfiguration of Gaudi failed")
endif()
Here we reconfigure Gaudi with GAUDI_BUILD_TREE_AS_INSTALL_AREA=ON.
If the reconfiguration fails, the files are restored.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
execute_process(COMMAND ${CMAKE_COMMAND} -E env CMAKE_PREFIX_PATH=${BIN_DIR}:$ENV{CMAKE_PREFIX_PATH}
${CMAKE_COMMAND}
-S ${CMAKE_CURRENT_LIST_DIR}/dummyGaudiDownstreamProject
-B ${BIN_DIR}/dummyGaudiDownstreamProjectFromBuildTree
-G ${CMAKE_GENERATOR}
-D CMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}
${toolchain}
RESULT_VARIABLE returned_value
COMMAND_ECHO STDOUT)
if(NOT returned_value EQUAL "0")
file(RENAME ${BIN_DIR}/bck/CMakeCache.txt ${BIN_DIR}/CMakeCache.txt) # revert to old cache
file(RENAME ${BIN_DIR}/bck/CTestTestfile.cmake ${BIN_DIR}/CTestTestfile.cmake)
file(RENAME ${BIN_DIR}/bck/cmake_install.cmake ${BIN_DIR}/cmake_install.cmake)
file(RENAME ${BIN_DIR}/bck/GaudiConfig.cmake ${BIN_DIR}/GaudiConfig.cmake)
message(FATAL_ERROR "Configuration of dummyGaudiDownstreamProject using the build tree of Gaudi failed.")
endif()
Here we configure dummyGaudiDownstreamProject the same way as in the
previous test but we give the path to Gaudi build tree instead of
an install location.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
execute_process(COMMAND ${CMAKE_COMMAND} --build
${BIN_DIR}/dummyGaudiDownstreamProjectFromBuildTree
RESULT_VARIABLE returned_value
COMMAND_ECHO STDOUT)
if(NOT returned_value EQUAL "0")
file(RENAME ${BIN_DIR}/bck/CMakeCache.txt ${BIN_DIR}/CMakeCache.txt)
file(RENAME ${BIN_DIR}/bck/CTestTestfile.cmake ${BIN_DIR}/CTestTestfile.cmake)
file(RENAME ${BIN_DIR}/bck/cmake_install.cmake ${BIN_DIR}/cmake_install.cmake)
file(RENAME ${BIN_DIR}/bck/GaudiConfig.cmake ${BIN_DIR}/GaudiConfig.cmake)
message(FATAL_ERROR "Build of dummyGaudiDownstreamProject using the build tree of Gaudi failed")
endif()
# Try to execute
execute_process(COMMAND ${BIN_DIR}/dummyGaudiDownstreamProjectFromBuildTree/run
${BIN_DIR}/dummyGaudiDownstreamProjectFromBuildTree/mainexe
RESULT_VARIABLE returned_value
COMMAND_ECHO STDOUT)
# Reset cached variables
file(RENAME ${BIN_DIR}/bck/CMakeCache.txt ${BIN_DIR}/CMakeCache.txt)
file(RENAME ${BIN_DIR}/bck/CTestTestfile.cmake ${BIN_DIR}/CTestTestfile.cmake)
file(RENAME ${BIN_DIR}/bck/cmake_install.cmake ${BIN_DIR}/cmake_install.cmake)
file(RENAME ${BIN_DIR}/bck/GaudiConfig.cmake ${BIN_DIR}/GaudiConfig.cmake)
if(NOT returned_value EQUAL "0")
message(FATAL_ERROR "Execution of dummyGaudiDownstreamProject using the build tree of Gaudi failed.")
endif()
Here we build and try to execute. Same thing as in the previous test
and same thing about file restoration.
cmake.test_dummyGaudiDownstreamProject_full_stack
tries to build and run the project dummyLHCbFullStack.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
if(CMAKE_TOOLCHAIN_FILE)
set(toolchain "-DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE}")
endif()
Same thing with the toolchain.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
execute_process(COMMAND ${CMAKE_COMMAND}
-D SRC_DIR=${SRC_DIR}
-D BIN_DIR=${BIN_DIR}
-S ${CMAKE_CURRENT_LIST_DIR}/dummyLHCbFullStack
-B ${BIN_DIR}/dummyLHCbFullStack
-G ${CMAKE_GENERATOR}
-D CMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}
${toolchain}
RESULT_VARIABLE returned_value
COMMAND_ECHO STDOUT)
if(NOT returned_value EQUAL "0")
message(FATAL_ERROR "Configuration of dummyLHCbFullStack failed")
endif()
Here instead of configuring dummyGaudiDownstreamProject, we
configure dummyLHCbFullStack that will configure both Gaudi
and dummyGaudiDownstreamProject.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
execute_process(COMMAND ${CMAKE_COMMAND} --build
${BIN_DIR}/dummyLHCbFullStack
RESULT_VARIABLE returned_value
COMMAND_ECHO STDOUT)
if(NOT returned_value EQUAL "0")
message(FATAL_ERROR "Build of dummyLHCbFullStack failed")
endif()
# Try to execute
execute_process(COMMAND ${BIN_DIR}/dummyLHCbFullStack/run
${BIN_DIR}/dummyLHCbFullStack/dummyGaudiDownstreamProject/mainexe
RESULT_VARIABLE returned_value
COMMAND_ECHO STDOUT)
if(NOT returned_value EQUAL "0")
message(FATAL_ERROR "Execution of dummyLHCbFullStack failed.")
endif()
Like in the other tests, we try to build and run the software.
This test is long because it does not use the build tree of Gaudi
not to mess with it.
In the end, tests defined in this file are pretty useless because
the dummyGaudiDownstreamProject is a dummy project and does not
really use features from Gaudi. Trying to build LHCb is a better test.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
add_test(NAME cmake.test_dummyGaudiDownstreamProject
COMMAND ${CMAKE_COMMAND} -D TEST_GAUDI_DOWNSTREAM:BOOL=TRUE
-D GAUDI_INSTALL_DIR=${CMAKE_CURRENT_BINARY_DIR}/GaudiTestInstall
-D BIN_DIR=${CMAKE_CURRENT_BINARY_DIR}
-D CMAKE_GENERATOR=${CMAKE_GENERATOR}
-D CMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}
$<$<BOOL:CMAKE_TOOLCHAIN_FILE>:-DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE}>
-P ${CMAKE_CURRENT_LIST_FILE})
set_tests_properties(cmake.test_dummyGaudiDownstreamProject PROPERTIES
DEPENDS cmake.test_gaudi_install
FIXTURES_REQUIRED cmake.test_gaudi_install
FIXTURES_SETUP cmake.test_dummyGaudiDownstreamProject
LABELS CMake)
# Disable this test if did not install Gaudi
if(GAUDI_BUILD_TREE_AS_INSTALL_AREA)
set_tests_properties(cmake.test_dummyGaudiDownstreamProject
PROPERTIES DISABLED TRUE)
endif()
This test is disabled if we build Gaudi with GAUDI_BUILD_TREE_AS_INSTALL_AREA
because we cannot install without reconfiguring Gaudi.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
# Test usability from the build tree
add_test(NAME cmake.test_dummyGaudiDownstreamProject_from_build_tree
COMMAND ${CMAKE_COMMAND} -D TEST_GAUDI_DOWNSTREAM_FROM_BUILD:BOOL=TRUE
-D SRC_DIR=${CMAKE_CURRENT_SOURCE_DIR}
-D BIN_DIR=${CMAKE_CURRENT_BINARY_DIR}
-D CMAKE_GENERATOR=${CMAKE_GENERATOR}
-D CMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}
$<$<BOOL:CMAKE_TOOLCHAIN_FILE>:-DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE}>
-P ${CMAKE_CURRENT_LIST_FILE})
set_tests_properties(cmake.test_dummyGaudiDownstreamProject_from_build_tree PROPERTIES
DEPENDS "cmake.test_gaudi_install;cmake.test_gaudi_install_package" # not to mess up the cache
FIXTURES_SETUP cmake.test_dummyGaudiDownstreamProject_from_build_tree
LABELS CMake)
# Test usability as part of a stack
add_test(NAME cmake.test_dummyGaudiDownstreamProject_full_stack
COMMAND ${CMAKE_COMMAND} -D TEST_GAUDI_DOWNSTREAM_FULL_STACK:BOOL=TRUE
-D SRC_DIR=${CMAKE_CURRENT_SOURCE_DIR}
-D BIN_DIR=${CMAKE_CURRENT_BINARY_DIR}
-D CMAKE_GENERATOR=${CMAKE_GENERATOR}
-D CMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}
$<$<BOOL:CMAKE_TOOLCHAIN_FILE>:-DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE}>
-P ${CMAKE_CURRENT_LIST_FILE})
set_tests_properties(cmake.test_dummyGaudiDownstreamProject_full_stack PROPERTIES
DEPENDS cmake.test_dummyGaudiDownstreamProject_from_build_tree
FIXTURES_SETUP cmake.test_dummyGaudiDownstreamProject_full_stack
LABELS CMake)
This is how the tests are run. They follow the conventions defined before.
$<$:-DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE}>
is a generator expression to pass a toolchain file to the test if one
was given to the configuration.
testGaudiInstallation.cmake
cmake.test_gaudi_install
tries to install Gaudi in
${CMAKE_CURRENT_BINARY_DIR}/GaudiTestInstall
,
removing any previous installation if needed.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
execute_process(COMMAND ${CMAKE_COMMAND}
--install ${BIN_DIR}
--prefix ${GAUDI_INSTALL_DIR}
RESULT_VARIABLE returned_value
COMMAND_ECHO STDOUT)
if(NOT returned_value EQUAL "0")
message(FATAL_ERROR "Installation of Gaudi failed")
endif()
Here we use
cmake --install
which is a new feature of cmake 3.15
The installation command is printed on stdout if CTest is in verbose
mode.We check that the result is OK.
cmake.test_gaudi_install_check
checks that some special
files are installed. It checks at least one script per
scripts/
directory, one header per
include/
directory, one python
module per
python/
directory (those folders are installed at
once with
gaudi_install()
so if one file is missing it probably
means that someone forgot to install the directory) one shared
object, one
.components
, one
.confdb
, one
.rootmap
(these
are generated automatically by
gaudi_add_module()
, if one of them
is missing, the function has probably a problem), GAUDI_VERSION.h...
It would be nice if this test could be maintained to still fit this
description and thus validate the installation.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
if(NOT EXISTS ${GAUDI_INSTALL_DIR}/include/GaudiHive/HiveSlimEventLoopMgr.h)
message(FATAL_ERROR "Include directory not installed correctly,"
" HiveSlimEventLoopMgr.h not found in include/GaudiHive/")
endif()
This test is just a lot of
if this file does not exist, error.
cmake.test_gaudi_install_package
tries to remove the old packages
of Gaudi before trying to package Gaudi according to its CPack settings.
This test is disabled if Gaudi can be used from the build tree.
testGaudiToolbox.cmake
contains tests to build, run, test and
install dummyProject and check that
GaudiToolbox.cmake behave as
expected. Here, instead of defining huge tests that do all the steps
to carry out an action, the whole file is about working on dummyProject.
Each test is a step in the configuration.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
add_test(NAME cmake.test_dummyProject_clean
COMMAND rm -rf ${CMAKE_CURRENT_BINARY_DIR}/dummyProjectBinaryDir)
This test just clean the build of old tests.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
add_test(NAME cmake.test_dummyProject_configure
COMMAND ${CMAKE_COMMAND}
-DCMAKE_INSTALL_PREFIX=${CMAKE_CURRENT_BINARY_DIR}/dummyProjectBinaryDir/install
-DBIN_DIR=${CMAKE_CURRENT_BINARY_DIR}
-S ${CMAKE_CURRENT_LIST_DIR}/dummyProject
-B ${CMAKE_CURRENT_BINARY_DIR}/dummyProjectBinaryDir
-G ${CMAKE_GENERATOR}
-D CMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}
$<$<BOOL:CMAKE_TOOLCHAIN_FILE>:-DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE}>)
# ...
add_test(NAME cmake.test_dummyProject_build
COMMAND ${CMAKE_COMMAND} --build ${CMAKE_CURRENT_BINARY_DIR}/dummyProjectBinaryDir)
# ...
add_test(NAME cmake.test_dummyProject_test
COMMAND ${CMAKE_COMMAND} --build ${CMAKE_CURRENT_BINARY_DIR}/dummyProjectBinaryDir
--target test)
# ...
add_test(NAME cmake.test_dummyProject_run
COMMAND ${CMAKE_COMMAND} -DTEST_EXECTUTE_BINARY_DIR:BOOL=TRUE
-DBIN_DIR:PATH=${CMAKE_CURRENT_BINARY_DIR}
-P ${CMAKE_CURRENT_LIST_FILE})
Here, we configure, build, run the tests of the project and run a binary file.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
add_test(NAME cmake.test_dummyProject_install
COMMAND ${CMAKE_COMMAND}
--build ${CMAKE_CURRENT_BINARY_DIR}/dummyProjectBinaryDir
--target install)
# ...
add_test(NAME cmake.test_dummyProject_install_checks
COMMAND ${CMAKE_COMMAND} -DTEST_INSTALLATION_LAYOUT:BOOL=TRUE
-DBIN_DIR:PATH=${CMAKE_CURRENT_BINARY_DIR}
-DINSTALL_DIR=dummyProjectBinaryDir/install
-P ${CMAKE_CURRENT_LIST_FILE})
Finally, we install it and test the installation
toolchains/
contains some toolchain files that can be used to configure
Gaudi. This folder should be moved somewhere on CVMFS.
fragments/
contains factored out code that can be used in any toolchain file
(
gcc8.cmake
set up the compiler to be gcc8 and
dependencies.cmake
fill the environment variables with the software releases on CVMFS).
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
set(_prefix /cvmfs/sft.cern.ch/lcg/releases)
# Set the compiler
set(CMAKE_CXX_COMPILER ${_prefix}/gcc/8.2.0-3fa06/x86_64-centos7/bin/g++)
This is the absolute path to the compiler.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
set(ENV{PATH} "${_prefix}/gcc/8.2.0-3fa06/x86_64-centos7/bin:${_prefix}/binutils/2.30/x86_64-centos7/bin:/usr/bin")
Here we set
PATH
to the path to the compiler and the path to
ar
,
as
,
ranlib
,
ld
...
We also add
/usr/bin
because we need standard command like
chmod
.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
list(APPEND CMAKE_INCLUDE_PATH ${_prefix}/gcc/8.2.0-3fa06/x86_64-centos7/include/c++/8.2.0
${_prefix}/gcc/8.2.0-3fa06/x86_64-centos7/include/c++/8.2.0/x86_64-pc-linux-gnu)
set(ENV{ROOT_INCLUDE_PATH} "${_prefix}/gcc/8.2.0-3fa06/x86_64-centos7/include/c++/8.2.0:${_prefix}/gcc/8.2.0-3fa06/x86_64-centos7/include/c++/8.2.0/x86_64-pc-linux-gnu")
include_directories(BEFORE SYSTEM ${_prefix}/gcc/8.2.0-3fa06/x86_64-centos7/include/c++/8.2.0
${_prefix}/gcc/8.2.0-3fa06/x86_64-centos7/include/c++/8.2.0/x86_64-pc-linux-gnu)
Here we set everything for the headers.
First we append to
CMAKE_INCLUDE_PATH
the include directories of the standard library.
This variable is used by
find_path()
and may be specified through the command line
that is why we do not override it with
set()
Then we set
ROOT_INCLUDE_PATH
for dictionaries to be able to use the standard library.
Then we add to every target of the build the include directories of the standard headers.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
execute_process(COMMAND ${CMAKE_CXX_COMPILER} -print-file-name=libstdc++.so OUTPUT_VARIABLE stdlib_path)
get_filename_component(stdlib_path ${stdlib_path} REALPATH)
get_filename_component(stdlibdir ${stdlib_path} DIRECTORY)
list(APPEND CMAKE_LIBRARY_PATH ${stdlibdir})
set(ENV{LD_LIBRARY_PATH} "${stdlibdir}")
link_directories(BEFORE ${stdlibdir})
# FIXME: On MacOS it must be needed to add to CMAKE_LIBRARY_PATH the path to Foundation
Finally, we take care of the standard library by first asking the compiler where
the libraries are then resolving potential symlinks then appending their location
to
CMAKE_LIBRARY_PATH
(used by
find_library()
).
Then we set
LD_LIBRARY_PATH
to this path and add this directory to the
library directories of every target.
I have never compiled Gaudi on
MacOS and
MacOS requires the Foundation framework
but I do not know what to do with it.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
# This file contains what is needed to setup the dependencies
# NB: path to Intel VTune Amplifier should be provided externally (by setting
# the environment variable CMAKE_PREFIX_PATH or -DCMAKE_PREFIX_PATH at configure time)
There could be licensing issues otherwise.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
set(_prefix /cvmfs/sft.cern.ch/lcg/releases)
file(TO_CMAKE_PATH "$ENV{PATH}" path)
file(TO_CMAKE_PATH "$ENV{LD_LIBRARY_PATH}" ld_library_path)
file(TO_CMAKE_PATH "$ENV{ROOT_INCLUDE_PATH}" root_include_path)
set(pythonpath) # empty the PYTHONPATH
Here we convert PATHs environment variables to CMake lists to be able to
use
list()
.
PATH
,
LD_LIBRARY_PATH
and
ROOT_INCLUDE_PATH
are set with the compiler
but PYTHONPATH is not, that is why we start with an empty one.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
# Set the paths to the dependencies
file(GLOB libs LIST_DIRECTORIES true "${_prefix}/LCG_${LCG_VERSION}/*/*/${BINARY_TAG}") # */* = name/version
list(FILTER libs EXCLUDE REGEX "^${_prefix}.*(ninja|Gaudi)") # do not use ninja or Gaudi from there
We take everything from CVMFS except ninja and Gaudi that would not be suitable.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
foreach(lib IN LISTS libs)
list(APPEND CMAKE_PREFIX_PATH ${lib})
Each folder can be browse by
find_package()
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
if(EXISTS ${lib}/bin)
list(PREPEND path ${lib}/bin)
endif()
if(EXISTS ${lib}/lib)
file(GLOB inner_files "${lib}/lib/*.so")
if(inner_files)
list(PREPEND ld_library_path ${lib}/lib)
endif()
file(GLOB inner_files "${lib}/lib/*.py")
if(inner_files)
list(PREPEND pythonpath ${lib}/lib)
endif()
endif()
if(EXISTS ${lib}/lib64)
file(GLOB inner_files "${lib}/lib64/*.so")
if(inner_files)
list(PREPEND ld_library_path ${lib}/lib64)
endif()
file(GLOB inner_files "${lib}/lib64/*.py")
if(inner_files)
list(PREPEND pythonpath ${lib}/lib64)
endif()
endif()
if(EXISTS ${lib}/include)
list(PREPEND root_include_path ${lib}/include)
endif()
We add path to executable, libraries and include directories to corresponding
PATHs variables.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
endforeach()
# file(TO_NATIVE_PATH) does not transform ; to :
string(REPLACE ";" ":" path "${path}")
set(ENV{PATH} "${path}")
string(REPLACE ";" ":" ld_library_path "${ld_library_path}")
set(ENV{LD_LIBRARY_PATH} "${ld_library_path}")
string(REPLACE ";" ":" pythonpath "${pythonpath}")
set(ENV{PYTHONPATH} "${pythonpath}")
string(REPLACE ";" ":" root_include_path "${root_include_path}")
set(ENV{ROOT_INCLUDE_PATH} "${root_include_path}")
We set environment variables.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
# PYTHONHOME needed by gdb
list(FILTER libs INCLUDE REGEX "${_prefix}/LCG_${LCG_VERSION}/Python/[^/]+/${BINARY_TAG}")
list(GET libs 0 pythonhome)
set(ENV{PYTHONHOME} "${pythonhome}")
gdb
need
PYTHONHOME
to know where python files are located.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
list(APPEND CMAKE_PROGRAM_PATH ${_prefix}/nose/1.3.7-fa22b/${BINARY_TAG}/bin
${_prefix}/ccache/3.7.1-7651f/${BINARY_TAG}/bin)
Finally, we add paths to
nosetests
and
ccache
to be found by
find_program()
.
$BINARY_TAG.cmake
are toolchain files that can be used directly or symlinked.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
set(LCG_VERSION 96 CACHE STRING "The version of LCG the build is based on")
set(BINARY_TAG x86_64-centos7-gcc8-dbg CACHE STRING "Platform used to compile")
set(CMAKE_BUILD_TYPE Developer CACHE STRING "The build type used to build")
set(CMAKE_USE_CCACHE ON CACHE BOOL "ccache enabled by the toolchain")
We set here a bunch of cached variables.
LCG_VERSION
is pointless for the configuration but it may help the user.
BINARY_TAG
describe the platform.
CMAKE_BUILD_TYPE
set the build type according to
BINARY_TAG
.
CMAKE_USE_CCACHE
is set to ON by default if we use a toolchain because
if we use a toolchain then
ccache
is available so why not use it.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
# do not use directly CMAKE_CURRENT_LIST_DIR because it does not follow symlinks
get_filename_component(realfile ${CMAKE_CURRENT_LIST_FILE} REALPATH)
get_filename_component(CURRENT_DIR ${realfile} DIRECTORY)
Since this file will probably be symlinked we need to resolve the symlink
instead of using
CMAKE_CURRENT_LIST_DIR
.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
include(${CURRENT_DIR}/fragments/gcc8.cmake)
include(${CURRENT_DIR}/fragments/dependencies.cmake)
We first include the compiler that will set
PATH
,
LD_LIBRARY_PATH
and
ROOT_INCLUDE_PATH
. Then we include the dependencies that will append to
these environment variable and set
PYTHONPATH
.
CcacheOption.cmake
is a module that should be moved to CVMFS because
any project could use it. It adds the option
CMAKE_USE_CCACHE
(OFF by default) to
the project. (See
This twiki page)
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
include_guard() # can only be included once per project
option(CMAKE_USE_CCACHE "Enable/disable ccache for C and C++" OFF) # add the option
if(CMAKE_USE_CCACHE)
set(_ccache_dipslay_message FALSE)
if(NOT CCACHE_PROGRAM) # if ccache has not been found yet
set(_ccache_dipslay_message TRUE) # will display a message
endif()
find_program(CCACHE_PROGRAM ccache) # look for ccache
mark_as_advanced(CCACHE_PROGRAM)
if(CCACHE_PROGRAM)
foreach(lang IN ITEMS C CXX)
if(NOT CCACHE_PROGRAM IN_LIST CMAKE_${lang}_COMPILER_LAUNCHER)
list(APPEND CMAKE_${lang}_COMPILER_LAUNCHER ${CCACHE_PROGRAM})
endif()
endforeach()
if(_ccache_dipslay_message)
message(STATUS "Found ccache: ${CCACHE_PROGRAM}")
message(STATUS " Enabled ccache for C and C++")
endif()
else()
message(FATAL_ERROR "ccache was not found! Check your CMAKE_PREFIX_PATH")
endif()
endif()
It can be included once per project thanks to
include_guard()
.
option( )
declares the option.
If the user has enabled the option (
if(CMAKE_USE_CCACHE)
) we will try
to use
ccache
.
if(NOT CCACHE_PROGRAM)
is here because we want to display
a message only the first time
ccache
is found (like any other dependency
see the description of
GaudiDependencies.cmake
).
Then we try to find
ccache
(
find_program(CCACHE_PROGRAM ccache)
)
We mark it as advanced because it is something the user should not modify
by hand unless he really knows what he is doing (either changing the program
used without any checks or clearing the value to force CMake to redo the lookup).
In any case, if the option is enabled and
ccache
is not found an
error occurs and a message is displayed.
DeveloperBuildType.cmake
is a module that should be moved to CVMFS because
any project could use it.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
include_guard(GLOBAL) # can only be included once per configuration
Since this file mainly sets default values of cached variables it needs
to be included only once (once per
CMakeCache.txt
).
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
set(_flags "-g -Wall -Wextra -pedantic -Wnon-virtual-dtor -Werror=return-type -Wwrite-strings -Wpointer-arith -Woverloaded-virtual")
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") # only for gcc because this flag is not supported by clang yet
set(_flags "${_flags} -Wsuggest-override")
endif()
# WARNING: The default value of a cached variable cannot be modified after it has been set
set(CMAKE_CXX_FLAGS_${_CONFIG} "${_flags}"
CACHE INTERNAL "Compile flags for ${_config} build type")
Here, we cannot set the value of the cached variable then append a "-W"
because we are, in fact, setting the default value of the cached variable.
The only possibility to change it afterward is to use
FORCE
at the end
but we do not want to do so because it would also override a value set by
the user (either on the command line or with
ccmake
or ...).
This build type is a Debug+Warning build type. It has
-g
and lots of
-W...
.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
foreach(bin_type IN ITEMS EXE STATIC SHARED MODULE)
set(CMAKE_${bin_type}_LINKER_FLAGS_${_CONFIG} "-Wl,--no-undefined -Wl,-z,max-page-size=0x1000"
CACHE INTERNAL "Linker flags for ${bin_type} with ${_config} build type")
endforeach()
We set the linker flags for each type of target we may compile.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
if(CMAKE_CONFIGURATION_TYPES)
list(APPEND CMAKE_CONFIGURATION_TYPES ${_config})
set(CMAKE_CONFIGURATION_TYPES ${CMAKE_CONFIGURATION_TYPES} PARENT_SCOPE) # if build full stack
endif()
This tells multi-config generators (IDEs) that there is one more build
type they can add to the list of possible build type.
We set the value in the parent scope because, if we build a stack of
project, the first project (namely Gaudi) will be able to include this
module but not the others. So we propagate the modification at the stack level.
extract_qmtest_metadata.py
is a python2 script to generate cmake declaration
of tests based on
.qms
folder containing
.qmt
files. This script is
used in the function
gaudi_add_tests(QMTest)
.
GaudiConfig.cmake.in
is the template of Gaudi's config-file. The same file
is used to generate the config-file for an installed Gaudi and an
exported Gaudi (used from the build tree).
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
@PACKAGE_INIT@
Because we will use
PACKAGE_
variables and the
set_and_check()
macro.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
# Set the C++ standard used to build Gaudi
set(GAUDI_CXX_STANDARD @GAUDI_CXX_STANDARD@ CACHE INTERNAL "Version of C++ used to compile Gaudi")
set(GAUDI_BUILD_TREE_AS_INSTALL_AREA @GAUDI_BUILD_TREE_AS_INSTALL_AREA@)
We export some variables. The C++ standard used as a cached variable because
it will be useful in downstream projects.
GAUDI_BUILD_TREE_AS_INSTALL_AREA
because it makes the rest of the file easier to read after the configuration.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
set_and_check(_gaudi_config_files_location @_gaudi_config_files_location@)
list(PREPEND CMAKE_MODULE_PATH ${_gaudi_config_files_location}/modules) # Find*.cmake
list(PREPEND CMAKE_MODULE_PATH ${_gaudi_config_files_location}) # other modules
_gaudi_config_files_location
comes from the
CMakeLists.txt
in Gaudi.
set_and_check()
will make sure that the directory exists when this file
will be processed during the configuration of a downstream project.
CMAKE_MODULE_PATH
is set to enable downstream project to include module
with their names rather than their locations (e.g.
include(DeveloperBuildType)
).
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
if(NOT GAUDI_BUILD_TREE_AS_INSTALL_AREA)
# Set environment variables (so that we only need the CMAKE_PREFIX_PATH to use Gaudi)
set(ENV{PATH} "$ENV{PATH}:@PACKAGE_CMAKE_INSTALL_BINDIR@")
set(ENV{LD_LIBRARY_PATH} "$ENV{LD_LIBRARY_PATH}:@PACKAGE_CMAKE_INSTALL_LIBDIR@:@PACKAGE_GAUDI_INSTALL_PLUGINDIR@")
set(ENV{ROOT_INCLUDE_PATH} "$ENV{ROOT_INCLUDE_PATH}:@PACKAGE_CMAKE_INSTALL_INCLUDEDIR@")
set(ENV{PYTHONPATH} "$ENV{PYTHONPATH}:@PACKAGE_GAUDI_INSTALL_PYTHONDIR@")
# NB: it needs to be done before including GaudiToolbox
endif()
If Gaudi has been installed, we set the environment of the downstream project
by adding the location of Gaudi's executable, libraries, plugins, headers and
python modules to the right environment variables.
Consequently, having
CMAKE_PREFIX_PATH
that points to the install prefix
of Gaudi is enough to by able to use Gaudi with CMake.
The environment variables needs to be set before the inclusion of
GaudiToolbox.cmake
because
GaudiToolbox.cmake
set up the
run
script
and the
run
script uses the value of environment variables at configure time
when it is processed.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
if(NOT GAUDI_NO_TOOLBOX)
# Import the functions
include(GaudiToolbox)
endif()
For some reasons the downstream project may not want to import
GaudiToolbox
so it can
set(GAUDI_NO_TOOLBOX TRUE)
before
find_package(Gaudi)
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
# Enable optional dependencies
@GAUDI_OPTIONAL_DEPENDENCIES@
This is replaced at generation time by severals
set(GAUDI_USE_??? ON|OFF)
These variables are the options available in Gaudi at configure time to
enable or disable optional dependencies. They are no longer options when
Gaudi is imported because if a library has been used to build Gaudi, it
needs to be available to use Gaudi in downstream projects.
This needs to be done before including
GaudiDependencies.cmake
that will do the lookup based on these variables.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
if(@PROJECT_NAME@_FIND_QUIETLY)
set(GAUDI_DEPENDENCIES_FIND_QUIETLY TRUE)
endif()
include(GaudiDependencies)
Now we look for Gaudi's third party dependencies according the aforementioned
variables. If the
QUIET
argument was given to
find_package(Gaudi)
we
forward this argument to the other dependencies.
We handle the lookup in this file because in CMake's philosophy the transitivity
should by handled by CMake. Consequently, we do not want the downstream
project to do the lookup because Gaudi can do it. Downstream projects should
only look for their direct dependencies.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake")
Here we include the file generated either by
install(EXPORT)
(for
relocatable packages) or by
export(EXPORT)
(for non-relocatable packages).
This file defines all the imported targets that downstream projects may
use from Gaudi.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
if(GAUDI_BUILD_TREE_AS_INSTALL_AREA AND NOT GAUDI_NO_TOOLBOX)
@_set_extract_qmtest_metadata@
# Add paths of Gaudi's run script content to the run script downstream
file(STRINGS "${CMAKE_CURRENT_LIST_DIR}/gaudienv.sh" __gaudi_exported_paths REGEX "^export .*PATH=\".+\"$")
list(TRANSFORM __gaudi_exported_paths REPLACE "^export (.*PATH)=\"(.+)\"$" "\\1=\\2")
foreach(exported_path IN LISTS __gaudi_exported_paths)
string(REPLACE "=" ";" path_value ${exported_path})
list(POP_FRONT path_value path_name)
string(TOLOWER ${path_name} path_name)
file(TO_CMAKE_PATH "${path_value}" path_value)
set_property(TARGET target_runtime_paths APPEND
PROPERTY runtime_${path_name} ${path_value})
endforeach()
# NB: it needs to be done after including GaudiToolbox
endif()
If we use Gaudi from its build tree and we import
GaudiToolbox...
_set_extract_qmtest_metadata
used to be
set(extract_qmtest_metadata @extract_qmtest_metadata
@)
but this way it would have wrote in the config-file a path that is in
the source tree. This should not be visible after installation so this trick.
Note that this whole block of code has nothing to do in the config-file
after installation but it is easier for us and does not pollute that much
the top-level
CMakeLists.txt
of Gaudi.
Then we parse the content of
gaudienv.sh
which is the file that contains
all the runtime environment generated by the configuration for the
run
script.
The content is added to the runtime environment of the downstream project
with target_runtime_paths defined in
GaudiToolbox (that is the reason
why this needs to be done after including
GaudiToolbox).
This is needed because we do not install Gaudi thus we do not use
the previous block of code to set up the environment variables.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
if(NOT @PROJECT_NAME@_FIND_QUIETLY)
message(STATUS "Found @PROJECT_NAME@: ${CMAKE_CURRENT_LIST_DIR} (found version @PROJECT_VERSION@)")
endif()
We are not in a find-module-file and we do not use
FindPackageHandleStandardArgs
but it does not mean that we cannot tell Gaudi has been found (only if not in
QUIET
mode.)
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
mark_as_advanced(@PROJECT_NAME@_DIR)
Hide the location of this file to non-advanced users.
GaudiDependencies.cmake
contains the dependency lookup. It is
in a separate file because it can be used at configure time to
build Gaudi or in
GaudiConfig.cmake
by other projects.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
set(__quiet)
if(GAUDI_DEPENDENCIES_FIND_QUIETLY)
set(__quiet QUIET)
endif()
To pass
QUIET
or nothing to each call to
find_package()
we have a
variable that will be expanded to
QUIET
or nothing (
${__quiet}
).
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
include(FeatureSummary)
set(FeatureSummary_REQUIRED_PKG_TYPES RECOMMENDED REQUIRED)
FeatureSummary is a built-in module of CMake that especially defines
feature_summary()
(see at the end of the file).
Here we will use
REQUIRED
for required dependencies and
RECOMMENDED
for optional dependencies. Both are required because we decided that
if
GAUDI_USE_SOMETHING
is TRUE then you explicitly want to use something
so it means it is required.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
set(Boost_USE_STATIC_LIBS OFF)
set(OLD_BUILD_SHARED_LIBS ${BUILD_SHARED_LIBS}) # FIXME: the day BoostConfig.cmake handles Boost_USE_STATIC_LIBS correctly
set(BUILD_SHARED_LIBS ON)
find_package(Boost 1.70 ${__quiet} CONFIG COMPONENTS system filesystem regex
thread python unit_test_framework program_options log log_setup graph)
set_package_properties(Boost PROPERTIES TYPE REQUIRED)
set(BUILD_SHARED_LIBS ${OLD_BUILD_SHARED_LIBS})
mark_as_advanced(Boost_DIR) # FIXME: the day Boost correctly marks as advanced its variables
foreach(component IN ITEMS system filesystem regex thread python unit_test_framework
program_options log log_setup graph atomic chrono
date_time headers)
mark_as_advanced(boost_${component}_DIR)
endforeach()
This is Boost crap. The reason behind all this is that before Boost 1.70,
Kitware was maintaining a find-module-file for Boost. This
FindBoost.cmake
defined variables like Boost_USE_STATIC_LIBS to tell if we want static or
shared libraries. Since Boost 1.70, another third party project, namely "Boost CMake",
provides a config-file for Boost. Unfortunately it does not have the same
interface and is still under development. To be able to have this config-file
import shared libraries, we need to set
BUILD_SHARED_LIBS
to
ON
.
Furthermore, it does not provide a way of asking for
Boost::python36
.
The old
FindBoost.cmake
had 2 components, namely
Boost::python27
and
Boost::python36
. Now we only have
Boost::python
. If it were to
change, make sure to update all
CMakeLists.txt
that may need it.
Why do we anyway use this config-file? Because there is a rule that
says when a config-file is provided by the editor of a project, all
the find-module-files maintained in random projects slowly die.
Moreover, this config-file does not mark as advanced all the
_DIR
variables generated automatically by
find_package()
. Notice that
the list of components we require is not the full list of available
components.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
set(Python_FIND_STRATEGY VERSION) # Find latest version available
find_package(Python 2.7.15 ${__quiet} COMPONENTS Interpreter Development)
set_package_properties(Python PROPERTIES TYPE REQUIRED)
Python is almost simple. We require at least 2.7.15. The validate
Python_FIND_STRATEGY
is set to version to have
find_package()
look for the highest version it can find instead of the first one
found. Before CMake 3.15 it was the default.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
find_package(ROOT 6.18 ${__quiet} CONFIG COMPONENTS Core RIO Hist Thread Matrix
MathCore Net XMLIO Tree TreePlayer Graf3d Graf Gpad)
mark_as_advanced(ROOT_DIR ROOT_genmap_CMD ROOT_rootdraw_CMD)
set_package_properties(ROOT PROPERTIES TYPE REQUIRED)
# FIXME: the day ROOTConfig.cmake displays at least that it was found remove these lines
if(NOT GAUDI_DEPENDENCIES_FIND_QUIETLY)
message(STATUS "Found ROOT: ${ROOT_DIR} (found version ${ROOT_VERSION})")
endif()
Like for Boost, we mark some stuff as advanced and we display a message
because
ROOTConfig.cmake
never prints anything.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
# FIXME: if you are using the normal version of TBB, it has a config file so
# remove cmake/FindTBB.cmake and add CONFIG to the next line.
find_package(TBB 2019.0.11007.2 ${__quiet})
set_package_properties(TBB PROPERTIES TYPE REQUIRED)
The comment says it all.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
set(_gaudi_ZLIB_MIN_VERSION 1.2.11)
foreach(dep IN ITEMS UUID Threads ZLIB Rangev3 cppgsl)
find_package(${dep} ${_gaudi_${dep}_MIN_VERSION} ${__quiet})
set_package_properties(${dep} PROPERTIES TYPE REQUIRED)
endforeach()
Here we factored out the lookup for little dependencies.
For each required dependency, we call
find_package()
and then
we call
set_package_properties()
(defined in
FeatureSummary
)
to tell CMake it is required.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
set(_gaudi_CLHEP_MIN_VERSION 2.4.1.0)
set(_gaudi_gperftools_MIN_VERSION 2.7.0)
set(_gaudi_Doxygen_MIN_VERSION 1.8.15)
foreach(dep IN ITEMS AIDA XercesC CLHEP HepPDT CppUnit unwind gperftools
Doxygen IntelAmplifier jemalloc)
string(TOUPPER ${dep} DEP)
if(GAUDI_USE_${DEP})
find_package(${dep} ${_gaudi_${dep}_MIN_VERSION} ${__quiet})
if(CMAKE_FIND_PACKAGE_NAME) # if the lookup is perform from GaudiConfig.cmake
# then, all optional dependencies become required
set_package_properties(${dep} PROPERTIES TYPE REQUIRED)
else()
set_package_properties(${dep} PROPERTIES TYPE RECOMMENDED)
endif()
endif()
endforeach()
Here we do a similar loop but for optional dependencies.
First, we check the option to know if the dependency is enabled.
Then we call
find_package()
.
Then we check if we are building Gaudi or importing Gaudi.
If we build Gaudi, optional dependencies are
RECOMMENDED
if we import Gaudi, they are
REQUIRED
as explained earlier.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
# Make sure platform specific system libraries are available
if(UNIX AND NOT APPLE)
find_library(rt_LIBRARY rt)
mark_as_advanced(rt_LIBRARY)
if(NOT rt_LIBRARY)
message(FATAL_ERROR "rt was not found")
endif()
endif()
if(APPLE)
find_library(Foundation_FRAMEWORK Foundation)
mark_as_advanced(Foundation_FRAMEWORK)
if(NOT Foundation_FRAMEWORK)
message(FATAL_ERROR "Foundation framework was not found")
endif()
endif()
UNIX systems need
rt
and
MacOS ones need
Foundation
.
In
MacOS' case I do not know what changes because I do not have
any
MacOS system to test Gaudi on and no target links against Foundation.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
# Print a summary of the lookup
feature_summary(FATAL_ON_MISSING_REQUIRED_PACKAGES
DESCRIPTION "Missing Gaudi's required dependencies:"
QUIET_ON_EMPTY
WHAT REQUIRED_PACKAGES_NOT_FOUND)
feature_summary(DESCRIPTION "Gaudi's required optional dependencies found:"
QUIET_ON_EMPTY
WHAT RECOMMENDED_PACKAGES_FOUND)
feature_summary(FATAL_ON_MISSING_REQUIRED_PACKAGES
DESCRIPTION "Missing Gaudi's required optional dependencies:"
QUIET_ON_EMPTY
WHAT RECOMMENDED_PACKAGES_NOT_FOUND)
# Reset this summary not to mess with downstream projects look up
if(COMMAND __reset_feature_summary)
__reset_feature_summary()
endif()
THIS is the most important part of this file: the report. We use
FeatureSummary instead of directly specifing
REQUIRED
to
find_package()
because if we had done so, we would have had to reconfigure each and every
time a third party dependency was not found because it would have raised
an error, until we fixed all dependency issues. Here
feature_summary()
will print at once all the required dependencies that were not found to
be able to fix all the issues at once.
If a
REQUIRED
or
RECOMMENDED
dependency was not found, an error occurs.
All the
RECOMMENDED
(optional) dependencies used are displayed.
For
__reset_feature_summary()
see
GaudiToolbox.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
if(COMMAND __silence_header_warnings)
__silence_header_warnings(Boost::headers Boost::system Boost::filesystem
Boost::regex Boost::thread Boost::python Boost::unit_test_framework
Boost::program_options Boost::log Boost::log_setup Boost::graph
ROOT::Core ROOT::RIO ROOT::Hist ROOT::Thread ROOT::Matrix ROOT::MathCore
ROOT::Net ROOT::XMLIO ROOT::Tree ROOT::TreePlayer ROOT::Graf3d
ROOT::Graf ROOT::Gpad ZLIB::ZLIB cppgsl::cppgsl CppUnit::cppunit)
endif()
See
GaudiToolbox.
GaudiToolbox.cmake
See
GaudiToolbox (next part)
Most sub-projects of Gaudi are standard-ish but we will give
details on some of them.
Gaudi
is the sub-project that builds the executable
Gaudi. This executable
used to be referred as
Gaudi.exe
and this name is still used is several
places. So, for backward compatibility, we create a symlink
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
file(CREATE_LINK Gaudi ${CMAKE_CURRENT_BINARY_DIR}/Gaudi.exe SYMBOLIC)
GaudiCoreSvc
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
gaudi_generate_version_header_file(GaudiCoreSvc)
target_include_directories(GaudiCoreSvc PRIVATE
"${CMAKE_CURRENT_BINARY_DIR}/include")
if(UNIX AND NOT APPLE)
target_link_libraries(GaudiCoreSvc PRIVATE ${rt_LIBRARY})
endif()
Here we generate a version header. To have
GaudiCoreSvc be able to use
it we add directory where it is generated to the list of include directories
of
GaudiCoreSvc. On UNIX platform,
GaudiCoreSvc needs
rt
.
GaudiExamples
The
CMakeLists.txt
of this package is one of the most complete ones, thus it
is a good example. That is why it is used in the user guide.
(Here some sources were removed from the list of sources.)
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
gaudi_add_library(GaudiExamplesLib
SOURCES src/Lib/Counter.cpp
LINK PUBLIC GaudiKernel)
gaudi_add_module(GaudiExamples
SOURCES src/AbortEvent/AbortEventAlg.cpp
LINK GaudiKernel
GaudiAlgLib
GaudiUtilsLib
GaudiExamplesLib
ROOT::Tree
ROOT::RIO
ROOT::Hist
ROOT::Net
Rangev3::rangev3)
Examples of how to use
gaudi_*()
functions.
Within Gaudi, Gaudi's targets can be referred as
Gaudi???
or
Gaudi::Gaudi???
.
We use targets defined by third-party dependencies here instead of the old
${dep_LIBRARY}
or
${dep_INCLUDE_DIR}
...
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
if(GAUDI_USE_AIDA)
target_sources(GaudiExamples PRIVATE src/EvtColsEx/EvtColAlg.cpp
src/RandomNumber/RandomNumberAlg.cpp)
target_link_libraries(GaudiExamples PRIVATE AIDA::aida)
endif()
This how we manage optional dependencies. If they are enabled, we add
special source files to the list of sources of a target and we link the
target against them. It is standard CMake. It uses internally target properties.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
if(TARGET RootCnvLib)
target_sources(GaudiExamples PRIVATE src/MultiInput/DumpAddress.cpp
src/MultiInput/MIReadAlg.cpp
src/MultiInput/MIWriteAlg.cpp)
target_link_libraries(GaudiExamples PRIVATE RootCnvLib)
endif()
Here it is a special case of optional dependency we want to link against
RootCnvLib but this target may not have been built because it requires
an optional dependency to be enabled. That is why we test the existence
of the target.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
gaudi_add_executable(Allocator
SOURCES src/Allocator/Allocator.cpp
src/Allocator/MyClass1A.cpp
src/Allocator/MyClass1.cpp
LINK GaudiExamplesLib
GaudiAlgLib
GaudiKernel)
# ...
gaudi_generate_confuserdb()
# Tests
gaudi_add_tests(QMTest)
gaudi_add_tests(nosetests)
# Compiled python module
gaudi_add_python_module(PyExample
SOURCES src/PythonModule/Functions.cpp
src/PythonModule/PyExample.cpp
LINK Python::Python
Boost::python)
# ROOT dictionaries
gaudi_add_dictionary(GaudiExamplesDict
HEADERFILES src/IO/dict.h
SELECTION src/IO/dict.xml
LINK GaudiExamplesLib)
# Install python modules
gaudi_install(PYTHON)
# Install other scripts
configure_file(scripts/TupleEx3.py.in
${CMAKE_CURRENT_BINARY_DIR}/generated/scripts/TupleEx3.py)
configure_file(scripts/TupleEx4.py.in
${CMAKE_CURRENT_BINARY_DIR}/generated/scripts/TupleEx4.py)
gaudi_install(SCRIPTS)
gaudi_install(SCRIPTS ${CMAKE_CURRENT_BINARY_DIR}/generated/scripts/)
Here we have an example of scripts that are configured at generation time
with
configure_file()
then installed with
gaudi_install()
.
GaudiKernel
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
# Look for VectorClass required header (private dependency)
find_path(VectorClass_INCLUDE_DIR NAMES instrset_detect.cpp)
if(VectorClass_INCLUDE_DIR AND NOT VectorClass_VERSION)
# check that the version is good enough
set(VectorClass_VERSION 0.0)
file(STRINGS ${VectorClass_INCLUDE_DIR}/instrset.h _vectorclass_guard REGEX "define +INSTRSET_H +[0-9]+")
list(GET _vectorclass_guard 0 _vectorclass_guard)
if(_vectorclass_guard MATCHES "INSTRSET_H +([0-9]+)")
string(REGEX REPLACE "([0-9]+)([0-9][0-9])" "\\1.\\2" VectorClass_VERSION "${CMAKE_MATCH_1}")
endif()
set(VectorClass_VERSION "${VectorClass_VERSION}" CACHE INTERNAL "")
endif()
if(NOT VectorClass_INCLUDE_DIR OR VectorClass_VERSION VERSION_LESS 1.25)
if(VectorClass_INCLUDE_DIR)
message(STATUS "Found VectorClass instrset_detect ${VectorClass_VERSION} at ${VectorClass_INCLUDE_DIR}")
endif()
message(WARNING "using internal VectorClass instrset_detect")
set(VectorClass_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/src/contrib" CACHE PATH "" FORCE)
set(VectorClass_VERSION "1.25" CACHE INTERNAL "" FORCE)
endif()
mark_as_advanced(VectorClass_INCLUDE_DIR)
message(STATUS "Using VectorClass instrset_detect ${VectorClass_VERSION} at ${VectorClass_INCLUDE_DIR}")
Here we do a lookup of the sources of
VectorClass. It is not in a
FindVectorClass.cmake
because is the
VectorClass it found is not recent enough, we use an internal implementation
of
VectorClass defined in
src/contrib
.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
gaudi_add_library(GaudiKernel
SOURCES src/Lib/AlgContext.cpp
LINK
PUBLIC GaudiPluginService
Boost::headers
Boost::filesystem
Boost::thread
Boost::regex
Boost::system
TBB::tbb
${CMAKE_DL_LIBS}
ROOT::Core
PRIVATE cppgsl::cppgsl)
target_include_directories(GaudiKernel PRIVATE ${VectorClass_INCLUDE_DIR})
GaudiKernel needs dl and
VectorClass, thus ${CMAKE_DL_LIBS} and ${VectorClass_INCLUDE_DIR}.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
if(APPLE)
target_sources(GaudiKernel PRIVATE src/Lib/Platform/SystemMacOS.h
src/Lib/Platform/SystemMacOS.mm)
target_link_libraries(GaudiKernel PUBLIC Foundation)
elseif(WIN32)
target_sources(GaudiKernel PRIVATE src/Lib/Platform/SystemWin32.h
src/Lib/Platform/SystemWin32.cpp)
elseif(UNIX)
target_sources(GaudiKernel PRIVATE src/Lib/Platform/SystemLinux.h
src/Lib/Platform/SystemLinux.cpp)
target_link_libraries(GaudiKernel PUBLIC ${rt_LIBRARY})
endif()
GaudiKernel contains some sources specific to the platform. Here the headers
are specified for no particular reason.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
gaudi_add_executable(profile_Property
SOURCES tests/src/profile_Property.cpp
LINK GaudiKernel)
get_filename_component(package_name ${CMAKE_CURRENT_SOURCE_DIR} NAME)
gaudi_add_executable(DirSearchPath_test
SOURCES tests/src/DirSearchPath_test.cpp
LINK GaudiKernel
TEST)
gaudi_add_executable(PathResolver_test SOURCES tests/src/PathResolver_test.cpp
LINK GaudiKernel)
add_test(NAME ${package_name}.PathResolver_test
COMMAND run $<TARGET_FILE:PathResolver_test>
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/tests)
gaudi_add_executable(Parsers_test SOURCES tests/src/parsers.cpp
LINK GaudiKernel TEST)
Lots of tests are executable that just have to be run. If the test has a special
behavior we create an executable then we use
add_test
directly. The "problem"
with these tests is that they are exported at install time. All the tests are
installed (the binaries) but they are not exported because it is highly unlikely
that their targets could be useful in downstream projects.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
add_executable(test_StatusCodeFail tests/src/test_StatusCode_fail.cxx)
target_link_libraries(test_StatusCodeFail PRIVATE GaudiKernel)
set_target_properties(test_StatusCodeFail PROPERTIES EXCLUDE_FROM_ALL TRUE EXCLUDE_FROM_DEFAULT_BUILD TRUE)
add_test(NAME ${package_name}.test_StatusCodeFail
COMMAND ${CMAKE_COMMAND} --build ${CMAKE_BINARY_DIR} --target test_StatusCodeFail)
set_property(TEST ${package_name}.test_StatusCodeFail PROPERTY PASS_REGULAR_EXPRESSION "FAIL01;FAIL02;FAIL03;FAIL04")
All standard feature of CMake can be used.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
if(GAUDI_USE_CPPUNIT)
gaudi_add_executable(test_SerializeSTL SOURCES tests/src/test_SerializeSTL.cpp
LINK GaudiKernel CppUnit::cppunit TEST)
endif()
Tests that uses an optional dependency.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
gaudi_add_tests(nosetests ${CMAKE_CURRENT_SOURCE_DIR}/tests/nose/confdb)
gaudi_add_tests(nosetests)
gaudi_add_module(test_CustomFactory
SOURCES tests/src/custom_factory.cpp
LINK GaudiPluginService)
# Genconf
gaudi_add_executable(genconf
SOURCES src/Util/genconf.cpp
LINK GaudiKernel
GaudiPluginService
Boost::headers
Boost::program_options
Boost::regex
Boost::log
Boost::log_setup
ROOT::Core)
target_compile_definitions(genconf PRIVATE BOOST_LOG_DYN_LINK)
set_target_properties(genconf PROPERTIES ENABLE_EXPORTS TRUE)
if(WIN32)
gaudi_add_executable(genwindef
SOURCES src/Util/genwindef.cpp
src/Util/LibSymbolInfo.cpp)
endif()
gaudi_add_executable(instructionsetLevel
SOURCES src/Util/instructionsetLevel.cpp
LINK GaudiKernel
GaudiPluginService)
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/custom_factory_testdir)
add_test(NAME ${package_name}.genconf_with_custom_factory
COMMAND ${CMAKE_COMMAND} -E env LD_LIBRARY_PATH=$<TARGET_FILE_DIR:test_CustomFactory>:$<TARGET_FILE_DIR:GaudiCoreSvc>:$ENV{LD_LIBRARY_PATH}
$<TARGET_FILE:genconf>
-o ${CMAKE_CURRENT_BINARY_DIR}/custom_factory_testdir
-p CustomFactoryTest
-i test_CustomFactory
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/custom_factory_testdir)
Stuff related to genconf.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
# QMTest
gaudi_add_tests(QMTest)
# ROOT dictionaries
gaudi_add_dictionary(GaudiKernelDict
HEADERFILES dict/dictionary.h
SELECTION dict/dictionary.xml
LINK GaudiKernel)
# Install python modules
gaudi_install(PYTHON)
# Install other scripts
gaudi_install(SCRIPTS)
# Check that gdb can be used for stack traces
find_program(gdb_cmd gdb)
mark_as_advanced(gdb_cmd)
if(gdb_cmd)
get_filename_component(gdb_dir "${gdb_cmd}" DIRECTORY)
file(TO_CMAKE_PATH "$ENV{PATH}" envpath)
if(NOT gdb_dir IN_LIST envpath)
set_property(TARGET target_runtime_paths APPEND
PROPERTY runtime_path $<SHELL_PATH:${gdb_dir}>)
endif()
else()
message(WARNING "gdb not found, you will not be able to have stack traces for problems")
endif()
Here, we make sure that
gdb
is available at runtime (in the runtime environment).
find_program()
tries to find it in the CMAKE_PREFIX_PATH and CMAKE_PROGRAM_PATH
and then in ENV{PATH}. If we found it and it is not in the path we add it through
the target defined in
GaudiToolbox. It exposes the internal machinery of Gaudi but
it is a good thing not to have
gaudi_env()
like functions because people
would then use it to set tests environment instead of using the appropriate tag
in
.qmt
files. I am worrying about one case. What if a
gdb
is found in
CMAKE_PREFIX_PATH but you expect to use your
gdb
(the one in your PATH)
at runtime?
GaudiMonitor
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
if(WIN32)
target_link_libraries(GaudiMonitor PRIVATE ws2_32.lib)
# These are needed because Windows compiler doesn't like standard C and POSIX functions,
# but this package contains some imported (external) C file that is compiled here in C++.
target_compile_definitions(GaudiMonitor PRIVATE _CRT_SECURE_NO_WARNINGS
_SCL_SECURE_NO_WARNINGS)
target_compile_options(GaudiMonitor /wd4996)
endif()
I have no idea why it is needed. Since LHCb does not support Windows this is
apparently for ATLAS.
GaudiPluginService
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
option(GAUDI_REFLEX_COMPONENT_ALIASES
"allow use of old style (Reflex) component names"
ON)
An option specific to
GaudiPluginService.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
# Build library
gaudi_add_library(GaudiPluginService
SOURCES src/PluginServiceV2.cpp
src/PluginServiceV1.cpp
src/capi_pluginservice.cpp
LINK
PRIVATE ${CMAKE_DL_LIBS}
stdc++fs
PUBLIC Threads::Threads)
if(GAUDI_REFLEX_COMPONENT_ALIASES)
target_compile_definitions(GaudiPluginService PRIVATE
GAUDI_REFLEX_COMPONENT_ALIASES)
endif()
We need to explicitly link against
dl
stdc++fs
(no variable because it is
a standard library like
m
) and pthread.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
# Build runtime
gaudi_add_executable(listcomponents
SOURCES src/listcomponents.cpp
LINK GaudiPluginService
${CMAKE_DL_LIBS})
# Tests
gaudi_add_library(Test_GaudiPluginService_UseCasesLib
SOURCES tests/src/UseCasesLib.cpp
LINK PRIVATE GaudiPluginService
Boost::unit_test_framework)
gaudi_add_executable(Test_GaudiPluginService_UseCases
SOURCES tests/src/UseCasesTests.cpp
LINK GaudiPluginService
Boost::unit_test_framework
TEST)
gaudi_add_library(Test_GaudiPluginService_LegacyUseCasesLib
SOURCES tests/src/LegacyUseCasesLib.cpp
LINK PRIVATE GaudiPluginService)
gaudi_add_executable(Test_GaudiPluginService_LegacyUseCases
SOURCES tests/src/LegacyUseCasesTests.cpp
LINK GaudiPluginService
Boost::unit_test_framework
TEST)
GaudiPluginService is the only package where libraries are built for tests.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
get_filename_component(package_name ${CMAKE_CURRENT_SOURCE_DIR} NAME)
# tests on listcomponents
add_test(NAME ${package_name}.listcomponents.usage
COMMAND run $<TARGET_FILE:listcomponents>)
Some tests after.
GaudiProfiling
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
if(NOT CMAKE_SYSTEM_NAME MATCHES Linux)
message(WARNING "Requirements for building GaudiProfiling not reached. "
"GaudiProfiling will not be built. "
"Not building for Linux.")
return()
endif()
This package is only available on Linux.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
#-----------------------------------
# PerfMon profiler
#-----------------------------------
if(GAUDI_USE_UNWIND AND CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64")
gaudi_add_module(GaudiProfiling
SOURCES src/component/IgHook_IgHookTrace.cpp
src/component/PerfMonAuditor.cpp
LINK GaudiKernel
ZLIB::ZLIB
unwind::unwind)
target_include_directories(GaudiProfiling PRIVATE src/component)
gaudi_add_python_module(PyCPUFamily
SOURCES src/python/CPUFamily.cpp
LINK Python::Python
Boost::python)
gaudi_add_executable(GaudiGenProfilingHtml
SOURCES src/app/pfm_gen_analysis.cpp
LINK GaudiKernel
ZLIB::ZLIB
unwind::unwind)
else()
message(STATUS "GaudiProfiling: the module PerfMon profiler is disabled")
endif()
#-----------------------------------
# Google PerfTools profiler
#-----------------------------------
if(GAUDI_USE_GPERFTOOLS)
gaudi_add_module(GaudiGoogleProfiling
SOURCES src/component/google/GoogleAuditor.cpp
LINK GaudiKernel
GaudiAlgLib
ZLIB::ZLIB
gperftools::gperftools)
# Special handling of unresolved symbols in GaudiGoogleProfiling.
# The profilers need to have libtcmalloc.so or libprofiler.so pre-loaded to
# work, so it's better if the symbols stay undefined in case somebody tries to
# use the profilers without preloading the libraries.
set(gprof_linker_flags)
foreach(undef_symbol IN ITEMS IsHeapProfilerRunning
HeapProfilerStart HeapProfilerStop
HeapProfilerDump GetHeapProfile
ProfilerStart ProfilerStop
_ZN15HeapLeakCheckerC1EPKc
_ZN15HeapLeakChecker9DoNoLeaksENS_15ShouldSymbolizeE
_ZN15HeapLeakCheckerD1Ev)
list(APPEND gprof_linker_flags "-Wl,--defsym,${undef_symbol}=0")
endforeach()
set_target_properties(GaudiGoogleProfiling PROPERTIES LINK_OPTIONS "${gprof_linker_flags}")
else()
message(STATUS "GaudiProfiling: the module Google PerfTools profiler is disabled")
endif()
#-----------------------------------
# Intel VTune profiler
#-----------------------------------
if(GAUDI_USE_INTELAMPLIFIER)
gaudi_add_module(IntelProfiler
SOURCES src/component/intel/IntelProfilerAuditor.cpp
LINK GaudiKernel
IntelAmplifier::libittnotify)
gaudi_add_module(GaudiIntelProfiling
SOURCES src/component/intel/IntelProfile.cpp
LINK GaudiKernel
GaudiAlgLib
IntelAmplifier::libittnotify)
else()
message(STATUS "GaudiProfiling: the module Intel VTune profiler is disabled")
endif()
Intel VTune Amplifier is the only optional dependency that is not enable
by default. It it is disable this is the only block of code that is not processed.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
#-----------------------------------
# Valgrind profiler
#-----------------------------------
gaudi_add_module(GaudiValgrindProfiling
SOURCES src/component/valgrind/CallgrindProfile.cpp
LINK GaudiKernel
GaudiAlgLib
ZLIB::ZLIB)
#-----------------------------------
# jemalloc
#-----------------------------------
gaudi_add_module(GaudiJemalloc
SOURCES src/component/jemalloc/JemallocProfile.cpp
src/component/jemalloc/JemallocProfileSvc.cpp
LINK GaudiKernel
GaudiAlgLib)
# Special handling of unresolved symbols in Jemmalloc.
# The profilers need to have libjemalloc.so pre-loaded to
# work, so it's better if the symbols stay undefined in case somebody tries to
# use the profilers without preloading the libraries.
set_target_properties(GaudiJemalloc PROPERTIES LINK_OPTIONS "-Wl,--defsym,mallctl=0")
if(GAUDI_USE_JEMALLOC)
add_test(NAME GaudiProfiling.jira.gaudi_1045
COMMAND run ${CMAKE_COMMAND} -E env LD_PRELOAD=$<TARGET_PROPERTY:jemalloc::jemalloc,LOCATION> gaudirun.py)
endif()
Here, we do not link against jemalloc, instead we just need to preload it in order
the test not to fail.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
# Register the header only library (install include/)
gaudi_add_header_only_library(GaudiProfilingHeaders)
One of the 2 header only library of Gaudi. The other is in
GaudiHive.
%ENDCODE%
GaudiPython
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
gaudi_add_dictionary(GaudiPythonDict
HEADERFILES dict/kernel.h
SELECTION dict/selection_kernel.xml
LINK GaudiPythonLib
GaudiUtilsLib)
if(GAUDI_USE_AIDA)
target_compile_definitions(GaudiPythonDict PRIVATE AIDA_FOUND)
endif()
if(GAUDI_USE_CLHEP)
target_compile_definitions(GaudiPythonDict PRIVATE CLHEP_FOUND)
endif()
Here, we give the information that some optional dependencies are used
to the header.
GaudiRelease
Actually, I have nerver tried to do a fake release of Gaudi.
I do not even know if this package work nor what it should produce
(documentation generated by doxygen).
What is important to know though, is that this package is one of the 3 places
where a glob pattern is used. It means that you need to reconfigure if you want
it to be re-evaluated.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
file(GLOB project_rel_notes ${CMAKE_CURRENT_SOURCE_DIR}/doc/release.notes*.html)
PartPropSvc
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
# PartPropSvc subdirectory
# If HepPDT is not available, give up on this subdirectory:
if(NOT GAUDI_USE_HEPPDT)
message(WARNING "Requirements for building PartPropSvc disabled. "
"PartPropSvc will not be built. "
"HepPDT is disabled.")
return()
endif()
# Build the plugin
gaudi_add_module(PartPropSvc
SOURCES src/PartPropSvc.cpp
LINK GaudiKernel
Boost::headers
Boost::regex
HepPDT::heppdt)
The only target built by this package requires
HepPDT so if it is disabled
we do not build it.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
# # Install shared files TODO: if it is not used, drop the whole directory
# install(FILES share/PartPropSvc.py
# DESTINATION jobOptions/PartPropSvc)
# install(FILES share/PDGTABLE.MeV
# share/PDGTABLE.2002.MeV
# share/PDGTABLE.2014.MeV
# DESTINATION share)
We still need to ask someone from ATLAS if these files need to be installed.
Top level CMakeLists.txt
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
cmake_minimum_required(VERSION 3.15)
The configuration needs at least CMake 3.15.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
project(Gaudi VERSION 32.0
LANGUAGES CXX
DESCRIPTION "CERN Gaudi framework"
HOMEPAGE_URL "https://gitlab.cern.ch/gaudi/Gaudi")
VERSION is used is the configuration. LANGUAGES is here to disable the C language
and thus the C compiler checks.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
# Add the "Developer" build type
include(cmake/DeveloperBuildType.cmake)
# CMAKE_USE_CCACHE for ccache
include(cmake/CcacheOption.cmake)
# Import Gaudi functions (gaudi_*) ==> for documentation look in the file
include(cmake/GaudiToolbox.cmake)
Here we add our custom modules to the configuration.
Should
DeveloperBuildType
and
CcacheOption
be moved,
we would need to update
CMAKE_MODULE_PATH
(not an environment variable yet)
and remove
cmake/
and
.cmake
of calls to
include()
.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
# Export the list of compile commands
set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE BOOL "Enable/Disable output of compile_commands.json" FORCE)
Standard CMake here to generate
compile_commands.json
.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
# Options to choose dependencies that are not required to build Gaudi
foreach(optional_dependency IN ITEMS AIDA XERCESC CLHEP HEPPDT CPPUNIT UNWIND
GPERFTOOLS DOXYGEN JEMALLOC)
option(GAUDI_USE_${optional_dependency} "Enable the dependency ${optional_dependency}" ON)
set(GAUDI_OPTIONAL_DEPENDENCIES "${GAUDI_OPTIONAL_DEPENDENCIES}set(GAUDI_USE_${optional_dependency} ${GAUDI_USE_${optional_dependency}})\n")
endforeach()
# By default VTune is OFF
option(GAUDI_USE_INTELAMPLIFIER "Enable the dependency INTELAMPLIFIER" OFF)
set(GAUDI_OPTIONAL_DEPENDENCIES "${GAUDI_OPTIONAL_DEPENDENCIES}set(GAUDI_USE_INTELAMPLIFIER ${GAUDI_USE_INTELAMPLIFIER})\n")
Here, it is a factored out way of defining the options to enable/disable the
optional dependencies.
First we declare the option with the default value of
ON
.
Then we append to
GAUDI_OPTIONAL_DEPENDENCIES
a line of CMake code
to set the variable to the real value of this variable at configure time.
GAUDI_OPTIONAL_DEPENDENCIES
is expanded is
GaudiConfig.cmake
.
There is one particular case, namely Intel VTune Amplifier, that is disable by
default for licencing reasons.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
# Find all the dependencies of the project
list(PREPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake/modules") # (Find*.cmake)
if(DEFINED CACHE{ROOT_DIR}) # Not to have the output displayed each time
set(GAUDI_DEPENDENCIES_FIND_QUIETLY TRUE)
endif()
include(cmake/GaudiDependencies.cmake)
Here we call
GaudiDependencies.cmake
to do the dependency lookup.
First we update the location of find-module-files.
Then we use a feature from CMake 3.14 to know if a variable defined by a dependency
exists in
CMakeCache to know if it is the first configuration or not.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
# The C++ standard used must be the same as ROOT's
if(NOT GAUDI_CXX_STANDARD)
# FIXME: the day ROOT_CXX_STANDARD is provided by ROOTConfig.cmake, remove this lookup
set(ROOT_CXX_STANDARD ${ROOT_CXX_FLAGS}) # try to parse ROOT compile flags
separate_arguments(ROOT_CXX_STANDARD)
list(FILTER ROOT_CXX_STANDARD INCLUDE REGEX "-std=(c|gnu)\\+\\+[0-9]+")
list(TRANSFORM ROOT_CXX_STANDARD REPLACE "-std=(c|gnu)\\+\\+([0-9]+)" "\\2")
if(NOT ROOT_CXX_STANDARD)
message(FATAL_ERROR "Cannot find the C++ standard of ROOT")
endif()
list(GET ROOT_CXX_STANDARD 0 ROOT_CXX_STANDARD)
set(GAUDI_CXX_STANDARD ${ROOT_CXX_STANDARD} CACHE STRING "The version of C++ used to compile Gaudi")
endif()
message(STATUS "Gaudi will be built using C++${GAUDI_CXX_STANDARD} standard.")
set(CMAKE_CXX_STANDARD ${GAUDI_CXX_STANDARD})
set(CMAKE_CXX_STANDARD_REQUIRED TRUE) # do not fall back on older version
set(CMAKE_CXX_EXTENSIONS OFF) # strict c++.. instead of gnu++..
Here, we retrieve the C++ standard used by ROOT to use the same for Gaudi.
It is mandatory because ROOT dictionaries use C++ that will be parsed by
cling
embedded in ROOT. We cannot use a more recent C++ standard otherwise
cling
would not understand it.
To retrieve this standard, we parse ROOT's compile flags (that are imported
in
ROOTConfig.cmake
). We look for
-std=c++17
for instance. This parsing will
fail if the flag is
-std=c++1z
for example.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
# Prevent the installation if exporting to the build tree
option(GAUDI_BUILD_TREE_AS_INSTALL_AREA "Set this to ON to disable \
installation of Gaudi but be able to use the build tree as an installed Gaudi" OFF)
if(GAUDI_BUILD_TREE_AS_INSTALL_AREA)
install(CODE "message(FATAL_ERROR \"The option GAUDI_BUILD_TREE_AS_INSTALL_AREA \
was set to TRUE at configure time, it is impossible to install ${PROJECT_NAME}.\")")
endif()
Even if GAUDI_BUILD_TREE_AS_INSTALL_AREA is TRUE,
gaudi_add_*()
functions still
call
install()
thus generating
cmake_install.cmake
files for each target.
In order the installation to fail before attempting anything, we raise a FATAL_ERROR
before adding all the sub-projects to the configuration.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
enable_testing()
Mandatory to call this function once to add a special target call
test
to the build system. It does not harm if it is called several times.
It needs to be done before adding the sub-projects to the configuration.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
add_subdirectory(GaudiPluginService)
add_subdirectory(GaudiPolicy)
add_subdirectory(GaudiKernel)
add_subdirectory(GaudiCoreSvc)
add_subdirectory(GaudiUtils)
add_subdirectory(Gaudi)
add_subdirectory(GaudiAlg)
add_subdirectory(GaudiAud)
add_subdirectory(GaudiCommonSvc)
add_subdirectory(GaudiHive)
add_subdirectory(GaudiMonitor)
add_subdirectory(GaudiMP)
add_subdirectory(GaudiPartProp)
add_subdirectory(GaudiProfiling)
add_subdirectory(GaudiPython)
add_subdirectory(GaudiRelease)
add_subdirectory(GaudiSvc)
add_subdirectory(PartPropSvc)
add_subdirectory(RootCnv)
add_subdirectory(RootHistCnv)
add_subdirectory(GaudiExamples)
Explicit list of sub-projects without dependency conflicts.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
# Generate GAUDI_VERSION.h
gaudi_generate_version_header_file()
# Framework level tests
# Test scripts inside cmake/
gaudi_add_tests(nosetests ${PROJECT_SOURCE_DIR}/cmake/extract_qmtest_metadata.py)
if(NOT PROJECT_NAME STREQUAL CMAKE_PROJECT_NAME) # If we are building the full stack as one project
# we do not install nor package nor register CMake tests
# but we add Gaudi's CMake modules to the CMAKE_MODULE_PATH for downstream projects
list(PREPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} PARENT_SCOPE)
return()
endif()
Here, we do what is necessary for the downstream projects to be able to use
what is defined by Gaudi (CMake modules).
Since, we cannot install the
FullStack project there is no need to process
the rest of the file.
The trick used here is
if the name of the top level project is not the one we are in, we are in a project of a stack.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
# Unit tests of gaudi_* functions
include(cmake/tests/testGaudiToolbox.cmake)
# Unit tests of the installation
include(cmake/tests/testGaudiInstallation.cmake)
# Unit tests of Gaudi as dependency in a downstream project (with config files)
include(cmake/tests/testGaudiDownstream.cmake)
Inclusion of all the tests described earlier.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
# Installation of Gaudi
include(CMakePackageConfigHelpers)
# Generate the config files
if(GAUDI_BUILD_TREE_AS_INSTALL_AREA)
set(_gaudi_config_files_location ${CMAKE_CURRENT_SOURCE_DIR}/cmake)
set(_set_extract_qmtest_metadata "set(extract_qmtest_metadata ${extract_qmtest_metadata} CACHE INTERNAL \"\" FORCE)")
else() # used in GaudiConfig.cmake.in
set(_gaudi_config_files_location "\${CMAKE_CURRENT_LIST_DIR}")
endif()
If we install Gaudi, the CMake modules will be located in the same folder as
GaudiConfig.cmake
. If we are in the build tree, they are in the source tree.
Here we also set _set_extract_qmtest_metadata for
GaudiConfig.cmake.in discussed earlier.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
configure_package_config_file(cmake/GaudiConfig.cmake.in ${PROJECT_NAME}Config.cmake
INSTALL_DESTINATION "${GAUDI_INSTALL_CONFIGDIR}"
PATH_VARS CMAKE_INSTALL_BINDIR CMAKE_INSTALL_LIBDIR CMAKE_INSTALL_INCLUDEDIR
GAUDI_INSTALL_PLUGINDIR GAUDI_INSTALL_PYTHONDIR
NO_CHECK_REQUIRED_COMPONENTS_MACRO)
write_basic_package_version_file(${PROJECT_NAME}ConfigVersion.cmake
COMPATIBILITY AnyNewerVersion)
Here we generate
GaudiConfig.cmake
and
GaudiConfigVersion.cmake
with a
standard module of CMake (
CMakePackageConfigHelpers
). GAUDI_BUILD_TREE_AS_INSTALL_AREA
will be taken into account.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
# Install or export Gaudi
if(GAUDI_BUILD_TREE_AS_INSTALL_AREA) # no installation, use build tree
# Generate the file to import the exported targets in downstream projects
export(EXPORT ${PROJECT_NAME} NAMESPACE ${PROJECT_NAME}::
FILE "${PROJECT_NAME}Targets.cmake")
message(STATUS "Using the build tree instead of the install area. ${PROJECT_NAME} cannot be installed.")
Here, we generate a non-relocatable
GaudiTargets.cmake
with
export(EXPORT)
that
GaudiConfig.cmake in the build tree will use.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
else() # regular installation
# Install the set of exported targets
install(EXPORT ${PROJECT_NAME} NAMESPACE ${PROJECT_NAME}::
DESTINATION "${CMAKE_INSTALL_PREFIX}"
FILE "${PROJECT_NAME}Targets.cmake"
DESTINATION "${GAUDI_INSTALL_CONFIGDIR}")
Here we generate a relocatable
GaudiTargets.cmake
for an installation.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
# Install cmake files for downstream project to be able to use Gaudi
gaudi_install(CMAKE cmake/GaudiToolbox.cmake
cmake/GaudiDependencies.cmake
cmake/extract_qmtest_metadata.py # used in gaudi_add_tests(QMTest)
cmake/DeveloperBuildType.cmake
cmake/CcacheOption.cmake
"${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake"
cmake/modules) # Find*.cmake
CMake required files for downstream projects.
Should
DeveloperBuildType
and
CcacheOption
be move, we would need to
remove them from this list.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
# Add an uninstall target
add_custom_target(uninstall
COMMAND test -f ${CMAKE_CURRENT_BINARY_DIR}/install_manifest.txt &&
xargs rm < ${CMAKE_CURRENT_BINARY_DIR}/install_manifest.txt &&
rm ${CMAKE_CURRENT_BINARY_DIR}/install_manifest.txt &&
echo ${PROJECT_NAME} uninstalled successfully. ||
echo ${PROJECT_NAME} has not been installed yet or you need to be root to uninstall.
COMMENT "Uninstalling ${PROJECT_NAME}")
endif()
This
uninstall
target is only for convenience when we want to test the installation.
We do not expect you to need it.
Should Gaudi be built on Windows make sur using Unix commands works (e.g. with
PowerShell).
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
# CPack configuration
set(CPACK_PACKAGE_NAME ${PROJECT_NAME})
set(CPACK_PACKAGE_VENDOR "CERN-LHCb")
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "${PROJECT_DESCRIPTION}")
set(CPACK_PACKAGE_VERSION ${PROJECT_VERSION})
foreach(v IN ITEMS MAJOR MINOR PATCH)
set(CPACK_PACKAGE_VERSION_${v} ${PROJECT_VERSION_${v}})
endforeach()
if(DEFINED BINARY_TAG)
set(CPACK_SYSTEM_NAME ${BINARY_TAG})
elseif(DEFINED ENV{BINARY_TAG})
set(CPACK_SYSTEM_NAME $ENV{BINARY_TAG})
endif()
set(CPACK_GENERATOR "ZIP;RPM") # package format
# The following line is needed to prevent the compilation of python modules before packaging (an incorrect version of python may be used).
# FIXME: the day we can tell CMake to prepend the call to rpm by the run script, do it and remove this line (something like ``set(CPACK_RPM_LAUNCHER ${CMAKE_BINARY_DIR}/run)``)
set(CPACK_RPM_SPEC_MORE_DEFINE "%global __os_install_post %(echo '%{__os_install_post}' | sed -e 's!/usr/lib[^[:space:]]*/brp-python-bytecompile[[:space:]].*$!!g')")
# -- for the sources
set(CPACK_SOURCE_IGNORE_FILES "/InstallArea/;/build\\\\..*/;/\\\\.git/;/\\\\.git.*")
set(CPACK_SOURCE_GENERATOR "ZIP;RPM")
include(CPack)
Actually, the CPack configuration is not needed and is just here to see if it works.
It adds two targets to the build, namely
package
and
package_source
.
Makefile
This Makefile is actually a wrapper to build Gaudi more easily.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
# settings
CMAKE := cmake
CTEST := ctest
# Guess if a toolchain or cache preload should be used
ifneq ($(wildcard $(CURDIR)/toolchain.cmake),)
override CMAKEFLAGS += -D CMAKE_TOOLCHAIN_FILE=$(CURDIR)/toolchain.cmake
endif
ifneq ($(wildcard $(CURDIR)/cache_preload.cmake),)
override CMAKEFLAGS += -C $(CURDIR)/cache_preload.cmake
endif
If a file called
toolchain.cmake
exists, it is used as toolchain file for Gaudi's configuration.
If a file called
cache_preload.cmake
exists, it is used to pre-load the
CMakeCache.cmake of Gaudi's configuration.
NB: the cache is pre-loaded before the toolchain file is processed.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
# Retrieve the platform we build on
ifndef BINARY_TAG
ifdef CMAKECONFIG
BINARY_TAG := ${CMAKECONFIG}
else
ifdef CMTCONFIG
BINARY_TAG := ${CMTCONFIG}
$(info You are using CMTCONFIG but your BINARY_TAG is not set, you should `export BINARY_TAG=$$CMTCONFIG`)
else
BINARY_TAG := unknown
endif
endif
endif
Attempts to find the platform you want to build for.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
BUILDDIR := $(CURDIR)/build.$(BINARY_TAG)
Default name of the build tree.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
# default target
all:
# deep clean
purge:
$(RM) -r $(BUILDDIR) $(CURDIR)/InstallArea/$(BINARY_TAG)
find $(CURDIR) "(" -name "InstallArea" -prune -o -name "*.pyc" -o -name "*ref.~*~.new" ")" -a -type f -exec $(RM) -v \{} \;
make purge
removes everything related to the build.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
# delegate any target to the build directory (except 'purge')
ifneq ($(MAKECMDGOALS),purge)
%: $(BUILDDIR)/CMakeCache.txt FORCE
+$(CMAKE) --build $(BUILDDIR) -t $* -- $(BUILDFLAGS)
endif
# aliases
.PHONY: configure remove_cache FORCE
ifneq ($(wildcard $(BUILDDIR)/CMakeCache.txt),)
configure: remove_cache $(BUILDDIR)/CMakeCache.txt
else
configure: $(BUILDDIR)/CMakeCache.txt
endif
@ # do not delegate further
If we configure for the first time, we configure.
If we call
make configure
on an already configured project, it will be
reconfigured completely (remove
CMakeCache.cmake
then configure).
This enables us to reconfigure cleanly the project from scratch without
calling
make purge
which would erase all the binaries.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
# This wrapping around the test target is used to ensure the generation of
# the XML output from ctest.
test: $(BUILDDIR)/CMakeCache.txt
$(RM) -r $(BUILDDIR)/Testing $(BUILDDIR)/html
-cd $(BUILDDIR) && $(CTEST) -T test $(ARGS)
Produce the XML report to be converted with
CTestXML2HTML
which is no longer
part of Gaudi (formerly in
GaudiPolicy/scripts).
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
ifeq ($(VERBOSE),)
# less verbose install (see GAUDI-1018)
# (emulate the default CMake install target)
install: all
$(CMAKE) --install $(BUILDDIR) | grep -v "^-- Up-to-date:"
endif
Here we install with CMake 3.15 command line.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
# ensure that the target are always passed to the CMake Makefile
FORCE:
@ # dummy target
# Makefiles are used as implicit targets in make, but we should not consider
# them for delegation.
$(MAKEFILE_LIST):
@ # do not delegate further
# trigger CMake configuration
$(BUILDDIR)/CMakeCache.txt:
CMAKE_GENERATOR=Ninja $(CMAKE) -S $(CURDIR) -B $(BUILDDIR) $(CMAKEFLAGS)
# to force reconfiguration
remove_cache:
@rm -f $(BUILDDIR)/CMakeCache.txt
To configure, we use the current directory as source tree. The build tree is the
one described earlier. We forward the flags you want to pass to CMake.
CMAKE_GENERATOR
is here to set the
default generator used by CMake.
It is overridden by
-G
if specified in
CMAKEFLAGS
.
cache_preload.cmake
This file is not tracked by
git
. It is useful if you want modify the default
configuration of Gaudi.
Example:
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
echo "set(CMAKE_USE_CCACHE ON CACHE BOOL \"Use ccache\")" > cache_preload.cmake
toolchain.cmake
Most of the time this file is a symbolic link to a toolchain on CVMFS
(or for the time being, in Gaudi).
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
ln -s cmake/toolchains/$BINARY_TAG.cmake toolchain.cmake
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
include_guard(GLOBAL)
Because everything defined in this file is globally visible (namely
functions and global imported targets).
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
option(GAUDI_INSTALL_OPTIONAL "Target installation is optional if TRUE" FALSE)
if(GAUDI_INSTALL_OPTIONAL)
set(_gaudi_install_optional OPTIONAL)
endif()
This is because we will pass
${_gaudi_install_optional}
to
install()
inside
gaudi_add_*()
functions. So we cannot use directly
GAUDI_INSTALL_OPTIONAL
which is a boolean.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
# Option to prevent genconf to fail (turn this to true if you use
# a sanitizer)
option(GAUDI_GENCONF_NO_FAIL "Prevent genconf to fail (for builds with a sanitizer)" FALSE)
if(GAUDI_GENCONF_NO_FAIL)
set(_gaudi_no_fail "|| true")
endif()
Same explanation as
GAUDI_INSTALL_OPTIONAL
.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
set(CMAKE_INSTALL_BINDIR "bin" CACHE STRING "Install executable in <prefix>/\${CMAKE_INSTALL_BINDIR}")
set(CMAKE_INSTALL_LIBDIR "lib" CACHE STRING "Install libraries in <prefix>/\${CMAKE_INSTALL_LIBDIR}")
set(CMAKE_INSTALL_INCLUDEDIR "include" CACHE STRING "Install public headers in <prefix>/\${CMAKE_INSTALL_INCLUDEDIR}")
set(GAUDI_INSTALL_PLUGINDIR "${CMAKE_INSTALL_LIBDIR}" CACHE STRING "Install plugins in <prefix>/\${GAUDI_INSTALL_PLUGINDIR}")
set(GAUDI_INSTALL_PYTHONDIR "python" CACHE STRING "Install python packages in <prefix>/\${GAUDI_INSTALL_PYTHONDIR}")
set(GAUDI_INSTALL_CONFIGDIR "lib/cmake/${PROJECT_NAME}" CACHE STRING "Install cmake files in <prefix>/\${GAUDI_INSTALL_CONFIGDIR}")
Default installation layout. It is defined in the cache to be modified more easily.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
function(gaudi_add_library lib_name)
cmake_parse_arguments(
ARG
""
""
"SOURCES;LINK"
${ARGN}
)
Like many other
gaudi_add_*()
functions, this one has a SOURCES argument
for the list of source files.
We could have done without a
SOURCES
keyword and have used
ARG_UNPARSED_ARGUMENTS
to get the list of sources. It would actually be more consistent with CMake
add_executable()
and
add_library()
but modifing a bit the signature of the
function will make people realise that something has changed, glob patterns are
no onger handled.
LINK
is in fact a wrapper around
target_link_libraries()
.
It is here to ease the use of dependencies. Without it
you would have to call
gaudi_add_*()
then
target_link_libraries()
.
Furthermore, it will make people realise that we do no longer use
LINK_LIBRARIES
or
INCLUDE_DIRS
because we do not link against libraries or use their
include directories, we link against targets that handle the transitivity themself.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
add_library(${lib_name} SHARED "${ARG_SOURCES}")
All the libraries we build are shared objects.
We could also have not specified the type of library built when calling
add_library()
and have set
BUILD_SHARED_LIBS
to TRUE.
On one hand it would have enable the user to build either static or shared
libraries but on the other hand, the function it better sefl-contained this way.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
# Public headers
if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/include)
target_include_directories(${lib_name}
PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>)
endif()
We use a generator expression to set the public header directory because
it will change after installation.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
# Dependencies
if(ARG_LINK)
target_link_libraries(${lib_name} ${ARG_LINK})
endif()
Wrapper.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
# Alias
add_library(${PROJECT_NAME}::${lib_name} ALIAS ${lib_name})
lib_name is the name of the target inside Gaudi.
Gaudi::lib_name is the name of the imported target.
We can use Gaudi::lib_name everywhere in downstream project. It will work with
an imported Gaudi and a stack build.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
# Install the library
install(
TARGETS ${lib_name}
EXPORT ${PROJECT_NAME}
INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
${_gaudi_install_optional})
We install the target and add it to an export set that has the name of the project.
This export set is used by
export(EXPORT)
or
install(EXPORT)
to generate
GaudiTargets.cmake
.
We also specify the location of public headers after the installation.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
# -- and the include directory
get_property(include_installed DIRECTORY PROPERTY include_installed)
if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/include AND NOT include_installed)
install(DIRECTORY include/
TYPE INCLUDE)
set_property(DIRECTORY PROPERTY include_installed TRUE)
endif()
We install the include directory. We use a directory property to know if
the
include/
folder of this directory has already been installed because
a sub-project may build several shared libraries (e.g.
GaudiPluginService).
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
# Runtime ROOT_INCLUDE_PATH
set_property(TARGET target_runtime_paths APPEND
PROPERTY runtime_root_include_path $<TARGET_PROPERTY:${lib_name},INTERFACE_INCLUDE_DIRECTORIES>)
endfunction()
Finally, we extend the runtime
ROOT_INCLUDE_PATH
with all the directories containing
public headers for this target. We use a generator expression because you may add
other include directories with
target_include_directories()
afterward.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
function(gaudi_add_header_only_library lib_name)
cmake_parse_arguments(
ARG
""
""
"LINK"
${ARGN}
)
add_library(${lib_name} INTERFACE)
Here we need to define an interface target because we do not have any sources.
This is CMake way to define header only libraries.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
# Public headers
if(NOT EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/include)
message(FATAL_ERROR "Cannot find public headers in include/")
endif()
target_include_directories(${lib_name}
INTERFACE $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>)
The headers.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
# Dependencies
if(ARG_LINK)
target_link_libraries(${lib_name} INTERFACE ${ARG_LINK})
endif()
Since the target is an interface target, we only have interface libraries.
This is for CMake to handle the transitivity of dependencies.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
# Alias
add_library(${PROJECT_NAME}::${lib_name} ALIAS ${lib_name})
# Install the library
install(
TARGETS ${lib_name}
EXPORT ${PROJECT_NAME}
INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
${_gaudi_install_optional})
get_property(include_installed DIRECTORY PROPERTY include_installed)
if(NOT include_installed)
install(DIRECTORY include/
TYPE INCLUDE)
set_property(DIRECTORY PROPERTY include_installed TRUE)
endif()
# Runtime ROOT_INCLUDE_PATH
set_property(TARGET target_runtime_paths APPEND
PROPERTY runtime_root_include_path $<TARGET_PROPERTY:${lib_name},INTERFACE_INCLUDE_DIRECTORIES>)
endfunction()
Same as
gaudi_add_library()
.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
function(gaudi_add_module plugin_name)
cmake_parse_arguments(
ARG
""
""
"SOURCES;LINK;GENCONF_OPTIONS"
${ARGN}
)
Here we have one more argument:
GENCONF_OPTIONS
to be able to pass options to
genconf
.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
add_library(${plugin_name} MODULE "${ARG_SOURCES}")
A module library is a plugin.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
# dependencies
if(ARG_LINK)
target_link_libraries(${plugin_name} PRIVATE ${ARG_LINK})
endif()
All the dependencies of a plugin are PRIVATE because it is meant to be loaded
dynamically.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
# install
install(
TARGETS ${plugin_name}
LIBRARY DESTINATION "${GAUDI_INSTALL_PLUGINDIR}"
${_gaudi_install_optional})
You may decide to install plugin in a different directory than shared libraries.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
# Create a symlink to the module in a directory that is added to the rpath of every target of the build
add_custom_command(TARGET ${plugin_name} POST_BUILD
BYPRODUCTS ${CMAKE_BINARY_DIR}/.plugins/${CMAKE_SHARED_MODULE_PREFIX}${plugin_name}${CMAKE_SHARED_MODULE_SUFFIX}
COMMAND ${CMAKE_COMMAND} -E create_symlink $<TARGET_FILE:${plugin_name}> ${CMAKE_BINARY_DIR}/.plugins/$<TARGET_FILE_NAME:${plugin_name}>)
This is for an easier usage at runtime because the plugins have to be found in the LD_LIBRARY_PATH.
Unfortunately, it is not possible to use generator expressions in BYPRODUCTS.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
# generate the list of components for all the plugins of the project
add_custom_command(TARGET ${plugin_name} POST_BUILD
BYPRODUCTS ${plugin_name}.components
COMMAND run $<TARGET_FILE:Gaudi::listcomponents> --output ${plugin_name}.components $<TARGET_FILE_NAME:${plugin_name}>)
Here we use
listcomponents
built by
GaudiPluginService to generate a
.components
file
of the plugin.
We run it in th runtime environment.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
_merge_files(MergeComponents "${CMAKE_BINARY_DIR}/${PROJECT_NAME}.components" # see private functions at the end
${plugin_name} "${CMAKE_CURRENT_BINARY_DIR}/${plugin_name}.components"
"Merging .components files")
All
.components
files of all plugins are merged at the end of the build into
Gaudi.components
.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
# genconf
get_filename_component(package_name ${CMAKE_CURRENT_SOURCE_DIR} NAME)
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/genConf/${package_name})
add_custom_command(TARGET ${plugin_name} POST_BUILD
BYPRODUCTS genConf/${package_name}/${plugin_name}.confdb genConf/${package_name}/${plugin_name}Conf.py # genConf/${package_name}/__init__.py
COMMAND run $<TARGET_FILE:Gaudi::genconf>
--configurable-module=GaudiKernel.Proxy
--configurable-default-name=Configurable.DefaultName
--configurable-algorithm=ConfigurableAlgorithm
--configurable-algtool=ConfigurableAlgTool
--configurable-auditor=ConfigurableAuditor
--configurable-service=ConfigurableService
${ARG_GENCONF_OPTIONS}
-o genConf/${package_name}
-p ${package_name}
-i $<TARGET_FILE:${plugin_name}>
${_gaudi_no_fail}
COMMAND_EXPAND_LISTS)
Here we create a
genConf/
directory. It will be populated by genconf outputs,
namely a
.confdb
, a
Conf.py
and an
__init__.py
.
Notice that the
__init__.py
is not listed as BYPRODUCTS because a sub-project
may build several module libraries, thus producing several
__init__.py
.
In CMake a file cannot be produced by more than one command.
We run it in the runtime environment.
By default,
genconf
works on an
AthenaProject
that is why we have the
"--configurable-"s.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
if(TARGET listcomponents) # if we are building Gaudi (this function is not called from a downstream project)
add_dependencies(${plugin_name} listcomponents genconf GaudiCoreSvc)
endif()
If we are building Gaudi (or the full stack), we need listcomponents and genconf
to be built before being used.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
if(NOT EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/python/${package_name}/__init__.py) # if there is no python package in the source tree
set(init_file "${CMAKE_CURRENT_BINARY_DIR}/genConf/${package_name}/__init__.py") # we install the __init__.py generated by genconf
endif()
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/genConf/${package_name}/${plugin_name}Conf.py
${init_file}
DESTINATION "${GAUDI_INSTALL_PYTHONDIR}/${package_name}" # Install the python module alongside the other python modules of this package
${_gaudi_install_optional})
This is needed to install the
__init__.py
file because it is not installed why the rest.
You may want to build several modules in the same sub-project.
They will all be processed by genconf
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
# Merge the .confdb files
_merge_files_confdb(${plugin_name} "${CMAKE_CURRENT_BINARY_DIR}/genConf/${package_name}/${plugin_name}.confdb") # see private functions at the end
This is different from the
.components
files because
gaudi_add_module()
is not
the only function to produce
.confdb
,
gaudi_generate_confuserdb()
does it to.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
# To append the path to the generated library to LD_LIBRARY_PATH with run
set_property(TARGET target_runtime_paths APPEND
PROPERTY runtime_ld_library_path $<SHELL_PATH:$<TARGET_FILE_DIR:${plugin_name}>>)
# To append the path to the generated Conf.py file to PYTHONPATH
set_property(TARGET target_runtime_paths APPEND
PROPERTY runtime_pythonpath $<SHELL_PATH:${CMAKE_CURRENT_BINARY_DIR}/genConf>)
endfunction()
We append to LD_LIBRARY_PATH. This should not be needed since we have a
.plugins/
folder that contains symlinks to the pluigns but for some reasons it is not enough.
We also add to the PYTHONPATH the path to the python package generated by genconf.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
function(gaudi_add_python_module module_name)
cmake_parse_arguments(
ARG
""
"PACKAGE"
"SOURCES;LINK"
${ARGN}
)
# Detect which python package is used and compile the module
# FIXME: (The day CMake can invoke functions whose name are in a variable, use a loop)
if(COMMAND Python_add_library)
Python_add_library(${module_name} MODULE ${ARG_SOURCES})
elseif(COMMAND Python3_add_library)
Python3_add_library(${module_name} MODULE ${ARG_SOURCES})
elseif(COMMAND Python2_add_library)
Python2_add_library(${module_name} MODULE ${ARG_SOURCES})
else()
message(FATAL_ERROR "Python is not available, call find_package(Python) first.")
endif()
This is needed because in CMake we may
find_package(Python|Python2|Python3)
(NB: it is recommended to use
Python
unless we need to handle both Python2 and
Python3 in the same project).
Python_add_library()
is a
add_library()
that is defined by
FindPython.cmake
and link by default to
Python::Module
.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
# dependencies
if(ARG_LINK)
target_link_libraries(${module_name} PRIVATE ${ARG_LINK})
endif()
It is a module so it only has private dependencies.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
# Add it to the runtime PYTHONPATH
set_property(TARGET target_runtime_paths APPEND
PROPERTY runtime_pythonpath $<SHELL_PATH:${CMAKE_CURRENT_BINARY_DIR}>)
# install
if(ARG_PACKAGE)
set(package_path "${ARG_PACKAGE}")
get_filename_component(package_name "${package_path}" NAME)
else()
get_filename_component(package_name "${CMAKE_CURRENT_SOURCE_DIR}" NAME)
set(package_path "${CMAKE_CURRENT_SOURCE_DIR}/python/${package_name}")
endif()
if(NOT EXISTS "${package_path}/__init__.py")
message(FATAL_ERROR "${package_path}/__init__.py"
" must exist and the package must be installed (see gaudi_install(PYTHON)).")
endif()
install(
TARGETS ${module_name}
LIBRARY DESTINATION "${GAUDI_INSTALL_PYTHONDIR}/${package_name}"
${_gaudi_install_optional})
endfunction()
We add it to the PYTHONPATH.
The compiled python module can be added to any other python package at installation.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
function(gaudi_add_executable exe_name)
cmake_parse_arguments(
ARG
"TEST"
""
"SOURCES;LINK"
${ARGN}
)
add_executable(${exe_name} "${ARG_SOURCES}")
# dependencies
if(ARG_LINK)
target_link_libraries(${exe_name} PRIVATE ${ARG_LINK})
endif()
We cannot link against an executable, so all dependencies are private.
You may use standard CMake later to enable POSITION_INDEPENDENT_CODE and add
public dependencies.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
# test
if(ARG_TEST)
get_filename_component(package_name ${CMAKE_CURRENT_SOURCE_DIR} NAME)
add_test(NAME ${package_name}.${exe_name} COMMAND run $<TARGET_FILE:${exe_name}>)
endif()
Same idea as LINK. With TEST we wrap the call to
add_test()
.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
# install
if(NOT ARG_TEST)
set(_export EXPORT ${PROJECT_NAME})
add_executable(${PROJECT_NAME}::${exe_name} ALIAS ${exe_name})
endif()
There is no need to export the tests because downstream project will probably
not use them.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
install(
TARGETS ${exe_name}
${_export}
${_gaudi_install_optional})
# Runtime PATH with run
set_property(TARGET target_runtime_paths APPEND
PROPERTY runtime_path $<SHELL_PATH:$<TARGET_FILE_DIR:${exe_name}>>)
endfunction()
We install and export the executable and add them to the runtime PATH.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
function(gaudi_add_tests type)
# Naming convention for tests: PackageName.TestName
get_filename_component(package_name ${CMAKE_CURRENT_SOURCE_DIR} NAME)
This line means
get the package name. It is used in lots of places in the code.
We do not really need a function to wrap this line.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
_get_python_interpreter(python) # see private functions at the end
# Custom test_directory
if(ARGV1)
set(test_directory "${ARGV1}")
endif()
# Test framework used
if(type STREQUAL "QMTest")
if(test_directory)
set(qmtest_root_dir "${test_directory}")
else()
set(qmtest_root_dir "${CMAKE_CURRENT_SOURCE_DIR}/tests/qmtest")
endif()
file(GLOB_RECURSE qmt_files RELATIVE "${qmtest_root_dir}" "${qmtest_root_dir}/*.qmt") # ordered lexicographically
This is unfortunate that QMTest cannot auto-detect tests like
nosetests
and
pytest
because it forces us to use a glob pattern.
It is one of the 3 places where a glob pattern is used in Gaudi.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
string(TOLOWER "${package_name}" subdir_name_lower)
file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/qmtest_tmp")
# Add a test for each qmt files
foreach(qmt_file IN LISTS qmt_files)
string(REPLACE ".qms/" "." qmt_name "${qmt_file}")
string(REPLACE ".qmt" "" qmt_name "${qmt_name}")
string(REGEX REPLACE "^${subdir_name_lower}\\." "" qmt_name "${qmt_name}")
add_test(NAME ${package_name}.${qmt_name}
COMMAND run ${python} -m GaudiTesting.Run
--skip-return-code 77
--report ctest
--common-tmpdir ${CMAKE_CURRENT_BINARY_DIR}/qmtest_tmp
--workdir ${qmtest_root_dir}
${qmtest_root_dir}/${qmt_file}
WORKING_DIRECTORY "${qmtest_root_dir}")
set_tests_properties(${package_name}.${qmt_name} PROPERTIES LABELS QMTest
SKIP_RETURN_CODE 77)
endforeach()
This is how we register a QMTest test with the right naming conventions and how
we run it with
GaudiTesting.Run
.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
# Extract dependencies to a cmake file
find_file(extract_qmtest_metadata extract_qmtest_metadata.py
PATHS ${Gaudi_SOURCE_DIR}/cmake # When building Gaudi
${Gaudi_DIR} # When using an installed Gaudi
NO_DEFAULT_PATH)
Here we look for a script defined by Gaudi. There is only 2 places were this script
can be. It is either in the source tree of Gaudi if we are building it (standalone
or full stack) or it is alongside
GaudiConfig.cmake if it was installed.
Moreover, this variable will be written in
GaudiConfig.cmake is GAUDI_BUILD_TREE_AS_INSTALL_AREA
has been given at configure time.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
if(NOT extract_qmtest_metadata)
message(FATAL_ERROR "Cannot find extract_qmtest_metadata.py")
endif()
mark_as_advanced(extract_qmtest_metadata)
execute_process(COMMAND ${python} ${extract_qmtest_metadata}
${package_name} ${qmtest_root_dir}
OUTPUT_FILE ${CMAKE_CURRENT_BINARY_DIR}/qmt_deps.cmake
RESULT_VARIABLE qmt_deps_retcode)
if(NOT qmt_deps_retcode EQUAL "0")
message(WARNING "Failed to compute dependencies of QMTest tests.")
return()
endif()
# Include the generated file with the QMTest dependencies
include(${CMAKE_CURRENT_BINARY_DIR}/qmt_deps.cmake)
qmt_deps.cmake
is generated by
extract_qmtest_metadata
it defines in CMake
language the dependencies between tests. Thus, we include it to set the dependencies
between QMTest tests.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
elseif(type STREQUAL "nosetests")
_import_nosetests() # creates the imported target nosetests for the
# generator expression $<TARGET_FILE:nosetests>
if(NOT test_directory)
set(test_directory "${CMAKE_CURRENT_SOURCE_DIR}/tests/nose")
endif()
get_filename_component(name "${test_directory}" NAME)
add_test(NAME ${package_name}.${name}
COMMAND run $<TARGET_FILE:nosetests> -v --with-doctest ${test_directory})
nosetests
is able to auto-detect the tests that are defined is a folder.
The consequence though, is that for CTest, there is only one test.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
elseif(type STREQUAL "pytest")
_import_pytest() # creates the imported target pytest for the
# generator expression $<TARGET_FILE:pytest>
if(NOT test_directory)
set(test_directory "${CMAKE_CURRENT_SOURCE_DIR}/tests/pytest")
endif()
get_filename_component(name "${test_directory}" NAME)
add_test(NAME ${package_name}.${name}
COMMAND run $<TARGET_FILE:pytest> -v --doctest-modules ${test_directory})
else()
message(FATAL_ERROR "${type} is not a valid test framework.")
endif()
endfunction()
pytest
is similar to
nosetests
. It is not used yet inside Gaudi.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
function(gaudi_add_dictionary dictionary)
cmake_parse_arguments(
ARG
""
"SELECTION"
"HEADERFILES;OPTIONS;LINK"
${ARGN}
)
# Generate sources of the dictionary
set(gensrcdict ${CMAKE_CURRENT_BINARY_DIR}/${dictionary}.cxx)
set(rootmapname ${CMAKE_CURRENT_BINARY_DIR}/${dictionary}.rootmap)
set(pcmfile ${CMAKE_CURRENT_BINARY_DIR}/${dictionary}_rdict.pcm)
add_custom_command(OUTPUT ${gensrcdict} ${rootmapname} ${pcmfile}
COMMAND run
${ROOT_genreflex_CMD} # comes from ROOTConfig.cmake
${ARG_HEADERFILES}
-o ${gensrcdict}
--rootmap=${rootmapname}
--rootmap-lib=lib${dictionary}
--select=${ARG_SELECTION}
"-I$<JOIN:$<TARGET_PROPERTY:${dictionary},INCLUDE_DIRECTORIES>,;-I>"
"-D$<JOIN:$<TARGET_PROPERTY:${dictionary},COMPILE_DEFINITIONS>,;-D>"
${ARG_OPTIONS}
DEPENDS "${ARG_HEADERFILES};${ARG_SELECTION}"
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
COMMENT "Generating ${dictionary}.cxx and ${dictionary}.rootmap and ${dictionary}_rdict.pcm"
COMMAND_EXPAND_LISTS)
Here, we run genreflex in the runtime environment.
We use generator expressions to generate a CMake-style lists of include directories
and compile definitions that are then expanded thanks to COMMAND_EXPAND_LISTS.
These lists will never be empty because the first element of each is defined later.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
add_custom_target(${dictionary}-gen ALL DEPENDS "${gensrcdict};${rootmapname};${pcmfile}")
# Build the dictionary as a plugin
add_library(${dictionary} MODULE ${gensrcdict})
if(ARG_LINK)
target_link_libraries(${dictionary} PRIVATE ${ARG_LINK})
endif()
target_include_directories(${dictionary} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_compile_definitions(${dictionary} PRIVATE GAUDI_DICTIONARY)
# Install the dictionary
install(
TARGETS ${dictionary}
LIBRARY DESTINATION "${GAUDI_INSTALL_PLUGINDIR}"
${_gaudi_install_optional})
install(FILES ${pcmfile}
DESTINATION "${GAUDI_INSTALL_PLUGINDIR}"
${_gaudi_install_optional})
# Merge all the .rootmap files
_merge_files(MergeRootmaps "${CMAKE_BINARY_DIR}/${PROJECT_NAME}Dict.rootmap" # see private functions at the end
${dictionary}-gen "${CMAKE_CURRENT_BINARY_DIR}/${dictionary}.rootmap"
"Merging .rootmap files")
endfunction()
We install the dictionary and related files and merge
.rootmap
files.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
function(gaudi_install type)
if(type STREQUAL "PYTHON")
if(ARGV1)
set(directory ${ARGV1})
else()
set(directory python/)
endif()
install(DIRECTORY ${directory}
DESTINATION "${GAUDI_INSTALL_PYTHONDIR}"
REGEX "(~|\\.py[co]|\\.in)$" EXCLUDE)
We exclude compiled python modules and configure files from the installation
because they are not python modules.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
if(NOT IS_ABSOLUTE ${directory})
set(directory ${CMAKE_CURRENT_SOURCE_DIR}/${directory})
endif()
# Generate a special __init__.py in the build tree that gather every other pieces
file(GLOB python_packages LIST_DIRECTORIES true "${directory}/*")
This is the third and last place where a glob pattern is used in Gaudi.
Consequently, the last case when we have to reconfigure is when a new python
package is added in a sub-project that already contained an installed python
package. (If it was not installed, you need to add
gaudi_install(PYTHON)
to the
CMakeLists.txt
of the sub-project.)
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
list(FILTER python_packages EXCLUDE REGEX "\\.(py[co]?|in)$")
foreach(python_package IN LISTS python_packages)
get_filename_component(python_package_name ${python_package} NAME)
if(NOT EXISTS ${CMAKE_BINARY_DIR}/python/${python_package_name}/__init__.py)
file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/python/${python_package_name})
file(WRITE ${CMAKE_BINARY_DIR}/python/${python_package_name}/__init__.py
"import os, sys
__path__ = [d for d in [os.path.join(d, '${python_package_name}') for d in sys.path if d]
if (d.startswith('${CMAKE_CURRENT_BINARY_DIR}') or
d.startswith('${CMAKE_CURRENT_SOURCE_DIR}')) and
(os.path.exists(d) or 'python.zip' in d)]
if os.path.exists('${CMAKE_CURRENT_SOURCE_DIR}/python/${python_package_name}/__init__.py'):
execfile('${CMAKE_CURRENT_SOURCE_DIR}/python/${python_package_name}/__init__.py')
")
Here we generate a
__init__.py
file in the build tree that will point to all the
scattered parts of the package.
In the future we may use python's
pkgutil
.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
endif()
endforeach()
# Runtime PYTHONPATH with run
set_property(TARGET target_runtime_paths APPEND
PROPERTY runtime_pythonpath $<SHELL_PATH:${directory}>)
elseif(type STREQUAL "SCRIPTS")
if(ARGV1)
set(directory ${ARGV1})
else()
set(directory scripts/)
endif()
install(DIRECTORY ${directory}
TYPE BIN
FILE_PERMISSIONS OWNER_READ GROUP_READ WORLD_READ # same as PROGRAMS
OWNER_WRITE
OWNER_EXECUTE GROUP_EXECUTE WORLD_EXECUTE
REGEX "(~|\\.py[co]|\\.in)$" EXCLUDE)
Here we install all files that are not compiled python modules or backup files or
configure files as PROGRAMS.
Remove the explicit list of file permissions the day
install(DIRECTORY)
has an option to install files with the same permission as
install(PROGRAMS)
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
# Runtime PATH with run
if(NOT IS_ABSOLUTE ${directory})
set(directory ${CMAKE_CURRENT_SOURCE_DIR}/${directory})
endif()
set_property(TARGET target_runtime_paths APPEND
PROPERTY runtime_path $<SHELL_PATH:${directory}>)
elseif(type STREQUAL "CMAKE")
foreach(entity IN LISTS ARGN)
Here, instead of one directory, we may install severals at once.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
if(NOT IS_ABSOLUTE ${entity})
set(entity "${CMAKE_CURRENT_SOURCE_DIR}/${entity}")
endif()
if(IS_DIRECTORY ${entity})
install(DIRECTORY "${entity}"
DESTINATION "${GAUDI_INSTALL_CONFIGDIR}")
else()
install(FILES "${entity}"
DESTINATION "${GAUDI_INSTALL_CONFIGDIR}")
endif()
endforeach()
else()
message(FATAL_ERROR "${type} is not a valid first argument for gaudi_install().")
endif()
endfunction()
We install directories as directories and files as files.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
function(gaudi_generate_confuserdb)
# Get package_name
get_filename_component(package_name ${CMAKE_CURRENT_SOURCE_DIR} NAME)
# Handle default value
if(NOT ARGV)
set(modules "${package_name}.Configuration")
else()
set(modules "${ARGV}")
endif()
# Handle options
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/genConf/${package_name})
set(output_file "${CMAKE_CURRENT_BINARY_DIR}/genConf/${package_name}/${package_name}_user.confdb")
# Add the custom target to generate the _user.confdb file
string(REPLACE "." "/" modules_path_list "${modules}")
list(TRANSFORM modules_path_list PREPEND "${CMAKE_CURRENT_SOURCE_DIR}/python/")
list(TRANSFORM modules_path_list APPEND ".py")
Here we retrieve the paths to the list of python modules.
We turn
GaudiExamples.Configuration
into
${CMAKE_CURRENT_SOURCE_DIR}/python/GaudiExamples/Configuration.py
.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
add_custom_command(OUTPUT "${output_file}"
COMMAND run genconfuser.py
-r ${CMAKE_CURRENT_SOURCE_DIR}/python
-o "${output_file}"
${package_name}
${modules}
${_gaudi_no_fail}
DEPENDS "${modules_path_list}"
COMMENT "Generating ConfigurableUser of ${package_name}"
COMMAND_EXPAND_LISTS)
add_custom_target(${package_name}_confuserdb ALL DEPENDS "${output_file}")
# Merge ${package_name}_user.confdb with the others in ${CMAKE_BINARY_DIR}/${PROJECT_NAME}.confdb
_merge_files_confdb(${package_name}_confuserdb "${output_file}") # see private functions at the end
endfunction()
We merge the
_user.confdb
files with the other
.confdb
files.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
function(gaudi_check_python_module)
_get_python_interpreter(python) # see private functions at the end
set(not_found)
foreach(module IN LISTS ARGV)
execute_process(COMMAND ${python} -c "import ${module}"
RESULT_VARIABLE returned_value
OUTPUT_QUIET ERROR_QUIET)
if(NOT returned_value EQUAL "0")
list(APPEND not_found ${module})
endif()
endforeach()
if(not_found)
message(FATAL_ERROR "The following python module(s) cannot be imported: ${not_found}.")
endif()
endfunction()
This function can be used in script mode.
Trying to import a python module it the easiest way to know if we can import it.
Here we do something like
FeatureSummary. We try everything and report the list
of missing dependencies.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
function(gaudi_generate_version_header_file)
# Handle default value
if(NOT ARGV0)
string(TOUPPER ${PROJECT_NAME} NAME)
set(output_file_name ${NAME}_VERSION.h)
install(FILES ${CMAKE_BINARY_DIR}/include/${output_file_name} TYPE INCLUDE)
else()
set(name ${ARGV0})
string(TOUPPER ${name} NAME)
set(output_file_name ${name}Version.h)
endif()
# No patch number => set to 0
if(NOT PROJECT_VERSION_PATCH)
set(PROJECT_VERSION_PATCH 0)
endif()
We are in a function so this variable is local. The real
PROJECT_VERSION_PATCH
is not modified.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
# Generate the configure file only once
file(GENERATE OUTPUT include/${output_file_name} CONTENT
"#ifndef ${NAME}_VERSION_H
#define ${NAME}_VERSION_H
#ifndef CALC_GAUDI_VERSION
#define CALC_GAUDI_VERSION(maj,min) (((maj) << 16) + (min))
#endif
#define ${NAME}_MAJOR_VERSION ${PROJECT_VERSION_MAJOR}
#define ${NAME}_MINOR_VERSION ${PROJECT_VERSION_MINOR}
#define ${NAME}_PATCH_VERSION ${PROJECT_VERSION_PATCH}
#define ${NAME}_VERSION CALC_GAUDI_VERSION(${NAME}_MAJOR_VERSION,${NAME}_MINOR_VERSION)
#endif // ${NAME}_VERSION_H")
endfunction()
Here we use
CALC_GAUDI_VERSION
instead of
CALC_${PROJECT_NAME}_VERSION
because every Gaudi-based project has the same way of computing the version
so we do not need several macros.
Now let us explain the private functions.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
# Get the python interpreter
# return_python_var: name of the returned value
function(_get_python_interpreter return_python_var)
# Check which python is available
set(python)
foreach(package IN ITEMS Python Python3 Python2)
if(${package}_Interpreter_FOUND)
set(python ${${package}_EXECUTABLE})
We use
Python[23]_EXECUTABLE
instead of Python[23]::Interpreter because this
enables the use of this function in script mode.
The configuration may use
find_package(Python)
or
find_package(Python2)
or
find_package(Python3)
, that is the reason of this loop.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
break()
endif()
endforeach()
# Display a warning if none was found
if(NOT python)
message(WARNING "No python interpreter was found, "
"call find_package(Python) first.")
endif()
# return
set(${return_python_var} ${python} PARENT_SCOPE)
endfunction()
Setting a value in the parent scope is a way of doing
return
is CMake.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
# This functions merge files generated during the build
# (.components, .rootmap, .confdb) in a single one.
# merge_target: target to merge the byproducts of other targets
# output_file: the absolute path to the file that will contain all the others
# dependency: a target on which ${merge_target} depends
# file_to_merge: the absolute path to the file to be merged in ${output_file}
# message: a message to display while building ${merge_target}
function(_merge_files merge_target output_file dependency file_to_merge message)
if(NOT TARGET ${merge_target})
add_custom_command(OUTPUT "${output_file}"
COMMAND cat $<TARGET_PROPERTY:${merge_target},fragments> > "${output_file}"
We need to have
cat
available.
We merge all the files that are "fragments" of the ultimate file.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
DEPENDS $<TARGET_PROPERTY:${merge_target},fragments>
Fortunatly we can use generator expressions in
DEPENDS
.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
COMMENT "${message}"
COMMAND_EXPAND_LISTS)
add_custom_target(${merge_target} ALL DEPENDS "${output_file}")
install(FILES "${output_file}"
TYPE LIB
${_gaudi_install_optional})
endif()
Whether is it
Gaudi.confdb
,
Gaudi.components
or
Gaudi.rootmap
they are installed
alongside the shared libraries.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
add_dependencies(${merge_target} ${dependency})
set_property(TARGET ${merge_target} APPEND PROPERTY fragments "${file_to_merge}")
This is how we add new fragments to the ultimate file: by appending to a target property.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
endfunction()
# special wrapper for genconf and genconfuser so that they always have the same merge_target
function(_merge_files_confdb dependency file_to_merge)
_merge_files(MergeConfdb "${CMAKE_BINARY_DIR}/${PROJECT_NAME}.confdb"
${dependency} "${file_to_merge}"
"Merging .confdb files")
endfunction()
This function is just here to ensure that
.confdb
and
_user.confdb
are merged in
the same file using the same target with the same message.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
# A function to factor out nosetests and pytest lookup
function(_import_runtime runtime)
if(TARGET ${runtime}) # this function can be called several times
return()
endif()
find_program(${runtime}_PROGRAM ${runtime})
mark_as_advanced(${runtime}_PROGRAM)
if(NOT ${runtime}_PROGRAM)
message(FATAL_ERROR "Unable to find ${runtime}, ${runtime} is required.")
endif()
add_executable(${runtime} IMPORTED GLOBAL)
set_target_properties(${runtime} PROPERTIES IMPORTED_LOCATION ${${runtime}_PROGRAM})
endfunction()
# This functions import nosetests and pytest to be able to use them
# in gaudi_add_tests(nosetests|pytest)
function(_import_nosetests)
_import_runtime(nosetests)
endfunction()
function(_import_pytest)
_import_runtime(pytest)
endfunction()
In principle the functions defined in this module are meant to be unsable without
any setup beforehand (except for python stuff because we cannot know which python
the user wants to use in the project).
Here
nosetests
and
pytest
will always be the same programs. We create the imported target
for
gaudi_add_tests()
. It is not an implicit dependency because if the configuration
does not contain any
gaudi_add_tests(nosetests|pytest)
it is not called.
On the other hand, if
gaudi_add_tests(nosetests|pytest)
is called then there is an
explicit dependency on
nosetests
or
pytest
.
Let us explain the private internal magic that happens in this module.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
# To generate the script run and make it executable
if(NOT CMAKE_SCRIPT_MODE_FILE AND NOT TARGET target_runtime_paths)
If we are in script mode we cannot define targets, thus this block of code is not processed.
Some functions may be used in script mode like
gaudi_check_python_module()
so it make sense to have the entire module usable in script mode.
Moreover, we cannot define a target more than once so this block of code
is not processed if a
target_runtime_paths
already exists.
This may be used to override the generation of the runtime environment.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
add_custom_target(target_runtime_paths)
This target does nothing it is only here to hold values with target properties.
It is like a hashmap.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
foreach(env_var IN ITEMS PATH LD_LIBRARY_PATH PYTHONPATH ROOT_INCLUDE_PATH)
# Clean the paths
string(REGEX REPLACE "(^:|:$)" "" _ENV_${env_var} "$ENV{${env_var}}")
string(REGEX REPLACE "::+" ":" _ENV_${env_var} "${_ENV_${env_var}}")
file(TO_CMAKE_PATH "${_ENV_${env_var}}" _ENV_${env_var})
Here we sanitize the content of PATHs environment variables and turn them
into CMake lists to be able to use the generator expression REMOVE_DUPLICATES
later on.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
endforeach()
string(TOLOWER "${CMAKE_PROJECT_NAME}env.sh" _env_file)
Each project has a different environment script name but anyway the environment
is generated only once per configuration. So if we build a full stack
the script will be named
lhcbfullstackenv.sh
and there will not be any
gaudienv.sh
nor
lhcbenv.sh
...
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
file(GENERATE OUTPUT ${CMAKE_BINARY_DIR}/${_env_file}
CONTENT "#!/bin/sh
# Auto-generated script to set environment variables
export PATH=\"$<SHELL_PATH:$<REMOVE_DUPLICATES:$<GENEX_EVAL:$<TARGET_PROPERTY:target_runtime_paths,runtime_path>>;${_ENV_PATH}>>\"
export LD_LIBRARY_PATH=\"$<SHELL_PATH:$<REMOVE_DUPLICATES:$<GENEX_EVAL:$<TARGET_PROPERTY:target_runtime_paths,runtime_ld_library_path>>;${_ENV_LD_LIBRARY_PATH}>>\"
export PYTHONPATH=\"$<SHELL_PATH:$<REMOVE_DUPLICATES:$<GENEX_EVAL:$<TARGET_PROPERTY:target_runtime_paths,runtime_pythonpath>>;${_ENV_PYTHONPATH}>>\"
$<$<NOT:$<STREQUAL:$ENV{PYTHONHOME},>>:export PYTHONHOME=\"$ENV{PYTHONHOME}\">
export ROOT_INCLUDE_PATH=\"$<SHELL_PATH:$<REMOVE_DUPLICATES:$<GENEX_EVAL:$<TARGET_PROPERTY:target_runtime_paths,runtime_root_include_path>>;${_ENV_ROOT_INCLUDE_PATH}>>\"
export ENV_CMAKE_SOURCE_DIR=\"${CMAKE_SOURCE_DIR}\"
export ENV_CMAKE_BINARY_DIR=\"${CMAKE_BINARY_DIR}\"
export ENV_CMAKE_BUILD_TYPE=\"$<CONFIG>\"
$<IF:$<NOT:$<STREQUAL:${BINARY_TAG},>>,export BINARY_TAG=\"${BINARY_TAG}\",$<$<NOT:$<STREQUAL:$ENV{BINARY_TAG},>>:export BINARY_TAG=\"$ENV{BINARY_TAG}\">>
# Other user defined commands
${RUN_SCRIPT_EXTRA_COMMANDS}
")
This is where the magic happens.
Foreach PATH-like variable, we do the same thing.
Step by step example with PATH:
-
${_ENV_PATH}
is the basis of the content of the variable. It either comes from the environment or from the toolchain. It is expanded at configure time when the module is processed (included).
-
$<TARGET_PROPERTY:target_runtime_paths,runtime_path>
contains path to targets built by the current project in the build tree. This property is filled by gaudi_add_*()
functions. It enables the syntax ./run prog
in the build tree.
-
$<GENEX_EVAL:$<TARGET_PROPERTY:target_runtime_paths,runtime_path>>
is here to tell CMake to expand the generator expression otherwise instead of the content of the property "$<TARGET_PROPERTY:target_runtime_paths,runtime_path>" would be written in the script.
-
$<GENEX_EVAL:$<TARGET_PROPERTY:target_runtime_paths,runtime_path>>;${_ENV_PATH}
we create a CMake list by concatenating the two parts. The one that points to the build tree prepend the other in order the binaries already defined in the environment not to override the newly built ones.
-
$<REMOVE_DUPLICATES:$<GENEX_EVAL:$<TARGET_PROPERTY:target_runtime_paths,runtime_path>>;${_ENV_PATH}>
we removes the duplicates if any
-
$<SHELL_PATH:$<REMOVE_DUPLICATES:$<GENEX_EVAL:$<TARGET_PROPERTY:target_runtime_paths,runtime_path>>;${_ENV_PATH}>>
This turns the CMake list into a shell ":"-separated list.
- export PATH=\"$<SHELL_PATH:$<REMOVE_DUPLICATES:$<GENEX_EVAL:$<TARGET_PROPERTY:target_runtime_paths,runtime_path>>;${_ENV_PATH}>>\" We export the new value surrounded by quotes. These quotes also help the parsing of the environment in GaudiConfig.cmake if used from the build tree.
At the end, we have
RUN_SCRIPT_EXTRA_COMMANDS
which is empty by default. It is here to
ease the convertion of the old
gaudi_env()
function in downstream projects.
It may be remove without consequences on Gaudi itself.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
# Since we cannot tell file(GENERATE) to create an executable file (at generation time)
# we create one (at configure time) that source the first one, then we make it executable
file(WRITE ${CMAKE_BINARY_DIR}/run "#!/bin/sh\n# Auto-generated script to set the environment before the execution of a command\nsource ${CMAKE_BINARY_DIR}/${_env_file}\nexec \"$@\"\n")
execute_process(COMMAND chmod a+x ${CMAKE_BINARY_DIR}/run)
The environment script is sourced by the
run
script because
file(GENERATE)
does not have any option to generate executable files.
To create the
run
at configure time, we use "\n" because for some unknown
reasons, typing plain newlines made the
execute_process()
not work
as expected.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
# Add a executable target for convenience
add_executable(run IMPORTED GLOBAL)
set_target_properties(run PROPERTIES IMPORTED_LOCATION ${CMAKE_BINARY_DIR}/run)
To be able to use "run" in
COMMAND
.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
# Add the path to the merged confdb file to LD_LIBRARY_PATH
set_property(TARGET target_runtime_paths APPEND
PROPERTY runtime_ld_library_path $<SHELL_PATH:${CMAKE_BINARY_DIR}>)
# Path to python at the top level of the build tree where the __init__.py files are
set_property(TARGET target_runtime_paths APPEND
PROPERTY runtime_pythonpath $<SHELL_PATH:${CMAKE_BINARY_DIR}/python>)
First thing we add to the properties are the folder that contains
Gaudi.components
Gaudi.confdb
and
Gaudi.rootmap
to
LD_LIBRARY_PATH
and the
python/
folder
that has the special
__init__.py
files to gather the scattered parts of packages.
This folder needs to be the first one in
PYTHONPATH
.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
# Prepend the rpath of every target with the folder containing the symlinks to the plugins
file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/.plugins)
link_directories(BEFORE ${CMAKE_BINARY_DIR}/.plugins)
endif()
Here
link_directories()
is not used to add a "-L" to the link command but
rather to prepend the
rpath
of the targets. Otherwise, they may find the plugins
they look for in the view with their
rpath
and may use the one from the wrong Gaudi
(the one installed in the view). This prevent this behavior.
In the end, let us have a look at the functions that could be standard
CMake functions but does not exist yet.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
# deprecate variables if a target exist
function(__deprecate_var_for_target var access val file)
if(access STREQUAL "READ_ACCESS" AND
(NOT file MATCHES ".*Find.+\\.cmake") AND
(NOT file MATCHES ".*Config\\.cmake") )
message(DEPRECATION "The variable ${var} is deprecated, use the target instead.")
endif()
endfunction()
# then call
# variable_watch(var_name __deprecate_var_for_target)
# for each variable that is deprecated
# => use it in every Find*.cmake
This function is meant to be used in find-module-files to entice people to use
imported targets instead of
Dep_LIBRARY
or
Dep_INCLUDE_DIR
...
Check
variable_watch()
documentation for more information.
It prints a deprecation message when a deprecated variable is accessed.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
# Silence the warning from third-party headers
function(__silence_header_warnings)
foreach(target IN LISTS ARGN)
get_target_property(inc_dir ${target} INTERFACE_INCLUDE_DIRECTORIES)
set_target_properties(${target} PROPERTIES
INTERFACE_SYSTEM_INCLUDE_DIRECTORIES "${inc_dir}")
endforeach()
endfunction()
# then call
# __silence_header_warnings(targets...)
With this function we declare that include directories of a target are also
SYSTEM include directories. Since "-isystem" wins over "-I" the headers of these
targets are treated as system headers and do not display warnings.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
# Reset the found and not found packages for FeatureSummary
# call this function after calling feature_summary(...)
function(__reset_feature_summary)
set_property(GLOBAL PROPERTY PACKAGES_FOUND "")
set_property(GLOBAL PROPERTY PACKAGES_NOT_FOUND "")
endfunction()
# strangely this function is not defined in the standard
# CMake module FeatureSummary
The
FeatureSummary
built-in CMake module is awesome but it lacks this
function. Resetting the lookup summary is mandatory otherwise downstream
project will seen Gaudi's dependencies in their dependency summary which is
not good. (Same behavior form an installed Gaudi or a build tree of Gaudi or
a full stack (in this last case required dependencies change)).
Continuous integration
SyntaxHighlightingPlugin: Language yaml is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
image: gitlab-registry.cern.ch/lhcb-core/lbdocker/centos7-build
stages:
- build
- test
variables:
CMAKE_GENERATOR: 'Ninja' # default build system
This environment variable will have this value in each job.
SyntaxHighlightingPlugin: Language yaml is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
# All the configuration
before_script:
# FIXME: When cmake 3.15 is installed on cvmfs use it
- export PATH="$PWD/cmakelocal/bin:$PATH"
- test -f cmakelocal/bin/cmake || mkdir -p cmakelocal && wget -qO- "https://github.com/Kitware/CMake/releases/download/v3.15.0/cmake-3.15.0-Linux-x86_64.tar.gz" | tar --strip-components=1 -xz -C cmakelocal
- export CMAKE_PREFIX_PATH="/cvmfs/lhcb.cern.ch/lib/contrib/ninja/1.8.2.g81279.kitware.dyndep-1.jobserver-1/Linux-x86_64:$CMAKE_PREFIX_PATH" # ninja build
- export CCACHE_DIR=$PWD/.ccache
Currently CMake 3.15 is installed on CVMFS in /cvmfs/lhcb.cern.ch but not yet
in /cvmfs/sft.cern.ch that is why this FIXME is still here.
We use the runner's cache not to have to redownload cmake in each job.
SyntaxHighlightingPlugin: Language yaml is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
# cache cmake not to re-download it for each job
cache:
paths:
- cmakelocal
# Job templates
.ccache: &template_ccache
cache:
key: "$CI_JOB_NAME-$CI_COMMIT_REF_SLUG"
paths:
- .ccache
- cmakelocal # overridden otherwise
This is to keep the ccache cache between pipelines.
SyntaxHighlightingPlugin: Language yaml is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
### Build
build.x86_64-centos7-gcc8-opt:
stage: build
script:
- cmake -S . -B build.x86_64-centos7-gcc8-opt -D CMAKE_TOOLCHAIN_FILE=cmake/toolchains/x86_64-centos7-gcc8-opt.cmake
- cmake --build build.x86_64-centos7-gcc8-opt
artifacts:
paths:
- build.x86_64-centos7-gcc8-opt/ # pass the result of the build to the test stage
expire_in: 1 day
<<: *template_ccache
build.x86_64-centos7-gcc8-dbg:
stage: build
script:
- cmake -S . -B build.x86_64-centos7-gcc8-dbg -D CMAKE_TOOLCHAIN_FILE=cmake/toolchains/x86_64-centos7-gcc8-dbg.cmake
- cmake --build build.x86_64-centos7-gcc8-dbg
artifacts:
paths:
- build.x86_64-centos7-gcc8-dbg/ # pass the result of the build to the test stage
expire_in: 1 day
<<: *template_ccache
The build jobs use toolchain files to be in the same environment each time.
We would anyway need to set the environment for each job for each pipeline.
SyntaxHighlightingPlugin: Language yaml is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
### Test build
compile_make:
stage: test
script:
- cmake -S . -B build.makefile -G "Unix Makefiles" -D CMAKE_TOOLCHAIN_FILE=cmake/toolchains/x86_64-centos7-gcc8-opt.cmake
- cmake --build build.makefile -j `nproc`
<<: *template_ccache
compile_view:
stage: test
script:
- source /cvmfs/sft.cern.ch/lcg/views/LCG_96/x86_64-centos7-gcc8-opt/setup.sh
- export CMAKE_PREFIX_PATH="$CMAKE_PREFIX_PATH:/cvmfs/sft.cern.ch/lcg/releases/LCG_95/vectorclass/1.30/x86_64-centos7-gcc8-opt"
- export PATH="$PWD/cmakelocal/bin:$PATH"
- cmake -S . -B build.view -D CMAKE_USE_CCACHE=ON
- cmake --build build.view
<<: *template_ccache
These tests are just here to make sure we are still able to build with
GNU make and to build in a view.
SyntaxHighlightingPlugin: Language yaml is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
### Test
test.x86_64-centos7-gcc8-opt:
stage: test
script:
- find build.x86_64-centos7-gcc8-opt -type f -exec touch -d $(date +@%s) \{} \; # not to re-run cmake
- cd build.x86_64-centos7-gcc8-opt
- ctest -T test --schedule-random
artifacts:
paths:
- build.x86_64-centos7-gcc8-opt/Testing/*/Test.xml
when: on_failure
expire_in: 1 week
test.x86_64-centos7-gcc8-dbg:
stage: test
script:
- find build.x86_64-centos7-gcc8-dbg -type f -exec touch -d $(date +@%s) \{} \; # not to re-run cmake
- cd build.x86_64-centos7-gcc8-dbg
- ctest -T test --schedule-random
artifacts:
paths:
- build.x86_64-centos7-gcc8-dbg/Testing/*/Test.xml
when: on_failure
expire_in: 1 week
These jobs run every test defined in Gaudi the
find
is here to make sure the
targets will not be recompiled.
They produce XML reports that can be downloaded.
WARNING: Gaudi contains async tests with reference files that may fail.
SyntaxHighlightingPlugin: Language yaml is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
### Package (almost deploy)
package:
stage: test
script:
- find build.x86_64-centos7-gcc8-opt -type f -exec touch -d $(date +@%s) \{} \; # not to re-run cmake
- cmake --build build.x86_64-centos7-gcc8-opt -t package
artifacts:
paths:
- build.x86_64-centos7-gcc8-opt/*.zip
- build.x86_64-centos7-gcc8-opt/*.rpm
expire_in: 1 week
This is pointless but it is nice to have a usable output at the end of the
pipeline (because it is the purpose of Continuous integration).
WARNING: Sometimes the pipeline fail because the compiler takes to much
resources and the runner kill it. Thus, the build system reports an error.
An error occured so the job failed so did the pipeline.
"How to"s
How to maintain the dependency lookup?
The dependency lookup is defined in
GaudiDependencies.cmake
.
Nerver forget that this file is used during Gaudi's configuration
and during Gaudi's importation in downstream projects.
If a new required dependency (Dep) is added it must be added to the list
of dependencies in the
foreach(dep)
loop that looks for them.
It is better to define the minimal required version but do this
only if a
DepConfigVersion.cmake
file exists or if the
FindDep.cmake
retrieves the version.
To do this
set(_gaudi_Dep_MIN_VERSION 1.2.3)
If the dependency does not provide a
DepConfig.cmake
file,
you must write a
FindDep.cmake
file and put it under
cmake/modules/
alongside the other find-module files or on CVMFS if they have been moved.
If an old required dependency is removed (no longer used anywhere,
no variable defined by it is used, no target defined by it are used),
remove the name of the dependency from the list of required dependencies
in the
foreach(dep)
, remove its
set(_gaudi_Dep_MIN_VERSION)
if any and
remove its find-module file from
cmake/modules/
if has not been moved
on CVMFS yet. If it is on CVMFS, other projects may still need this find-module file.
If a new third optional dependency is added do the same things as for required
dependencies but to the second
foreach(dep)
loop.
Furthermore, you need to add the option to enable/disable the dependency.
To do so, add it to the
foreach(optional_dependency)
in the top level
CMakeLists.txt
of the project.
To remove an optional dependency, remove it option in the top level
CMakeLists.txt
of the project and do the same things as for required
dependencies.
What to do when a third-party dependency provides a config-file?
Sometimes, the update does not come from the project but from the dependencies.
Remember the following rule "Once a config-file is provided by the editor
of the dependency, drop the support of your find-module and use the
official config-file."
First, check that your project is able to use the official config-file.
You may need to update the imported targets your targets link against.
Add
CONFIG
to the call to
find_package()
that looks for this dependency.
Be careful if you want to use
CMAKE_FIND_PACKAGE_PREFER_CONFIG
because the
day you update the binaries of your dependencies and a config-file is
provided without you knowing about it, you will probably have trouble
figuring out why CMake says that the imported target of your dependency
does not exist.
How to update a find-module file?
We are going to show this with the example of
CppUnit
.
Initial find-module file:
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
# - Locate CppUnit library
# Defines:
#
# CPPUNIT_FOUND
# CPPUNIT_INCLUDE_DIR
# CPPUNIT_INCLUDE_DIRS (not cached)
# CPPUNIT_LIBRARY
# CPPUNIT_LIBRARIES (not cached)
find_path(CPPUNIT_INCLUDE_DIR cppunit/Test.h)
find_library(CPPUNIT_LIBRARY NAMES cppunit)
set(CPPUNIT_INCLUDE_DIRS ${CPPUNIT_INCLUDE_DIR})
# handle the QUIETLY and REQUIRED arguments and set CPPUNIT_FOUND to TRUE if
# all listed variables are TRUE
INCLUDE(FindPackageHandleStandardArgs)
FIND_PACKAGE_HANDLE_STANDARD_ARGS(CppUnit DEFAULT_MSG CPPUNIT_INCLUDE_DIR CPPUNIT_LIBRARY)
mark_as_advanced(CPPUNIT_FOUND CPPUNIT_INCLUDE_DIR CPPUNIT_LIBRARY)
set(CPPUNIT_LIBRARIES ${CPPUNIT_LIBRARY} ${CMAKE_DL_LIBS})
To update it and take advantage of imported targets (and transitivity of dependencies),
there is no need to deeply understand this file. We will mainly use the documentation
at the beginning. Anyway, even if you are proud of your find-module file, remember
that it needs to work until a config-file comes out not until the end of the world.
We want to define an imported target that will spare us the hassle to link against
a shared object and to add external include directories to our targets.
Since the documentation is very useful, the first thing to do is to update it
by appending a block describing the imported target.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
# Imports:
#
# CppUnit::cppunit
#
# Usage of the target instead of the variables is advised
Then (I am still wondering if it is such a good idea) add a block
to silence the lookup if it cannot change. Use any cached variable.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
# Find quietly if already found before
if(DEFINED CACHE{CPPUNIT_INCLUDE_DIR})
set(${CMAKE_FIND_PACKAGE_NAME}_FIND_QUIETLY YES)
endif()
Finally, jump to the end of the file and add the imported target. Make sure
the imported target is not yet defined. Create an
IMPORTED INTERFACE
target
and use
target_include_directories()
,
target_link_libraries()
,
target_compile_definitions()
,
target_compile_options()
... as you
would have done without the imported target onto your targets. Be nice to the
user and display that an imported target exists (if not QUIET).
In Gaudi the imported target is named
PackageName::packagename
unless there
is a better name.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
# Modernisation: create an interface target to link against
if(TARGET CppUnit::cppunit)
return()
endif()
if(CPPUNIT_FOUND)
add_library(CppUnit::cppunit IMPORTED INTERFACE)
target_include_directories(CppUnit::cppunit SYSTEM INTERFACE "${CPPUNIT_INCLUDE_DIRS}")
target_link_libraries(CppUnit::cppunit INTERFACE "${CPPUNIT_LIBRARIES}")
# Display the imported target for the user to know
if(NOT ${CMAKE_FIND_PACKAGE_NAME}_FIND_QUIETLY)
message(STATUS " Import target: CppUnit::cppunit")
endif()
endif()
To polish your file, now that you have an imported target, you may
deprecate the variables with the private function of
GaudiToolbox.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
if(COMMAND __deprecate_var_for_target)
foreach(v IN ITEMS CPPUNIT_INCLUDE_DIR CPPUNIT_INCLUDE_DIRS CPPUNIT_LIBRARY CPPUNIT_LIBRARIES)
variable_watch(${v} __deprecate_var_for_target)
endforeach()
endif()
The final find-module looks like this:
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
# - Locate CppUnit library
# Defines:
#
# CPPUNIT_FOUND
# CPPUNIT_INCLUDE_DIR
# CPPUNIT_INCLUDE_DIRS (not cached)
# CPPUNIT_LIBRARY
# CPPUNIT_LIBRARIES (not cached)
#
# Imports:
#
# CppUnit::cppunit
#
# Usage of the target instead of the variables is advised
# Find quietly if already found before
if(DEFINED CACHE{CPPUNIT_INCLUDE_DIR})
set(${CMAKE_FIND_PACKAGE_NAME}_FIND_QUIETLY YES)
endif()
find_path(CPPUNIT_INCLUDE_DIR cppunit/Test.h)
find_library(CPPUNIT_LIBRARY NAMES cppunit)
set(CPPUNIT_INCLUDE_DIRS ${CPPUNIT_INCLUDE_DIR})
# handle the QUIETLY and REQUIRED arguments and set CPPUNIT_FOUND to TRUE if
# all listed variables are TRUE
INCLUDE(FindPackageHandleStandardArgs)
FIND_PACKAGE_HANDLE_STANDARD_ARGS(CppUnit DEFAULT_MSG CPPUNIT_INCLUDE_DIR CPPUNIT_LIBRARY)
mark_as_advanced(CPPUNIT_FOUND CPPUNIT_INCLUDE_DIR CPPUNIT_LIBRARY)
set(CPPUNIT_LIBRARIES ${CPPUNIT_LIBRARY} ${CMAKE_DL_LIBS})
# Modernisation: create an interface target to link against
if(TARGET CppUnit::cppunit)
return()
endif()
if(CPPUNIT_FOUND)
add_library(CppUnit::cppunit IMPORTED INTERFACE)
target_include_directories(CppUnit::cppunit SYSTEM INTERFACE "${CPPUNIT_INCLUDE_DIRS}")
target_link_libraries(CppUnit::cppunit INTERFACE "${CPPUNIT_LIBRARIES}")
# Display the imported target for the user to know
if(NOT ${CMAKE_FIND_PACKAGE_NAME}_FIND_QUIETLY)
message(STATUS " Import target: CppUnit::cppunit")
endif()
endif()
if(COMMAND __deprecate_var_for_target)
foreach(v IN ITEMS CPPUNIT_INCLUDE_DIR CPPUNIT_INCLUDE_DIRS CPPUNIT_LIBRARY CPPUNIT_LIBRARIES)
variable_watch(${v} __deprecate_var_for_target)
endforeach()
endif()
If the dependencies contains several libraries, you just need to create several
imported targets. See
Findgperftools.cmake
to have an example.
One may think it is not a good idea to define an imported target in a find-module
because the day a config-file is provided it may use different names for its
imported targets. This is true but I think it is easier to do a find-and-replace
onto the imported target name to update it everywhere than having to remove
every
target_include_directories()
,
target_*()
or
include_directories()
or
link_libraries()
of a project.
How to modernize a sub-project?
All the functions that do no longer exist needs either to be removed or changed
to perform the same action with modern CMake following good practices.
All the functions that were modernize and still exist but with a different signature
need to be updated to the new signature.
The main modifications due to the modernization are:
- no topological sort at the beginning of the configuration, thus we removed
gaudi_subdir()
and gaudi_depend_on_subdir()
- no
gaudi_project()
, we use project()
- centralized dependency lookup
- explicit optional dependencies, we use
if(GAUDI_USE_CLHEP)
instead of if(CLHEP_FOUND)
- CMake is not meant be set the test environment, we no longer use
gaudi_env()
we use VAR=value
in .qmt
files.
What if a target defined upstream is overridden by a downstream project?
I have never seen this yet but I was told it may happened.
Change:
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
gaudi_add_library(MyTarget ...)
To:
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
set(_prefix)
if(TARGET MyTarget) # if we build the full stack and the target already exists elsewhere
set(_prefix ${PROJECT_NAME}::)
endif()
gaudi_add_library(${_prefix}MyTarget ...)
set_target_properties(${_prefix}MyTarget PROPERTIES OUTPUT_NAME MyTarget) # to keep the same name (e.g. libMyTarget.so)
Example: LHCb/GaudiGSL
We are going to modernize
GaudiGSL
which is a sub-project of
LHCb
step by step.
Initial
CMakeLists.txt
:
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
gaudi_subdir(GaudiGSL)
gaudi_depends_on_subdirs(GaudiAlg GaudiUtils)
find_package(CLHEP)
find_package(GSL)
find_package(ROOT REQUIRED)
find_package(Boost REQUIRED)
# If any of the non-essential externals is missing, give up on building
# these targets.
if( NOT CLHEP_FOUND OR NOT GSL_FOUND )
message( STATUS "Requirements for GaudiGSL not found. "
"Not building package" )
return()
endif()
# Hide some CLHEP/ROOT compile time warnings
include_directories(SYSTEM ${CLHEP_INCLUDE_DIRS} ${ROOT_INCLUDE_DIRS} ${Boost_INCLUDE_DIRS})
#---Libraries---------------------------------------------------------------
gaudi_add_library(GaudiGSLLib src/Lib/*.cpp
LINK_LIBRARIES GaudiAlgLib GSL CLHEP
INCLUDE_DIRS GSL CLHEP
PUBLIC_HEADERS GaudiGSL GaudiMath)
gaudi_add_module(GaudiGSL src/Components/*.cpp
LINK_LIBRARIES GaudiGSLLib)
gaudi_add_module(GaudiGSLExamples examples/src/*.cpp
LINK_LIBRARIES GaudiGSLLib)
#---Dictionaries------------------------------------------------------------
gaudi_add_dictionary(GaudiGSLMath dict/GaudiGSLMath.h dict/GaudiGSLMath.xml LINK_LIBRARIES GaudiGSLLib)
#---Executables-------------------------------------------------------------
macro(add_gsl_unit_test name)
gaudi_add_unit_test(${name} src/Tests/${name}.cpp LINK_LIBRARIES GaudiGSLLib GaudiUtilsLib)
endmacro()
foreach(test IntegralInTest DerivativeTest 2DoubleFuncTest GSLAdaptersTest
PFuncTest ExceptionsTest SimpleFuncTest 3DoubleFuncTest InterpTest
Integral1Test)
add_gsl_unit_test(${test})
endforeach()
Let us go!
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
gaudi_subdir(GaudiGSL)
gaudi_depends_on_subdirs(GaudiAlg GaudiUtils)
These lines are now useless. We can remove them
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
find_package(CLHEP)
find_package(GSL)
find_package(ROOT REQUIRED)
find_package(Boost REQUIRED)
# If any of the non-essential externals is missing, give up on building
# these targets.
if( NOT CLHEP_FOUND OR NOT GSL_FOUND )
message( STATUS "Requirements for GaudiGSL not found. "
"Not building package" )
return()
endif()
These third party dependencies (CLHEP, GSL, ROOT, Boost) must be moved to
LHCbDependencies.cmake
using
FeatureSummary
as in
GaudiDependencies.cmake
.
We then see that GSL and CLHEP should be required because you cannot build
without them.
So
LHCbDependencies.cmake
looks more and less like this:
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
# Lookup of the dependencies of LHCb
if(LHCB_DEPENDENCIES_FIND_QUIETLY)
set(__quiet QUIET)
endif()
include(FeatureSummary)
set(FeatureSummary_REQUIRED_PKG_TYPES RECOMMENDED REQUIRED)
### Required
# Gaudi
if(NOT TARGET Gaudi::GaudiPluginService) # if not full stack project
find_package(Gaudi 32.0 ${__quiet})
set_package_properties(Gaudi PROPERTIES TYPE REQUIRED)
endif()
# Boost
set(Boost_USE_STATIC_LIBS OFF)
set(OLD_BUILD_SHARED_LIBS ${BUILD_SHARED_LIBS}) # FIXME: the day BoostConfig.cmake handles Boost_USE_STATIC_LIBS correctly
set(BUILD_SHARED_LIBS ON)
find_package(Boost 1.70 ${__quiet} CONFIG)
set_package_properties(Boost PROPERTIES TYPE REQUIRED)
set(BUILD_SHARED_LIBS ${OLD_BUILD_SHARED_LIBS})
# ROOT
find_package(ROOT 6.18 ${__quiet} CONFIG)
set_package_properties(ROOT PROPERTIES TYPE REQUIRED)
# FIXME: the day ROOTConfig.cmake displays at least that it was found remove these lines
if(NOT LHCB_DEPENDENCIES_FIND_QUIETLY)
message(STATUS "Found ROOT: ${ROOT_DIR} (found version ${ROOT_VERSION})")
endif()
# Others required dependencies
foreach(dep IN ITEMS CLHEP GSL)
find_package(${dep} ${_lhcb_${dep}_MIN_VERSION} ${__quiet})
set_package_properties(${dep} PROPERTIES TYPE REQUIRED)
endforeach()
# Print a summary of the lookup
feature_summary(FATAL_ON_MISSING_REQUIRED_PACKAGES
DESCRIPTION "Missing LHCb's required dependencies:"
QUIET_ON_EMPTY
WHAT REQUIRED_PACKAGES_NOT_FOUND)
feature_summary(DESCRIPTION "LHCb's required optional dependencies found:"
QUIET_ON_EMPTY
WHAT RECOMMENDED_PACKAGES_FOUND)
feature_summary(FATAL_ON_MISSING_REQUIRED_PACKAGES
DESCRIPTION "Missing LHCb's required optional dependencies:"
QUIET_ON_EMPTY
WHAT RECOMMENDED_PACKAGES_NOT_FOUND)
# Reset this summary not to mess with downstream projects look up
if(COMMAND __reset_feature_summary)
__reset_feature_summary()
endif()
# Silence warnings from dependencies
if(COMMAND __silence_header_warnings)
__silence_header_warnings(Boost::headers ROOT::Core GSL::gsl)
endif()
Then we stumble across:
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
# Hide some CLHEP/ROOT compile time warnings
include_directories(SYSTEM ${CLHEP_INCLUDE_DIRS} ${ROOT_INCLUDE_DIRS} ${Boost_INCLUDE_DIRS})
It is a very bad practice. It is already handled in
LHCbDependencies.cmake
.
So we drop these lines.
Then we have the part where the binaries are declared.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
#---Libraries---------------------------------------------------------------
gaudi_add_library(GaudiGSLLib src/Lib/*.cpp
LINK_LIBRARIES GaudiAlgLib GSL CLHEP
INCLUDE_DIRS GSL CLHEP
PUBLIC_HEADERS GaudiGSL GaudiMath)
gaudi_add_module(GaudiGSL src/Components/*.cpp
LINK_LIBRARIES GaudiGSLLib)
gaudi_add_module(GaudiGSLExamples examples/src/*.cpp
LINK_LIBRARIES GaudiGSLLib)
#---Dictionaries------------------------------------------------------------
gaudi_add_dictionary(GaudiGSLMath dict/GaudiGSLMath.h dict/GaudiGSLMath.xml LINK_LIBRARIES GaudiGSLLib)
These function still exist but do not have the same signature.
We will replace glob patterns with the explicit list of sources.
We will use targets instead of
LINK_LIBRARIES
and
INCLUDE_DIRECTORIES
.
Finally we will move the folders
GaudiGSL/
and
GaudiMath/
into
include/
.
Run:
cd GaudiGSL # inside LHCb
mkdir include
mv GaudiGSL/ GaudiMath/ include/
Then write:
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
# Library
gaudi_add_library(GaudiGSLLib
SOURCES
src/Lib/Adapter.cpp
src/Lib/Adapters.cpp
src/Lib/Constant.cpp
src/Lib/GaudiGSL.cpp
src/Lib/GslErrorHandlers.cpp
src/Lib/GSLFunAdapters.cpp
src/Lib/Helpers.cpp
src/Lib/Integral.cpp
src/Lib/Interpolation.cpp
src/Lib/NumericalDefiniteIntegral.cpp
src/Lib/NumericalDerivative.cpp
src/Lib/NumericalIndefiniteIntegral.cpp
src/Lib/Splines.cpp
LINK
PUBLIC
Gaudi::GaudiAlgLib
GSL::gsl
CLHEP::CLHEP)
# Plugin
gaudi_add_module(GaudiGSL
SOURCES
src/Components/EqSolver.cpp
src/Components/GslErrorCount.cpp
src/Components/GslErrorPrint.cpp
src/Components/FuncMinimum.cpp
src/Components/GslErrorException.cpp
src/Components/GslSvc.cpp
LINK GaudiGSLLib)
gaudi_add_module(GaudiGSLExamples
SOURCES
examples/src/EqSolverGenAlg.cpp
examples/src/EqSolverPAlg.cpp
examples/src/FuncMinimumIAlg.cpp
examples/src/EqSolverIAlg.cpp
examples/src/FuncMinimumGenAlg.cpp
examples/src/FuncMinimumPAlg.cpp
LINK GaudiGSLLib)
# Dictionary
gaudi_add_dictionary(GaudiGSLMathDict
HEADERFILES dict/GaudiGSLMath.h
SELECTION dict/GaudiGSLMath.xml
LINK GaudiGSLLib)
In the end, we notice a bunch of test that need to be compiled (not python tests).
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
#---Executables-------------------------------------------------------------
macro(add_gsl_unit_test name)
gaudi_add_unit_test(${name} src/Tests/${name}.cpp LINK_LIBRARIES GaudiGSLLib GaudiUtilsLib)
endmacro()
foreach(test IntegralInTest DerivativeTest 2DoubleFuncTest GSLAdaptersTest
PFuncTest ExceptionsTest SimpleFuncTest 3DoubleFuncTest InterpTest
Integral1Test)
add_gsl_unit_test(${test})
endforeach()
Defining a macro here is useless, we can just use the iterator of the loop.
gaudi_add_unit_test()
was removed, now we use
gaudi_add_executable(... TEST)
.
In modern CMake,
foreach()
should specify
IN ITEMS
or
IN LISTS
.
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
foreach(test IN ITEMS IntegralInTest DerivativeTest 2DoubleFuncTest GSLAdaptersTest
PFuncTest ExceptionsTest SimpleFuncTest 3DoubleFuncTest InterpTest
Integral1Test)
gaudi_add_executable(${test}
SOURCES src/Tests/${test}.cpp
LINK GaudiGSLLib
Gaudi::GaudiUtilsLib
TEST)
endforeach()
Final
CMakeLists.txt
:
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
# Library
gaudi_add_library(GaudiGSLLib
SOURCES
src/Lib/Adapter.cpp
src/Lib/Adapters.cpp
src/Lib/Constant.cpp
src/Lib/GaudiGSL.cpp
src/Lib/GslErrorHandlers.cpp
src/Lib/GSLFunAdapters.cpp
src/Lib/Helpers.cpp
src/Lib/Integral.cpp
src/Lib/Interpolation.cpp
src/Lib/NumericalDefiniteIntegral.cpp
src/Lib/NumericalDerivative.cpp
src/Lib/NumericalIndefiniteIntegral.cpp
src/Lib/Splines.cpp
LINK
PUBLIC
Gaudi::GaudiAlgLib
GSL::gsl
CLHEP::CLHEP)
# Plugin
gaudi_add_module(GaudiGSL
SOURCES
src/Components/EqSolver.cpp
src/Components/GslErrorCount.cpp
src/Components/GslErrorPrint.cpp
src/Components/FuncMinimum.cpp
src/Components/GslErrorException.cpp
src/Components/GslSvc.cpp
LINK GaudiGSLLib)
gaudi_add_module(GaudiGSLExamples
SOURCES
examples/src/EqSolverGenAlg.cpp
examples/src/EqSolverPAlg.cpp
examples/src/FuncMinimumIAlg.cpp
examples/src/EqSolverIAlg.cpp
examples/src/FuncMinimumGenAlg.cpp
examples/src/FuncMinimumPAlg.cpp
LINK GaudiGSLLib)
# Dictionary
gaudi_add_dictionary(GaudiGSLMathDict
HEADERFILES dict/GaudiGSLMath.h
SELECTION dict/GaudiGSLMath.xml
LINK GaudiGSLLib)
# Tests
foreach(test IN ITEMS IntegralInTest DerivativeTest 2DoubleFuncTest GSLAdaptersTest
PFuncTest ExceptionsTest SimpleFuncTest 3DoubleFuncTest InterpTest
Integral1Test)
gaudi_add_executable(${test}
SOURCES src/Tests/${test}.cpp
LINK GaudiGSLLib
Gaudi::GaudiUtilsLib
TEST)
endforeach()
How to install Gaudi even if it was built with its build tree as install area or in a full stack?
If Gaudi was build with
GAUDI_BUILD_TREE_AS_INSTALL_AREA=ON
,
GaudiConfig.cmake
is not
relocatable. If Gaudi was build as part of a stack project,
GaudiConfig.cmake
does not
exist. However the binaries do not need to be recompiled to be installed.
Thus, if you used
GAUDI_BUILD_TREE_AS_INSTALL_AREA=ON
, you just need to reconfigure
Gaudi with
GAUDI_BUILD_TREE_AS_INSTALL_AREA=OFF
, the build system should not have
any target to rebuild.
If Gaudi was build as part of a stack, configure Gaudi alone with the build tree of
the stack as Gaudi's build tree:
cmake -S Gaudi -B build.$BINARY_TAG/Gaudi
and
cmake --install build.$BINARY_TAG/Gaudi
.
How to override the generation of the runtime environment?
The runtime environment is generated by
GaudiToolbox only if no target called
target_runtime_paths
exist when it is included.
So to override the generation of the build environment, you need to
add_custom_target(target_runtime_paths)
before
include(GaudiToolbox)
or
find_package(Gaudi)
, then the block of code that generate the
run
script
will not be processed. You will be free to define whatever you want to generate
the runtime environment. Moreover, the
gaudi_add_*()
will continue to append
paths to
target_runtime_paths
target properties that you will be able to use
to generate the runtime environment.
If you just need to add stuff in the runtime environment do not bother with
overridding it completely. Use
set(RUN_SCRIPT_EXTRA_COMMANDS "${RUN_SCRIPT_EXTRA_COMMANDS}\nexport VAR=value\nexport SOME_PATH=/path/to/something:$SOME_PATH")
.
What if the support for IDEs is dropped or IDE's CMake generator support glob patterns?
We may drop the explicit list of sources and enable the use of glob pattern forwarded to the build system
with
file(GLOB|GLOB_RECURSE ... CONFIGURE_DEPENDS)
. This way no one would neither need to
add a source file to the list of sources each time a new one is added nor
touch CMakeLists.txt
to trigger the reconfiguration.
Appendix
CMake memento
SyntaxHighlightingPlugin: Language md is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
# CMake
**CMake** is a build management software supported by Kitware.
[Official webpage](https://cmake.org/) and
[Official documetation](https://cmake.org/cmake/help/latest/)
It aims at generating compilation scripts like `makefile` or `build.ninja` or
`project.sln`...
CMake does not compile anything alone, it needs a build system (like `make`)
and a compiler (like `gcc`) to build a software. Moreover, CMake is not a
dependency manager (like `maven`).
CMake is bundled with other tools, namely, **CTest**
to run unit tests and **CPack** to package software.
## Concepts
- *CMakeLists.txt*: is the file CMake looks for when launched. It contains all
the instructions CMake must follow to generate compilation scripts.
- **generator**: a generator is the build system cmake produce files for (e.g.
make, ninja, Visual Studio, Xcode...).
[Official list](https://cmake.org/cmake/help/latest/manual/cmake-generators.7.html)
- **target**: the result of a compilation: executable or library. Each target has its on properties.
Analogy with OOP: target=object, add_library/executable=constructor,
properties=attributes, (get/set_)target_*=methods
It is possible to set the default value of every property by setting `CMAKE_property_name`
- Visibility:
- **PUBLIC**: public properties are needed to compile the target and targets which depend on it.
- **PRIVATE**: private properties are only needed to compile the target
- **INTERFACE**: interface properties are not needed to compile the target but targets which
depend on it will need them
- declaring something PRIVATE will populate a property of the target, declaring something
INTERFACE will populate a INTERFACE_property of the target, declaring something PUBLIC will
populate both.
- Steps of compilation with CMake:
1. The cache is loaded.
1. If there is a toolchain it is processed.
1. **Configure time**: when CMake process "CMakeLists.txt".
1. **Generation time**: when generator expressions are expanded and files (build files
and files created with `configure_file()` or `file(GENERATE)`) are generated.
1. **Build time**: when the build system (make, ninja...) is called and the compiler is run.
1. **Install time** (optional): when the target _install_ (or _package_) is called (e.g. make install)
- The **toolchain** is the set of tools used to build a project (compilers, ...).
- CMake will assume that a directory is a **source tree** (directory that contains
the sources) if it contains a *CMakeLists.txt* and will assume that a directory is a
**build tree** (directory that contains the products of the build)
if it contains a *CMakeCache.txt*. That is why it is recommended
to do an **out-of-source build** (source tree != build tree).
## Syntax
### Basics
- **comments**: `# I am a single line comment`
```cmake
#[===[
I am a multi line comment
#]===]
```
- **variables**: are all _string_ typed, but can also be lists
- `set(var_name a_value)` to set a value
- `${var_name}` to use the value
- if the value may contain spaces, the variable be quoted: `"${var}"`
- even this works: `set(${var_name}_abc 1)` ==> ${a_value_abc} is 1
- About scopes :
> When a variable that is already set is set again in a
> subdirectory it overrides the value in that scope and any
> deeper subdirectories but not in the parent scope.
> Loop and control flow blocks do not have their own scopes.
`set(var value PARENT_SCOPE)` will set a variable into the parent scope but not
the current one.
- **Cached variables**: can be defined from the command line (`cmake -DVAR=val`)
and are stored in a text file (CMakeCache.txt) so that CMake can
remember them between executions. They are visible in all scopes.
```cmake
set(MY_CACHED_VARIABLE "DEFAULT_VALUE" CACHE STRING "Description")
option(MY_CACHED_BOOLEAN_VARIABLE "something that can be enabled" OFF) # shortcut for BOOL
```
Default values are used when the variable if not defined by the command line.
`set(... FORCE)` will always set the value to its default unless it is defined
by the command line. It is also the only way to override CMake variables default values.
- **Environnement variables**: `$ENV{HOME}` to get the value
and `set(ENV{HOME} value)` to modify the value only in
CMake (the environment outside CMake is not modified).
- **Properties**: are variables attached to a target (or test, directory...)
`set_target_properties(TargetName PROPERTIES PROP_NAME value)`,
`set_property(TARGET target1 target2 PROPERTY PROP_NAME value)`,
`get_property(result_var TARGET target1 PROPERTY PROP_NAME)`
- **Lists**: `set(my_list 1 2 3 4)` is the same as `set(my_list "1;2;3;4")`
but is different as `set(str "1 2 3 4")` which is a string.
List of lists are possible.
- _Tip_: use `separate_arguments(var)` to turn a string with spaces
into a list.
- **Print on output**: `message([mode] "hello world!")` where `mode` is
- FATAL_ERROR, SEND_ERROR (displayed on stderr, prevent the generation of build files)
- WARNING, AUTHOR_WARNING, DEPRECATION, NOTICE (displayed on stderr)
- STATUS, VERBOSE, DEBUG or TRACE (displayed on stdout)
- **NB**: `set(CMAKE_ERROR_DEPRECATED TRUE)` to treat deprecation messages as errors.
- **if** blocks:
```cmake
if(<condition>)
# something
elseif(<condition>)
# something
else()
# something
endif()
```
list of operators: [https://cmake.org/cmake/help/latest/command/if.html](https://cmake.org/cmake/help/latest/command/if.html)
- **foreach loops**:
```cmake
foreach(var IN ITEMS 1 2 3)
message(${var})
endforeach()
foreach(var IN LISTS my_list ITEMS 5 6 final_value)
message(${var}) # 7 values printed
endforeach()
```
- `while()`, `endwhile()`, `break()`, `continue()` also exist
- **Generator-expressions**: are evaluated at **build time** (or **install time**)
by the build system (make, ninja, ...).
- *Boolean generator*: `$<EQUAL:val1,val2>` will be replaced by `TRUE` if val1==val2
- *String-valued generator*: `"$<BOOL_VAR:value>"` will be replaced by value if BOOL_VAR is true.
```cmake
target_include_directories(my_target PUBLIC
$<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
)
```
`$<IF:cond,valueIfTrue,valueIfFalse>`, `$<TARGET_FILE_NAME:target>`...
Calls to generator expressions can be nested.
For the complete list see [https://cmake.org/cmake/help/latest/manual/cmake-generator-expressions.7.html](https://cmake.org/cmake/help/latest/manual/cmake-generator-expressions.7.html).
### Functions, macros and modules
- **functions**: are always with variable number of arguments
and have their own scope of variables.
They do not return any value.
```cmake
function(func_name arg1 arg_ret) # and unnamed parameters
message(${arg1})
# the list of unnamed parameters are stored in the list ${ARGN}
set(${arg_ret} "a returned value" PARENT_SCOPE) # to fake return values
endfunction()
```
- Argument parsing:
```cmake
function(my_function)
cmake_parse_arguments(
MY_FUNCTION_PREFIX
"BOOL_VAR1;BOOL_VAR2"
"SINGLE_VAR1;SINGLE_VAR2;SINGLE_VAR3"
"LIST_VAR1;LIST_VAR2"
${ARGN}
)
endfunction()
my_function(BOOL_VAR1 SINGLE_VAR2 value LIST_VAR1 some other values)
# inside the function
MY_FUNCTION_PREFIX_BOOL_VAR1 = TRUE
MY_FUNCTION_PREFIX_BOOL_VAR2 = FALSE
MY_FUNCTION_PREFIX_SINGLE_VAR1 = <UNDEFINED>
MY_FUNCTION_PREFIX_SINGLE_VAR2 = value
MY_FUNCTION_PREFIX_SINGLE_VAR3 = <UNDEFINED>
MY_FUNCTION_PREFIX_LIST_VAR1 = "some;other;values"
MY_FUNCTION_PREFIX_LIST_VAR2 = <UNDEFINED>
```
- **macros**: do not have their own scope and will spread their
variables in the parent scope. Be very careful.
- **modules**: are files that contain cmake code and can be included with `include()`
**NB**: Use macros to wrap commands that have output parameters,
otherwise create a function.
**NB**: How to deprecate functions and variables
```cmake
# deprecate the function/macro my_cmd
macro(my_cmd)
message(DEPRECATION "This command is deprecated.")
_my_cmd(${ARGV})
endmacro()
# deprecate any variable
function(__deprecated_var var access val file stack)
if(access STREQUAL "READ_ACCESS")
message(DEPRECATION "The variable ${var} is deprecated")
endif()
endfunction()
variable_watch(var_name __deprecated_var)
```
Note that an overridden function can still be called with "_name()".
```cmake
# Caveats of overriding
function(my_func) #1
endfunction()
function(my_func) #2
_my_func() # calls 2 recursively (not 1)
endfunction()
function(my_func) #3
_my_func() # calls 2
endfunction()
my_func() # calls 3
```
**NB**: When creating a function that add targets to the build,
instead of having lots of parameters, use custom target properties
and expand them in the command line with `$<TARGET_PROPERTY:tgt,prop>`
and possibly `COMMAND_EXPAND_LISTS`. This way it is possible to set things
after the function call (like with `target_*()`).
### Running commands
- at configure time (when CMakeLists.txt is parsed):
`execute_process(COMMAND echo hello)`
to display the command before it is run (for debugging),
`execute_process(COMMAND <cmd> COMMAND_ECHO <STDOUT|STDERR>)`
- at build time (when the targets are built):
- produce a file (that may then be used as a source (dependency) to build other targets):
`add_custom_command(OUTPUT path/to/a/file COMMAND echo hello DEPENDS file1 file2)`
- add a new target to the build system:
`add_custom_target(my_target ALL DEPENDS path/to/a/file COMMENT "Building my_target")`
- add a build command to a target production:
`add_custom_command(TARGET a_target POST_BUILD COMMAND echo hello)`
- **NB**: custom targets are always considered out of date. Never use the
`COMMAND` option of `add_custom_target()` but use `add_custom_command()`
and the `DEPENDS` option of `add_custom_target()`.
### Add information to files at configure time
Fill some "template files" with values of CMake variables. It can be any
type of file: headers, cmake modules, docs, scripts...
In a file with ".in" extension (like stuff.h.in)
```
#pragma once
// @useful_comment@
#define VAR @VAR@
#cmakedefine OPTIONAL_VAR @OPTIONAL_VAR@
#cmakedefine01 @OPTIONAL_VAR@
```
In CMakeLists.txt
```cmake
set(useful_comment "Hey, I'm a comment!")
set(VAR 123)
configure_file("${PROJECT_SOURCE_DIR}/include/myLib/stuff.h.in"
"${PROJECT_BINARY_DIR}/include/myLib/stuff.h")
```
It will generate (in the build tree)
```c
#pragma once
// Hey, I'm a comment!
#define VAR 123
/* #undef OPTIONAL_VAR */
#define OPTIONAL_VAR 0
```
:warning: If the generated file is a header, then the path to
the generated file in the build tree must be added to the
include directories of the targets if any (with `target_include_directories()`)
## Project architecture
```
- project ==> name of the project (root folder)
- .gitignore
- README.md
- LICENCE
- CMakeLists.txt ==> file to configure the project
- cmake ==> directory containing cmake code (to find libraries, ...)
- FindSomeLib.cmake
- include ==> the content of this folder could be copy-pasted to /usr/include
- project
- lib.hpp
- src
- lib.cpp ==> sources of the library
- tests
- test1source.cpp
- test2script.sh
- docs
- Doxyfile.in
- extern
- googletest_gitsubmodule
- scripts
- helper.py
```
Use `set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake" ${CMAKE_MODULE_PATH})` so that
".cmake" files can be included with `include(filename)`.
If the project contains sub-projects, then each sub-project should be a sub-directory
with its own "CMakeLists.txt" and the top level folder should have a master
"CMakeLists.txt" that uses `add_subdirectory()` and may define some global variables.
### Top level CMakeLists.txt
```cmake
# CMake version to use
cmake_minimum_required(VERSION 3.14)
# The global project
project(MyProject VERSION 1.0
LANGUAGES CXX
DESCRIPTION "Very nice project" )
# set variables relative to the whole project here (e.g. CMAKE_MODULE_PATH)
# optionally enable warning for the whole project here
# Add subdirectories (that contain their own CMakeLists.txt)
add_subdirectory(sub-project1)
add_subdirectory(sub-project2)
...
# Or build binaries if no sub-modules
```
**Enable warnings** in the whole project
(but it is better to do it for each target in subdirectories):
```cmake
if(MSVC)
add_compile_options(/W4)
else()
add_compile_options(-Wall -Wextra "-Wl,--no-undefined")
endif()
```
**NB**: It is mandatory to call the `project()` function at the beginning
of the toplevel CMakeLists.txt file. Additional commands can be passed with
`CMAKE_PROJECT_INCLUDE` and `CMAKE_PROJECT_INCLUDE_BEFORE`.
### Sub CMakeLists.txt
```cmake
cmake_minimum_required(VERSION 3.14)
project(MySubProject VERSION 1.0
LANGUAGES CXX
DESCRIPTION "Very nice sub-project" )
# look for dependencies here
find_package(SomeLib 4.2 REQUIRED)
# build binaries and link here
add_library(...)
add_executable(...)
target_*(...)
```
**NB**: To where we are in the source/build tree there is a couple of variables:
* `CMAKE_SOURCE_DIR`/`CMAKE_BINARY_DIR`: source/build tree of the top level project
* `PROJECT_SOURCE_DIR`/`PROJECT_BINARY_DIR`: source/build tree of the last project (last call to `project()`)
* `CMAKE_CURRENT_SOURCE_DIR`/`CMAKE_CURRENT_BINARY_DIR`: source/build tree of the current CMakeLists.txt being processed
* `CMAKE_CURRENT_LIST_DIR`, `CMAKE_CURRENT_LIST_FILE`...
## Building binaries
- compile an **executable**: `add_executable(one two.cpp three.h)`
- compile a **library**: `add_library(one STATIC two.cpp three.h)` or `add_library(another SHARED another.cpp another.h)`
- specify the folder containing the public headers: `target_include_directories(one PUBLIC include)`
(or `target_include_directories(my_library PRIVATE include/)` and
`target_include_directories(my_library SYSTEM INTERFACE include/)`
to disable header warnings for downstream projects but keep them in ours.
It is possible to use the generator expressions
`$<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}/include> $<INSTALL_INTERFACE:include>`)
- tell cmake a target needs a **dependency** (library+headers): `target_link_libraries(another PRIVATE one)`
_Remarks_:
- add source files to a target afterward: `target_sources(target PRIVATE file.cpp)`
- 3 types of libraries can be built: `STATIC`, `SHARED` or `MODULE` (modules
are plugins, they are not linked but can be loaded dynamically)
- it is possible to use glob patterns to specify the list of source files.
`file(GLOB src *.c)`
- _CAVEATS_:
- the glob pattern is evaluated at configure time which means that each
time a file is added cmake must be called to reconfigure before compiling
the project.
- if you still want to use a glob pattern, use the CONFIGURE_DEPENDS
option like this `file(GLOB src CONFIGURE_DEPENDS *.c)` which will pass
the globing expression to the build system so that each time the project
is compiled the expression is processed. :warning: Not all generators
support this feature (see
[doc](https://cmake.org/cmake/help/latest/command/file.html?highlight=file#glob)),
make and ninja do.
### Create alias of targets
```cmake
add_library(PackageName::target ALIAS target)
# then, target or PackageName::target can be used
add_executable(<name> ALIAS <target>) # same for executable
```
**NB**: Always make an alias of each target which will be exported so that
it can be used the same way whether it is imported or built.
`add_library(PackageName::one ALIAS one)` and then use PackageName as NAMESPACE
when exporting the export set of targets.
**NB**: An ALIAS target cannot be used to modify target properties. It
cannot be installed. It cannot be aliased again.
### "Building" header-only libraries
```cmake
add_library(mylib INTERFACE) # fake target, nothing is built
target_include_directories(mylib INTERFACE include
$<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}/include> # folder where headers are in
$<INSTALL_INTERFACE:include>) # folder where headers will be after installation
target_link_libraries(mylib INTERFACE Boost::boost) # libraries required to compile a project based on mylib
# Then, use mylib as any other target
```
## Choosing compile features
```cmake
# specify the version of C++
set(CMAKE_CXX_STANDARD 17) # set default for the whole project in the top level CMakeLists.txt
set(CMAKE_CXX_STANDARD_REQUIRED TRUE) # do not fall back on older version
set(CMAKE_CXX_EXTENSIONS OFF) # strict c++.. instead of gnu++..
target_compile_features(my_target PUBLIC cxx_std_11) # override default for one target
# Interprocedural optimization
include(CheckIPOSupported)
check_ipo_supported(RESULT ipo_is_supported)
if(ipo_is_supported)
set_target_properties(my_target PROPERTIES INTERPROCEDURAL_OPTIMIZATION TRUE)
endif()
```
## How to link against external libraries?
The following snippets assume that the external libraries are installed
(binaries and headers are in /usr/local/(include|lib) for example).
### The library is standard (e.g. maths)
```cmake
find_library(MATH_LIBRARY m)
if(MATH_LIBRARY)
target_link_libraries(my_target PRIVATE ${MATH_LIBRARY})
endif()
```
### The library use CMake and has a config-file or a find-module exists for CMake to use it
```cmake
find_package(Dependency [1.42] REQUIRED)
target_link_libraries(my_target PRIVATE Dependency) # will also set include directories
```
What happened here?
* CMake looks for a dependency-config.cmake or DependencyConfig.cmake or
FindDependency.cmake file in `CMAKE_PREFIX_PATH` to find Dependency and may
produce an ERROR if not found (because of REQUIRED).
[More information](https://cmake.org/cmake/help/latest/command/find_package.html#search-procedure)
* Then every INTERFACE_properties of the target "Dependency" is added to
"my_target" properties (because of PRIVATE).
Sometimes, with some libraries, it is possible to load only some parts of
the library. These parts are called components.
```cmake
find_package(Dependency REQUIRED COMPONENTS component1 component2)
target_link_libraries(my_target PRIVATE Dependency::component1 Dependency::component2)
```
**NB**: To tell CMake to use the highest/latest version available of a package
`set(CMAKE_FIND_PACKAGE_SORT_ORDER NATURAL)` before calling `find_package()`.
Note that it works only for config-file packages (\*-config.cmake or
\*Config.cmake) and not for find module packages (Find\*.cmake).
#### What to do if no target were imported? (with find-module)
In case of error
"Target "AAA" links to target "XXX" but the target was not found."
happening (while the library is correctly installed).
This can happened with old find-modules.
```cmake
add_library(Dependency IMPORTED INTERFACE)
target_include_directories(Dependency INTERFACE "${Dependency_INCLUDE_DIRS}")
target_link_libraries(Dependency INTERFACE "${Dependency_LIBRARIES}")
```
**NB**: `Dependency_LIBRARIES` and `Dependency_INCLUDE_DIRS` are not standard
so it is better to open the file "FindDependency.cmake" and look at which
variables are exported.
### Library = binaries + headers
```cmake
find_path(FOO_INCLUDE_DIR "foo.h" "/usr/include" "/usr/local/include") # where the headers may be (optional)
find_library(FOO_LIBRARY "foo" "/usr/lib" "/usr/local/lib") # where the binaries may be (optional)
# NB: FOO_INCLUDE_DIR and FOO_LIBRARY are cached (no need to search again next times)
if(FOO_INCLUDE_DIR AND FOO_LIBRARY)
add_library(foo INTERFACE)
target_include_directories(foo INTERFACE ${FOO_INCLUDE_DIR})
target_link_libraries(foo INTERFACE ${FOO_LIBRARY})
else()
message(FATAL_ERROR "library foo was not found")
endif()
```
#### A reusable alternative: write a find-module file
In a file called "FindFoo.cmake" in the cmake folder of the project:
```cmake
# FindFoo.cmake
# Export the following targets: Foo::Foo
find_path(Foo_INCLUDE_DIR foo.h)
find_library(Foo_LIBRARY foo)
mark_as_advanced(Foo_INCLUDE_DIR Foo_LIBRARY)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(Foo REQUIRED_VARS Foo_LIBRARY Foo_INCLUDE_DIR)
if(Foo_FOUND AND NOT TARGET Foo::Foo)
add_library(Foo::Foo UNKNOWN IMPORTED)
set_target_properties(Foo::Foo PROPERTIES
IMPORTED_LOCATION "${Foo_LIBRARY}"
INTERFACE_SYSTEM_INCLUDE_DIRECTORIES "${Foo_INCLUDE_DIR}")
endif()
```
This is a minimal find-module, for a more complete version see
[https://cmake.org/cmake/help/latest/manual/cmake-developer.7.html](https://cmake.org/cmake/help/latest/manual/cmake-developer.7.html)
### Library = sources + another build system
```cmake
add_custom_command(OUTPUT path/to/output
COMMAND cmd_to_build
DEPENDS some_target)
add_custom_target(generate_target ALL
DEPENDS path/to/output)
# optionally add a fake target
```
### Download and integrate third-party libraries
For an easiest and lightest integration of external libraries,
it is a good idea to use git submodules.
```cmake
find_package(Git QUIET)
if(GIT_FOUND AND EXISTS "${PROJECT_SOURCE_DIR}/.git")
# Update submodules as needed
option(GIT_SUBMODULE "Check submodules during build" ON)
if(GIT_SUBMODULE)
message(STATUS "Submodule update")
execute_process(COMMAND Git::Git submodule update --init --recursive
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
RESULT_VARIABLE GIT_SUBMOD_RESULT)
if(NOT GIT_SUBMOD_RESULT EQUAL "0")
message(FATAL_ERROR "git submodule update --init failed with ${GIT_SUBMOD_RESULT}, please checkout submodules")
endif()
endif()
endif()
if(NOT EXISTS "${PROJECT_SOURCE_DIR}/extern/repo/CMakeLists.txt")
message(FATAL_ERROR "The submodules were not downloaded! GIT_SUBMODULE was turned off or failed. Please update submodules and try again.")
endif()
```
But it is also possible to download a library with CMake (here Catch2 and Google test)
```cmake
# At configure time
include(FetchContent)
FetchContent_Declare( # prepare
catch
GIT_REPOSITORY https://github.com/catchorg/Catch2.git
GIT_TAG v2.2.1
[DOWNLOAD_DIR "..."]
[SOURCE_DIR "..."]
[BINARY_DIR "..."]
)
FetchContent_MakeAvailable(catch) # download
# Or at build time
include(ExternalProject)
ExternalProject_Add(googletest
GIT_REPOSITORY https://github.com/google/googletest.git
GIT_TAG master
[SOURCE_DIR "${CMAKE_BINARY_DIR}/googletest-src"]
[BINARY_DIR "${CMAKE_BINARY_DIR}/googletest-build"]
[CONFIGURE_COMMAND "..."]
[BUILD_COMMAND "..."]
[INSTALL_COMMAND "..."]
[TEST_COMMAND "..."]
)
```
### Good practice for dependencies lookup
It is a good idea to list all the calls to `find_package()`
in the same place and to use the `FeatureSummary` CMake package.
```cmake
include(FeatureSummary)
find_package(XXX)
set_package_properties(XXX PROPERTIES
TYPE REQUIRED
PURPOSE "Used to do something.")
[... other find_package ...]
feature_summary(INCLUDE_QUIET_PACKAGES
FATAL_ON_MISSING_REQUIRED_PACKAGES
WHAT ALL)
```
Documentation: [https://cmake.org/cmake/help/latest/module/FeatureSummary.html](https://cmake.org/cmake/help/latest/module/FeatureSummary.html)
## How to make easily linkable/installable/packageable libraries
In a file named "my_package-config.cmake.in", paste:
```cmake
#[=============================================[
@package_name@ config file
Import the following targets:
@namespace_prefix@::(@library_names@)
Set the variables:
@package_name@_FOUND
@package_name@_VERSION
@package_name@_INCLUDE_DIRS (legacy)
@package_name@_LIBRARIES (legacy)
@package_name@ needs the following dependencies to work:
@package_dependencies@
#]=============================================]
include_guard()
@PACKAGE_INIT@
if(NOT @package_name@_FIND_QUIETLY)
message(STATUS "Import @package_name@")
endif()
# Find dependencies of the package
include(CMakeFindDependencyMacro)
foreach(dep IN ITEMS @package_dependencies@)
separate_arguments(dep)
find_dependency(${dep})
list(GET dep 0 dep_name)
if(NOT ${dep_name}_FOUND)
set(@package_name@_FOUND FALSE)
set(@package_name@_NOT_FOUND_MESSAGE "Dependency ${dep_name} of @package_name@ not found")
return()
endif()
endforeach()
include("${CMAKE_CURRENT_LIST_DIR}/@package_name@_targets.cmake")
set(@package_name@_INCLUDE_DIRS)
set(@package_name@_LIBRARIES)
foreach(library_name IN ITEMS @library_names@)
get_target_property(tmp_inc @namespace_prefix@::${library_name} INTERFACE_INCLUDE_DIRECTORIES)
list(APPEND @package_name@_INCLUDE_DIRS ${tmp_inc})
list(APPEND @package_name@_LIBRARIES @namespace_prefix@::${library_name})
endforeach()
```
In the CMakeLists.txt of the package of libraries, fill the first part of this
file and paste the second.
```cmake
cmake_minimum_required(VERSION 3.14)
# project description
project(my_package VERSION 1.0
LANGUAGES C
DESCRIPTION "Am amazing package" )
# Look for dependencies (if any)
set(package_dependencies "Foo 3.7" "Bar 1.2 COMPONENTS Baz")
find_package(Foo 3.7 REQUIRED)
find_package(Bar 1.2 REQUIRED COMPONENTS Baz)
# Names of the libraries to export and name of the prefix
# it will then be usable from other package trough "${namespace_prefix}::${library_name}"
set(library_names my_lib my_other_lib) # Names of libraries (that will be exported)
set(namespace_prefix MyPackage) # "${namespace_prefix}::${library_name}" usable
set(package_name my_package) # find_package(my_package) will work
# Sources and headers of each libraries
set(my_lib_sources file.c)
set(my_lib_headers "${CMAKE_SOURCE_DIR}/include/${package_name}/my_lib/file.h")
set(my_lib_header_dir "${CMAKE_SOURCE_DIR}/include/${package_name}")
set(my_lib_dependencies PRIVATE some_lib PUBLIC some_other_lib)
set(my_lib_type SHARED) # STATIC, SHARED or MODULE
set(my_other_lib_sources other_file.c)
set(my_other_lib_headers ${CMAKE_SOURCE_DIR}/include/${package_name}/my_other_lib/other_file.h)
set(my_other_lib_header_dir ${CMAKE_SOURCE_DIR}/include/${package_name})
set(my_other_lib_dependencies PRIVATE some_lib PUBLIC some_other_lib)
set(my_other_lib_type SHARED)
### Automatic beyond this line
# set export parameters
set(${package_name}_dest "lib/${package_name}-${PROJECT_VERSION}")
set(${package_name}_include_dest "include/${package_name}-${PROJECT_VERSION}")
set(${package_name}_config_files_dest "lib/cmake/${package_name}-${PROJECT_VERSION}")
foreach(library_name IN LISTS library_names)
# build the library
add_library(${library_name} ${${library_name}_type} ${${library_name}_sources} ${${library_name}_headers})
add_library(${namespace_prefix}::${library_name} ALIAS ${library_name})
target_include_directories(${library_name} PUBLIC
$<BUILD_INTERFACE:${${library_name}_header_dir}>
$<INSTALL_INTERFACE:${${package_name}_include_dest}>) # or append /${library_name} not to have to write #include <library_name/file.h>
set_target_properties(${library_name} PROPERTIES PUBLIC_HEADER ${${library_name}_headers})
target_link_libraries(${library_name} ${${library_name}_dependencies})
# export the library
install(TARGETS ${library_name} EXPORT ${package_name}
LIBRARY DESTINATION "${${package_name}_dest}"
PUBLIC_HEADER DESTINATION "${${package_name}_include_dest}/${library_name}"
INCLUDES DESTINATION "${${package_name}_include_dest}/${library_name}")
endforeach()
# export the package of targets
include(CMakePackageConfigHelpers)
configure_package_config_file(${package_name}-config.cmake.in
"${PROJECT_BINARY_DIR}/${package_name}-config.cmake"
INSTALL_DESTINATION "${${package_name}_config_files_dest}")
write_basic_package_version_file(${package_name}-config-version.cmake COMPATIBILITY AnyNewerVersion)
install(FILES
"${PROJECT_BINARY_DIR}/${package_name}-config.cmake"
"${PROJECT_BINARY_DIR}/${package_name}-config-version.cmake"
DESTINATION "${${package_name}_config_files_dest}")
install(EXPORT ${package_name} NAMESPACE ${namespace_prefix}:: DESTINATION "${${package_name}_dest}"
FILE "${package_name}_targets.cmake" DESTINATION "${${package_name}_config_files_dest}")
```
### Install the library:
```sh
cmake -S . -B build/
cmake --build build/ --target install
```
The library will then be installed (in "/usr/local/" by default on linux).
This may required root privileges if it targets a system folder.
```sh
# Or, for an already built project (after --build)
cmake --install build/ [--prefix path/to/install/folder ]
```
Installing a project produces a "install_manifest.txt" file that
contains the list of all installed files. It can be used to uninstall
the project `xargs rm < install_manifest.txt` (can be a custom target
of the build).
### Use the installed library as dependency in another project
```cmake
# list(APPEND CMAKE_PREFIX_PATH "path/to/custom/install/folder") # if non standard install location
find_package(my_package REQUIRED)
target_link_libraries(client_target PRIVATE MyPackage::my_lib)
```
To include headers
```c
#include <my_lib/file.h>
```
## Go further with components
It is possible to have parts of a package that are not loaded
automatically with `find_package()`. These parts are called
**components** and are loaded when specified to `find_package()`.
### Make a part of a library a component
This is very similar to building a library.
In the file "my_package-config.cmake.in", append:
```cmake
### ... normal configuration ...
# Find components
foreach(comp IN ITEMS ${@package_name@_FIND_COMPONENTS})
include("${SELF_DIR}/@package_name@_${comp}.cmake")
if(NOT TARGET @namespace_prefix@::${comp})
set(@package_name@_FOUND False)
if(@package_name@_FIND_REQUIRED_${comp})
set(@package_name@_NOT_FOUND_MESSAGE "Unsupported component: ${comp}")
return()
endif()
else()
get_target_property(tmp_inc @namespace_prefix@::${comp} INTERFACE_INCLUDE_DIRECTORIES)
list(APPEND @package_name@_INCLUDE_DIRS ${tmp_inc})
list(APPEND @package_name@_LIBRARIES @namespace_prefix@::${comp})
endif()
endforeach()
```
If the component sources are in a separated folder, put this in
the "CMakeLists.txt" of the folder and fill the first part: (otherwise
see after)
```cmake
cmake_minimum_required(VERSION 3.14)
# project description
project(super_component VERSION 1.0
LANGUAGES C
DESCRIPTION "A component of the amazing package." )
set(component_library_names my_component)
set(namespace_prefix MyPackage) # same as in the core part of the package
set(package_name my_package) # same as in the core part of the package
set(component_name super_component)
# Sources and headers of each libraries
set(my_component_sources my_component.c)
set(my_component_headers "${CMAKE_SOURCE_DIR}/include/${package_name}/my_component/my_component.h")
set(my_component_header_dir "${CMAKE_SOURCE_DIR}/include/${package_name}")
set(my_component_type SHARED)
set(my_component_dependencies PRIVATE MyPackage::my_lib)
### Automatic beyond this line
# set export parameters
set(${package_name}_dest "lib/${package_name}-${PROJECT_VERSION}")
set(${package_name}_include_dest "include/${package_name}-${PROJECT_VERSION}")
set(${component_name}_config_files_dest "lib/cmake/${package_name}-${PROJECT_VERSION}")
foreach(library_name IN LISTS component_library_names)
# build the library
add_library(${library_name} ${${library_name}_type} ${${library_name}_sources} ${${library_name}_headers})
add_library(${namespace_prefix}::${library_name} ALIAS ${library_name})
target_include_directories(${library_name} PUBLIC
$<BUILD_INTERFACE:${${library_name}_header_dir}>
$<INSTALL_INTERFACE:${${package_name}_include_dest}>) # or append /${library_name} not to have to write #include <library_name/file.h>
set_target_properties(${library_name} PROPERTIES PUBLIC_HEADER ${${library_name}_headers})
target_link_libraries(${library_name} ${${library_name}_dependencies})
# export the library
install(TARGETS ${library_name} EXPORT ${component_name}
LIBRARY DESTINATION "${${package_name}_dest}"
PUBLIC_HEADER DESTINATION "${${package_name}_include_dest}/${library_name}"
INCLUDES DESTINATION "${${package_name}_include_dest}/${library_name}")
endforeach()
# export the package of targets for this component
install(EXPORT ${component_name} NAMESPACE ${namespace_prefix}:: DESTINATION "${${package_name}_dest}"
FILE "${package_name}_${component_name}.cmake" DESTINATION "${${component_name}_config_files_dest}")
```
If it is not in a separated folder:
- remove calls to `cmake_minimum_required()` and `project()`
- in the "CMakeLists.txt" of the package
- set `component_library_names` and `component_name`
- set sources, headers... for each library of the component
- copy and paste from "set(${component_name}_config_files_dest ...
to the end" at the end of CMakeLists.txt
**NB**: It is a good idea to have only one exported library in a component and
to give it the name of the component so that the user can use the target
`Package::Component` after `find_package(Package REQUIRED COMPONENTS Component)`.
### Use components of a library
In the "CMakeLists.txt" of the project that requires the component:
```cmake
find_package(my_package 1.4 REQUIRED COMPONENTS super_component)
# MyPackage::my_component is available
```
## Package the project (CPack)
At the end of the CMakeLists.txt (the root one) or in a separate
file which is included
```cmake
set(CPACK_PACKAGE_NAME "my-package") # top project name by default
set(CPACK_PACKAGE_VENDOR "MyCompany")
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "A package of useful libraries")
foreach(t MAJOR MINOR PATCH)
set(CPACK_PACKAGE_VERSION_${t} ${PROJECT_VERSION_${t}})
endforeach()
set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENCE")
set(CPACK_RESOURCE_FILE_README "${CMAKE_CURRENT_SOURCE_DIR}/README.md")
set(CPACK_GENERATOR "TGZ;ZIP;DEB;RPM;NuGet") # error if empty
set(CPACK_SOURCE_GENERATOR "TGZ;ZIP")
set(CPACK_SOURCE_IGNORE_FILES /.git /install /.*build.*)
include(CPack)
```
This create two new targets in the build system called "package" and
"package_source" and a file "CPackConfig.cmake" in the build tree.
Running `cmake --build build/ --target package` will generate
"${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}-${CPACK_SYSTEM_NAME}.(zip|tar.gz|rpm|deb)"
files for each ${CPACK_GENERATOR} selected (if possible on the machine).
The generated packages contain everything installed with `install()`.
It is also possible to use directly the `cpack` command.
```sh
cd build/
cpack [-G "ZIP;TGZ"] [-D <var>=<val>] [--config MyCPackConfig.cmake]
# by default cpack will process the file CPackConfig.cmake to generate the packages
```
[CPack documentation](https://cmake.org/cmake/help/latest/manual/cpack.1.html)
## Build types
### For single-configuration generators (e.g. make, ninja)
The build type must be set at configure time with the variable CMAKE_BUILD_TYPE.
```sh
$ cmake -DCMAKE_BUILD_TYPE=Debug ...
```
Possible values are: Debug, Release, MinSizeRel, RelWithDebInfo.
It is possible to define a default build type in CMakeLists.txt.
```cmake
set(default_build_type "Release")
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
message(STATUS "Setting build type to '${default_build_type}' as none was specified.")
set(CMAKE_BUILD_TYPE "${default_build_type}" CACHE
STRING "Choose the type of build." FORCE)
# Set the possible values of build type for cmake-gui
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
"Debug" "Release" "MinSizeRel" "RelWithDebInfo")
endif()
```
### For multi-configurations generators (e.g. Visual Studio, Xcode)
The build type is selected at build time either with the IDE interface
or with cmake with `cmake --build build/ --config Debug`.
### Advice for both
Because the way build types are handled depends on the generator, it is
advised to use the boolean generator expression `$<CONFIG:cfg>` to modify
things depending on the build type.
### Custom build types
It is possible to create other build types than the default ones.
We only have to set the values of the cached variables `SOMETHING_<CONFIG>`
like `CMAKE_C_FLAGS_<CONFIG>`, `CMAKE_CXX_FLAGS_<CONFIG>`,
`CMAKE_EXE_LINKER_FLAGS_<CONFIG>`, `CMAKE_SHARED_LINKER_FLAGS_<CONFIG>`...
```cmake
set(CMAKE_C_FLAGS__MyCustomBuildType -s -O2 -Wall -Wextra)
# set some other variables
# The following lines "register" the new build type
if(CMAKE_CONFIGURATION_TYPES)
list(APPEND CMAKE_CONFIGURATION_TYPES "MyCustomBuildType")
else()
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
"Debug" "Release" "MinSizeRel" "RelWithDebInfo" "MyCustomBuildType")
endif()
```
## Set up a toolchain
The toolchain is cmake way of enabling cross compilation. A toolchain file
is basically a list of tools to use to compile the project. This file
is processed just before the first CMakeLists.txt of the source tree.
In a file named "toolchain.cmake" (for instance):
```cmake
# This enables Linux users to compile for Windows
set(CMAKE_SYSTEM_NAME Windows)
set(CMAKE_C_COMPILER x86_64-w64-mingw32-gcc)
set(CMAKE_CXX_COMPILER x86_64-w64-mingw32-g++)
set(CMAKE_RC_COMPILER x86_64-w64-mingw32-windres)
set(CMAKE_FIND_ROOT_PATH /usr/x86_64-w64-mingw32)
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_CROSSCOMPILING_EMULATOR wine64)
```
Never put logic in toolchain files.
```sh
# To use the toolchain file
-D CMAKE_TOOLCHAIN_FILE=toolchain.cmake # at configure time
```
## Pre-load the cache
It is possible to specify some initial entries for the cache in
a file that will be processed before everything else.
This file must only contain calls to `set(... CACHE ...)`.
In a file named "cache_preload.cmake" (for instance):
```cmake
set(MY_CACHED_VARIABLE "a value" CACHE STRING "description")
set(MY_OPTION ON CACHE BOOL "help message")
```
```sh
# To use the file
cmake ... -C cache_preload.cmake # at configure time
```
## Useful build-in things
### Useful commands
- `file()`: to manipulate files (read, write, search,
get info, lock, download, make directories...)
- `string()`: to manipulate strings (find, replace, regex,
append, concat, upper/lower case, compare...)
- `list()`: to manipulate lists (get element, sublist,
find, filter, append, transform, sort...)
- `math(EXPR <result_var> "<calculation>" [OUTPUT_FORMAT <format>])`
- `try_compile()`, `try_run()`: try to build or run something at configure time
### Useful variables
- `${CMAKE_COMMAND}` contains the path to `cmake` itself
- `${<dependency>_DIR}` contains the path to "dependency-config.cmake"
### Useful modules
```cmake
# for debugging cmake files
include(CMakePrintHelpers)
cmake_print_properties(...)
cmake_print_variables(...)
# check compiler flags
include(CheckCXXCompilerFlag)
check_cxx_compiler_flag(-someflag isSomeFlagAccepted)
```
## CMake command line tool
```sh
cd path/to/project/root/dir
# Configure the project (configure time) ==> always separate source tree and build tree
cmake -S . -B build/ [-G generator] # generate "makefile" or "build.ninja" or whatever
# build the project (build time)
cmake --build build/ # equivalent to "make"
# build a specific target (useful for test, install, package)
cmake --build build/ -t MyTarget # equivalent to "make MyTarget"
# define or modify a cached variable (at configure time)
cmake -DVAR_NAME=value ...
# set log level (default is status) (at configure time)
cmake --loglevel=<error|warning|notice|status|verbose|debug|trace>
# select the number of jobs running in parallel for the build system (at build time)
cmake -j 8 ...
# run a script (written in cmake language)
cmake -P cmake-script
# debug
cmake --trace ... # display each line processed (at configure time)
cmake --trace-expand ... # same but expands the variables
cmake --trace-source="filename" ... # same but only for one file
cmake --build build/ --clean-first ... # == --target clean then ...
cmake --build build/ -v # verbose
# see the cache
cmake -LAH build/ # list all cached variables and their help message
# Set default values for compilers and generator with environment variables (before configure)
CMAKE_GENERATOR="Unix Makefiles"
CC=gcc
CXX=clang++
# /!\ build systems programs and compilers are searched in the CMAKE_PREFIX_PATH
# before being searched in the PATH. (see https://cmake.org/cmake/help/latest/command/find_package.html#search-procedure)
# Other things not related to build
cmake --system-information | less # information about the system (more info if launched from a build tree)
cmake -E <cmd> [args...] # use a command (system agnostic) e.g. env, touch, sha256sum...
```
Note that it is also possible to use a TUI by invoking
`ccmake` to fill cached variables, then configure the
project by pressing "c" and then generate the files by pressing "g".
It is also possible to use a GUI with `cmake-gui`
to perform the same things (configuring and generating).
`ccmake` and `cmake-gui` support the same parameters
as the `cmake` command but they cannot compile they
can only configure.
:warning: You have to reconfigure the project
(by calling `cmake -S -B ...`) when you:
- you use `file(GLOB ...)` (without CONFIGURE_DEPENDS) and you added a new file
- you want to modify cached variables with the command line (-D...) and you did
not modify CMakeLists.txt
- you want to modify the architecture of the build tree
:warning: If you want to switch generator (e.g. use ninja instead of make)
you first need to remove CMakeCache.txt file and CMakeFiles directory
(`cd build && rm -rf CMakeCache.txt CMakeFiles */CMakeFiles`) and
then configure with cmake (by calling `cmake -S -B ...`).
:information_source: About the cache:
- The cache (CMakeCache.txt) is built the first time the projet is configured
- To modify the cache:
- modify CMakeCache.txt
- use ccmake or cmake-gui after the first configuration
- use "-D OPT=val" when running cmake
- use `set(var ... CACHE ... FORCE)` in CMakeLists.txt
- pre-load a cache with `cmake -C cache_preload.cmake ...`
- To reset the cache, delete CMakeCache.txt and reconfigure the project
- If you just want to reset some variables, delete them from the cache then use
`cmake --build build/ -t rebuild_cache`
:information_source: On the contrary, the generated files are generated each
time the project is reconfigured.
## Optimize CMake
Comes from [https://gitlab.kitware.com/cmake/community/wikis/doc/cmake/Performance-Tips](https://gitlab.kitware.com/cmake/community/wikis/doc/cmake/Performance-Tips)
### Optimize the configuration
To see what takes the longest:
```sh
strace -r cmake -S . -B build/ --trace 2>&1 | sort -r | less
```
Advice:
- Try not to use `execute_process()` which happen at configure time and
use `add_custom_command()` instead which happen at build time (and can
be parallelized).
- `list(APPEND var val)` is faster than `set(var "${var};val")`
- Use `include_guard()` at the beginning of files that can be
included (\*.cmake, Find\*.cmake, \*Config.cmake)
- Do not call `find_package()` multiple times for the same package (even
with different components).
- Do not test constants in functions but have different version of them
```cmake
if(condition on a constant)
function(foo)
# code
endfunction()
else()
function(foo)
# code
endfunction()
endif()
```
- Use `break()` or `return()` in loops as soon as possible
- Use `list(FILTER)` then a loop rather than `if()` inside loops
- Use `file(STRINGS ... REGEX ...)` instead of `file(READ)` and `string(REGEX)`
- Use `foreach(elem IN LISTS list)` instead of expanding the lists
- Use the latest version of CMake (new policies are faster)
### Optimize the build
Use a release build type: "Release", "MinSizeRel" or "RelWithDebInfo".
Or use optimization flags: "-O2", "-O3" or "-Os"
```sh
cmake -D CMAKE_BUILD_TYPE=Release ... # configure step of single configuration generators
# Select "compile release" on multi-configuration generators
export CXXFLAGS=-O3
cmake ... # or -D CMAKE_CXX_FLAGS=-O3
```
## CMake do's and don'ts
### Use
- `PUBLIC`, `PRIVATE` or `INTERFACE` always
- a white board to visualize the dependency graph (or see graphviz below)
- at least CMake 3.0.0
- global definitions of project properties (e.g. warnings)
- let cmake manage the transitivity
- imported targets from external dependencies (with `target_link_libraries()`)
- find modules (Find\*.cmake) for libraries that does not support CMake
- export libraries interface
- `cmake_parse_arguments()` for functions with complex arguments behavior
- toolchain files for cross compiling
- properties instead of variables whenever possible
### Don't use
- `include_directories()`
- `link_directories()`
- `link_libraries()`
- `add_definitions()`
- `add_compile_options()`
- path outside of the module (especially with `target_include_directories()`)
- flags directly instead of cmake commands
- `CMAKE_CXX_FLAGS`
- `${lib_INCLUDE_DIRS}` or `${lib_LIBRARIES}`, use targets instead
- variables defined in external project
## Be careful with
- new versions of `Boost` because Boost does not provide a finder.
The CMake team provides one (later on) through a CMake update.
It is the same for some other libraries like SDL, PNG, OpenCL...
- `file(GLOB)` because each time a file is added, CMake has to be called
to evaluate the list of files (the build system would not be able to see
that a file was added otherwise)
- `export(EXPORT)` because it generates non-relocatable configuration files
but is useful if you want to use your project from its build tree rather
than install it.
Use `install(EXPORT)` instead.
## Some ideas and creeds to keep in mind while writing CMakeLists.txt
- Don't build packages, write packagable libraries!
- Think in terms of targets and properties, not in terms of object files and compile flags.
- The build system handles transitivity.
- CMake code is code, apply good practice (comments, indent, DRY...)
## Bonus
### Export compile commands to a json file
```cmake
set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE BOOL "Enable/Disable output of compile_commands.json" FORCE)
```
There is no variable to export the link commands yet but for each target,
the link command is stored in "<build tree of the CMakeLists.txt>/CMakeFiles/<target>.dir/link.txt".
### CCache
```cmake
find_program(CCACHE_PROGRAM ccache)
if(CCACHE_PROGRAM)
set(CMAKE_CXX_COMPILER_LAUNCHER "${CCACHE_PROGRAM}")
endif()
```
### Static analysis utilities
Use the target properties `CXX_CLANG_TIDY`, `CXX_CPPCHECK`, `CXX_CPPLINT`,
`CXX_INCLUDE_WHAT_YOU_USE` and `LINK_WHAT_YOU_USE`.
### Graph to visualize dependencies
```sh
mkdir graphviz && cd graphviz
cmake --graphviz=graph ..
dot graph -Tsvg -o graph.svg
```
### Use imported executable as command
```cmake
find_program(PYTEST_PROGRAM pytest)
if(NOT PYTEST_PROGRAM)
message(FATAL_ERROR "Unable to find pytest.")
endif()
add_executable(pytest IMPORTED)
set_target_properties(pytest PROPERTIES IMPORTED_LOCATION ${PYTEST_PROGRAM})
# Then it is possible to use the target as an executable
add_test(NAME test_python COMMAND pytest --cov=mymodule --cov-report html)
```
Useful scripts
checkSourceFilesUsage.sh
#!/bin/bash
# Author: Thomas Cluzel <thomas.cluzel@cern.ch>
# This script checks if all the files in the src/ directory of every
# sub-projects are used in the CMakeLists.txt of the sub-project.
# This script is to be launched at the top level of the project.
for subpackage in `ls`
do
if [ -f $subpackage/CMakeLists.txt ] # process only sub-packages that contains compiled stuff
then
# List all the sources they currently used
array_of_sources=($(cat $subpackage/CMakeLists.txt | grep ' src/' | sed -r -e 's,^.*(src/[^ )]+).*$,\1,g'))
cd $subpackage
echo ==================== $subpackage ====================
# List all the sources available in the src/ directory
sources=$(find src -name '*.cpp' 2> /dev/null)
if [ "x$sources" = "x" ] ; then continue ; fi
for source_file in $sources
do
if [[ " ${array_of_sources[*]} " != *" $source_file "* ]] # check if some files are not used
then
echo "CMakeLists.txt of $subpackage does not use $source_file"
fi
done
cd ..
fi
done
extractCMakeDoc.py
#! /usr/bin/env python3
# -*- coding: utf-8 -*-
"""
A script to extract all blocks of documentation from CMake files.
:param $1: the name of the file to extract documentation from
:param $2: (optional) string to join the documentations
From
#[===[.rst:
doc1
#]===]
function(hello)
# some code
endfunction()
#[===[.rst:
doc2
#]===]
function(goodbye)
# some code
endfunction()
It retrieves
doc1
doc2
Example:
./extractCMakeDoc.py GaudiToolbox.cmake $'\n\n----------------------------------------\n\n'
:Author: Thomas Cluzel
:Mail: thomas.cluzel@cern.ch
:Date: Wed Aug 7 13:19:31 CEST 2019
"""
import re
def extract_doc(filename, glue:str = "\n\n\n\n\n") -> str:
"""
This function extract all blocks of documentation of a CMake file.
:param filename: str or path, to a file containing CMake code
:param glue: str, string to join all blocks found
:return: str, only the documentation
"""
re_doc = re.compile(r'#\[(=+)\[[^\n]*\n(.*?)#\]\1\]', flags=re.DOTALL)
with open(filename, "r") as file:
return glue.join([match[1] for match in re_doc.findall(file.read())])
if __name__ == "__main__":
from sys import argv
print(extract_doc(*argv[1:]))
listCMakeFunctions.sh
# Thomas CLUZEL
# Script to list the names of all functions in a CMake file
# $1: path to the file
if [ "x$1" = "x" ]
then
echo "Error, you must specify a file \n \
Usage: \n \
$0 file.cmake \n \
List the names of all functions declared in this file"
fi
filename="$1"
cat "$filename" | grep -v "endfunction" | grep "function(" | cut -d")" -f 1 | cut -d"(" -f 2 | cut -d" " -f 1
exit 0
updateCMakeLocally.sh
# Thomas CLUZEL
# Script to update an installed CMake to a particular version
# $1 : version number of the release e.g. "3.14.1", "3.15.0-rc4"
version=$1
if [ "x$version" = "x" ]
then
echo "Give the version number as first argument (e.g. 3.14.1, 3.15.0-rc4)"
exit 1
fi
cd $(dirname $(dirname $(which cmake)))
cmake_install_dir=$(basename $PWD)
cd ..
rm -rf $cmake_install_dir
mkdir $cmake_install_dir
wget -qO- "https://github.com/Kitware/CMake/releases/download/v$version/cmake-$version-Linux-x86_64.tar.gz" | \
tar --strip-components=1 -xz -C $cmake_install_dir
echo "CMake has been updated to version $version"
exit 0
old2modernCMake.py
#old2modernCMake
#! /usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Script to convert the old CMake configuration of Gaudi based projects
to the new configuration.
Author: Thomas Cluzel <thomas.cluzel@cern.ch>
Last update: Thu Jul 18 11:45:39 CEST 2019
"""
import os
import re
import shutil
### Some function needed later
def cmake_parse_arguments(bool_keys:list, single_keys:list, multi_keys:list, \
args:list) -> dict:
"""
This function emulates the behavior of the CMake function
cmake_parse_arguments().
See: https://cmake.org/cmake/help/latest/command/cmake_parse_arguments.html
There is no <prefix>, it is useless here.
:param bool_keys: list of boolean parameters
:param single_keys: list of single-valued parameters
:param multi_keys: list of multi-valued parameters
:param args: list of arguments given to a function
:return: a dict containing the values of parameters
>>> cmake_parse_arguments(["BOOL", "EMPTY"], ["SINGLE"], ["MULTI"], \
["abc", "BOOL", "SINGLE", "hey", "MULTI", "a", "b", "c"])
{'unparsed': ['abc'], 'bools': ['BOOL'], 'SINGLE': 'hey', \
'MULTI': ['a', 'b', 'c']}
"""
all_keys = []
all_keys.extend(bool_keys)
all_keys.extend(single_keys)
all_keys.extend(multi_keys)
return_dict = {"unparsed":[], "bools":[]}
wait_single = None
wait_multiple = None
for arg in args:
if(arg in bool_keys):
return_dict["bools"].append(arg)
elif(arg in single_keys):
wait_single = arg
elif(arg in multi_keys):
wait_multiple = arg
return_dict[wait_multiple] = []
elif(wait_single):
return_dict[wait_single] = arg
wait_single = None
elif(wait_multiple):
return_dict[wait_multiple].append(arg)
else:
return_dict["unparsed"].append(arg)
return return_dict
def convert_glob(expr:str, result_var:str = "files") -> str:
"""
This function converts a globing expression to a call to file(GLOB)
if needed.
:param expr: a globing expression
"param result_var: the name of the CMake variable to get the result.
"""
return f"file(GLOB {result_var} {expr})" if '*' in expr else expr # TODO: do a ls with and without src/ at the beginning
def convert_lib_name(lib_name:str) -> str:
"""
This function return the new name of lib_name.
>>> convert_lib_name("PythonLibs")
'Python::python'
"""
if(lib_name == "PythonLibs"):
return "Python::python"
if(lib_name == "ROOT"): # TODO: do better
return "ROOT::Core"
if(lib_name == "Boost"):
return "Boost::headers"
if(lib_name in ("GaudiPluginService", "GaudiKernel", "GaudiUtilsLib",
"GaudiAlgLib", "GaudiCommonSvcLib", "GaudiMPLib", "GaudiPythonLib",
"RootCnvLib", "GaudiExamplesLib")):
return "Gaudi::" + lib_name
return lib_name
def topological_sort(graph):
"""
This function perform a topological sort of a graph.
:param graph: a dict, keys are nodes, graph[key] is a list of node that
must precede key in the order.
>>> topological_sort({'GaudiPluginService': [], 'GaudiPolicy': \
['GaudiPluginService'], 'GaudiKernel': ['GaudiPluginService'], \
'GaudiCoreSvc': ['GaudiKernel'], 'GaudiUtils': \
['GaudiKernel', 'GaudiCoreSvc'], 'GaudiExamples': ['GaudiUtils']})
['GaudiPluginService', 'GaudiPolicy', 'GaudiKernel', 'GaudiCoreSvc', \
'GaudiUtils', 'GaudiExamples']
"""
seen = set()
order = []
for node in graph.keys():
if node in seen:
continue
stack = [node]
while stack:
v = stack.pop()
if v not in order:
order.append(v)
stack.extend(graph[v])
return order
### Functions to modernize function calls
def modernize_gaudi_add_library(args:list) -> str:
"""
This function modernize the call to gaudi_add_library.
:param args: the list of parameters given to the old function
:return: a string containing the call to the new function
>>> modernize_gaudi_add_library(["GaudiMPLib", "src/Lib/*.cpp", \
"LINK_LIBRARIES", "GaudiKernel", "PythonLibs", "ROOT", "INCLUDE_DIRS", \
"ROOT", "PythonLibs"]) # "PUBLIC_HEADERS", "GaudiMP"
'file(GLOB source0 src/Lib/*.cpp)\\n\
gaudi_add_library(GaudiMPLib SOURCES ${source0} LINK PUBLIC \
Gaudi::GaudiKernel Python::python ROOT::Core)\\n'
"""
# return value initialization
out = ""
# first argument is the name of the library
name = args.pop(0)
arg_dict = cmake_parse_arguments(["NO_PUBLIC_HEADERS"], [], ["LIBRARIES", \
"LINK_LIBRARIES", "INCLUDE_DIRS", "PUBLIC_HEADERS"], args)
# extract sources (may contain glob)
sources = []
for i, glob in enumerate(arg_dict["unparsed"]):
if('$' in glob): # is it the result of the expansion of a variable
print(f"the sources of the shared library {name} are "
"stored in {glob}. Not converted.", file=sys.stderr)
continue
sources.append("${source" + str(i) + "}")
out += convert_glob(glob, "source" + str(i)) + '\n'
# use them in the call to gaudi_add_library
out += "gaudi_add_library(" + name + " SOURCES " + " ".join(sources)
if("LINK_LIBRARIES" in arg_dict):
out += " LINK PUBLIC"
for lib in arg_dict["LINK_LIBRARIES"]:
out += " " + convert_lib_name(lib)
out += ")\n"
if("INCLUDE_DIRS" in arg_dict):
include_dirs = set(arg_dict["INCLUDE_DIRS"]) - \
set(arg_dict["LINK_LIBRARIES"])
if(include_dirs):
out += "target_include_directories(" + \
" ".join(include_dirs) + ")\n"
if("PUBLIC_HEADERS" in arg_dict):
include_dir = os.path.join(os.getcwd(), "include")
os.mkdir(include_dir)
for header in arg_dict["PUBLIC_HEADERS"]:
shutil.move(header, include_dir)
return out
# dict of the function to modernize function calls
modernize = {
"find_package": None,
"gaudi_subdir": None,
"gaudi_add_library": modernize_gaudi_add_library,
"gaudi_add_module": None,
"gaudi_add_python_module": None,
"gaudi_add_executable": None,
"gaudi_add_test": None,
"gaudi_add_unit_test": None,
"gaudi_add_compile_test": None,
"gaudi_add_dictionary": None,
"gaudi_generate_confuserdb": None,
"gaudi_env": None,
"gaudi_depends_on_subdirs": None,
"gaudi_install_headers": None,
"gaudi_install_python_modules": None,
"gaudi_install_scripts": None
}
def modernize_function_call(function_call:str):
"""
This function transform a CMake function call from the old style
to the new style.
:param function_call: the function call stored in a string
e.g. find_package(Boost REQUIRED COMPONENTS regex system)
"""
name, args = function_call.split("(")
name = name.lower()
args = args.replace(")", "")
new_function_call = ""
if(name in modernize and modernize[name] is not None):
new_function_call += modernize[name](args.split())
else:
new_function_call += function_call
print(f"The function {name} has not yet a rule to be converted.")
return new_function_call
def modernize_folder(folder:str):
"""
This function read the content of the CMakeLists.txt
file of a given folder and modernize it as much as possible.
:param folder: path to the folder that contains a CMakeLists.txt
"""
new_content = ""
with open(os.path.join(folder, "CMakeLists.txt"), "r") as \
cmake_lists:
content = cmake_lists.read()
# remove comments from the content
content = re.sub(r'#\[(=+)\[.*?#\]\1\]', '', content, flags=re.DOTALL)
content = re.sub(r'#.*\n', '', content)
# strip spaces
content = re.sub(r'\s+', " ", content)
# extract all function call
function_calls = re.findall(r'\w+\([^()]*\)', content)
for call in function_calls:
new_content += modernize_function_call(call)
with open(os.path.join(folder, "CMakeLists.txt"), "w") as \
cmake_lists:
cmake_lists.write(new_content)
def main(top_level_folder:str = os.getcwd()):
"""
This function modernize a source tree.
:param top_level_folder: path to the folder at the top of the source tree
default value is the current working directory.
"""
modernize_folder(top_level_folder)
# TODO: write files like cmake/${PROJECT_NAME}Dependencies.cmake
if __name__ == "__main__":
from sys import argv
if(len(argv) == 2):
main(argv[1])
else:
main()
SyntaxHighlightingPlugin: Language cmake is either undefined or unsupported. Check SyntaxHighlightingPlugin for more information.
#[========================================================================[.rst:
LHCb full stack
---------------
This file describe the build of a stack of LHCb projects.
The goal is to speed up the build of the stack by configuring and
compiling every sub-projects at once.
Build of the stack (without this file): configure Gaudi, compile Gaudi, (install
Gaudi), configure LHCb, compile LHCb, (install LHCb), configure Lbcom...
Build of the stack (with this file): configure all the projects,
compile all the projects (in parallel)
How to
^^^^^^
Assuming the repository has been cloned and the commands are run
at the top level.
First, set up the environment (set BINARY_TAG, set *PATH or source a view or use a toolchain)
Configure: ``cmake -S . -B build.$BINARY_TAG [-D CMAKE_TOOLCHAIN_FILE=path/to/a/toolchain]``
Build: ``cmake --build build.$BINARY_TAG -j `nproc` ``
Test: ``cd build.$BINARY_TAG ; ctest -j `nproc` ; cd ..``
Note that if you build the full stack at once, you cannot install nor package
any of the projects.
Options
^^^^^^^
* ``CLONE_WITH_KERBEROS``: if ON, the repositories will be cloned using Kerberos, standard https otherwise
* All options defined in the projects of the stack.
Ideas
^^^^^
* Use git submodules and remove ``FetchContent``
#]========================================================================]
cmake_minimum_required(VERSION 3.15)
project(LHCbFullStack
LANGUAGES CXX
DESCRIPTION "The full stack of LHCb projects starting from Gaudi")
# option to set the way ``git clone`` should be done
option(CLONE_WITH_KERBEROS "Use kerberos to clone the repositories instead of standard https" OFF)
if(CLONE_WITH_KERBEROS)
set(gitlab_url ":@gitlab.cern.ch:8443")
else()
set(gitlab_url "gitlab.cern.ch")
endif()
enable_testing()
# So that we can run all the tests of the stack at once
# Download the projects of the stack
include(FetchContent)
set(projects "Gaudi https://${gitlab_url}/gaudi/Gaudi.git"
"LHCb https://${gitlab_url}/lhcb/LHCb.git")
foreach(project IN LISTS projects)
separate_arguments(project)
list(POP_BACK project project_url)
# Download the sub-project only if it does not already exists
if(NOT EXISTS ${PROJECT_SOURCE_DIR}/${project}/CMakeLists.txt)
message(STATUS "Downloading ${project} from ${project_url}")
FetchContent_Declare(${project}
GIT_REPOSITORY ${project_url}
GIT_TAG origin/master
GIT_PROGRESS TRUE
DOWNLOAD_DIR ${CMAKE_SOURCE_DIR}/${project}
SOURCE_DIR ${CMAKE_SOURCE_DIR}/${project}
BINARY_DIR ${CMAKE_BINARY_DIR}/${project})
FetchContent_GetProperties(${project})
string(TOLOWER "${project}" project_lc)
if(NOT ${project_lc}_POPULATED)
FetchContent_Populate(${project})
endif()
endif()
# but always add its sources to the configuration
message(STATUS "Adding ${project} to the build")
add_subdirectory(${project})
endforeach()
--
ThomasCluzel - 2019-08-22