C++Now 2017 Daniel Pfeifer slide
use the same principles for CMakeLists.txt and modules as for the rest of your codebase
- Directories that contain a
CMakeLists.txtare the entry point for the build system generator. Subdirectories may be added withadd_subdirectory()and must contain aCMakeLists.txttoo. - Scripts are
<script>.cmakefiles that can be executed withcmake -P <script>.cmake. Not all commands are supported - Modules are
<script>.cmakefiles located in theCMAKE_MODULE_PATH. Modules can be loaded with theinclude()command
command_name(space separated list of strings)
- Scripting commands change state of command processor
- set variables
- change behavior of other commands
- Project commands
- create build targets
- modify build targets
- Command invocations are not expressions
set(hello world)
message(STATUS "hello, ${hello}")
- Set with the
set()command - Expand with
${} - Variables and values are strings
- Lists are
;separated strings - CMake variables are not environment variables (unlike
Makefile) - Unset variable expands to empty string
# a single line comment
#[==[
multi line comments
#[=[
may be nested
]=]
]==]
target_compile_definitions(foo PRIVATE
"VERBOSITY=$<IF:$<CONFIG:Debug>,30,10>"
)
- Generator expressions use the
$<>syntax - Not evaluated by command interpreter.
It is just a string with
$<> - Evaluated during build system generation
- Not supported in all commands (obviously)
- Commands can be added with
function()ormacro() - Difference is like in
C++ - When a new command replaces an existing command, the old one can be accessed with a
_prefix
# definition
function(my_command input output)
# ...
set(${output} ... PARENT_SCOPE)
endfunction()
# use
my_command(foo bar)
- Variables are scoped to the function, unless set with
PARENT_SCOPE - Available variables:
input, output, ARGC, ARGV, ARGN, ARG0, ARG1, ARG2, ... - Example:
${output}expands tobar
macro(my_command input output)
# ...
endmacro()
my_command(foo bar)
- No extra scope
- Text replacements:
${input},${output},${ARGC},${ARGV},${ARGN},${ARG0},${ARG1},${ARG2}, ... - Example:
${output}is replaced by bar
Variables are so CMake 2.8.13. Moden CMake is about Targets and Properties
add_library(Foo foo.cpp)
target_link_libraries(Foo PRIVATE Bar::Bar)
if (WIN32)
target_sources(Foo PRIVATE foo_win32.cpp)
target_link_libraries(Foo PRIVATE Bar::Win32Support)
endif()
Avoid Custom variables in the arguments of project commands
Don't use file(GLOB) in projects
- Constructors:
- add_executable()
- add_library()
- Member varibles:
- Target properties (too many to list here)
- Member functions:
- get_target_property()
- set_target_properties()
- get_property(TARGET)
- set_property(TARGET)
- target_compile_definitions()
- target_compile_features()
- target_compile_options()
- target_include_directories()
- target_link_libraries()
- target_sources()
add_compile_options() include_directories() link_directories() link_libraries()
target_compile_features(Foo
PUBLIC
cxx_strong_enums
PRIVATE
cxx_lambdas
cxx_range_for
)
- Add
cxx_strong_enumsto the target propertiesCOMPILE_FEATUERSandINTERFACE_COMPILE_FEATURES - Add
cxx_lambdas;cxx_range_forto the target propertyCOMPILE_FEATURES
Get your hands off CMAKE_CXX_FLAGS
- Non-
INTERFACE_properties define the build specifictation of a target INTERFACE_properties define the usage requirements of a targetPRIVATEpopulates the non-INTERFACE_propertyPUBLICpopulates both
Use target_link_libraries() to express direct dependencies
target_link_libraries(Foo
PUBLIC Bar::Bar
PRIVATE Cow::Cow
)
- Adds
Bar::Barto the target propertiesLINK_LIBRARIESandINTERFACE_LINK_LIBRARIES - Adds
Cow::Cowto the target propertyLINK_LIBRARIES - Effectively adds all
INTERFACE_<property>ofBar::Barto<property>andINTERFACE_<property> - Effectively adds all
INTERFACE_<property>ofCow::Cowto<property> - Adds
$<LINK_ONLY:Cow::Cow>toINTERFACE_LINK_LIBRARIES
add_library(Bar INTERFACE)
target_compile_definitions(Bar INTERFACE BAR=1)
INTERFACElibraries have no build specifictation- They only have usage requirements
Don't abuse requirements! Eg: -Wall is not a requirement!
Always like this:
find_package(Foo 2.0 REQUIRED)
# ...
target_link_libraries(... 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_LINK_INTERFACE_LANGUAGES "CXX"
IMPORTED_LOCATION "${Foo_LIBRARY}"
INTERFACE_INCLUDE_DIRECTORIES "${Foo_INCLUDE_DIR}"
)
endif()
find_package(Bar 2.0 REQUIRED)
add_library(Foo ...)
target_link_libraries(Foo PRIVATE Bar::Bar)
install(TARGETS Foo EXPORT FooTargets
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
RUNTIME DESTINATION lib
INCLUDES DESTINATION lib
)
install(EXPORT FooTargets
FILE FooTargets.cmake
NAMESPACE Foo::
DESTINATION lib/cmake/Foo
)
include(CMakePackageConfigureHelpers)
write_basic_package_version_file("FooConfigVersion.cmake"
VERSION ${Foo_VERSION}
COMPATIBILITY SameMajorVersion
)
install(FILES "FooConfig.cmake" "FooConfigVersion.cmake"
DESTINATION lib/cmake/Foo
)
include(CMakeFindDependencyMacro)
find_dependency(Bar 2.0)
include("${CMAKE_CURRENT_LIST_DIR}/FooTargets.cmake")
Warning:
The library interface may change during installation. Use the BUILD_INTERFACE and INSTALL_INTERFACE generator expressions as filters
target_include_directories(Foo PUBLIC
$<BUILD_INTERFACE:${Foo_BINARY_DIR}/include>
$<BUILD_INTERFACE:${Foo_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
)
- CPack is packaging tool distributed with CMake
set()variables inCPackConfig.cmake, orset()variables inCMakeLists.txtandinclude(CPack)
Write your own CPackageConfig.cmake and include() the one that is generated by CMake
The variable CPACK_INSTALL_CMAKE_PROJECTS is a list of quadruples:
- Build directory
- Project Name
- Project Compoent
- Directory
- Make sure different configurations don't collide:
set(CMAKE_DEBUG_POSTFIX "-d") - Create separate build directories for debug, release
- Use this CPackConfig.cmake
include("release/CPackConfig.cmake")
set(CPACK_INSTALL_CMAKE_PROJECTS
"debug;Foo;ALL;/"
"release;Foo;ALL;/"
)
- support system packages
- support prebuild libraries
- support building dependencies as subprojects
- do not require any change to my projects!
Always like this:
find_package(Foo 2.0 REQUIRED)
# ...
target_link_libraries(... Foo::Foo ...)
- System packages ...
- work out of the box
- Prebuilt libraries ...
- need to be put into
CMAKE_PREFIX_PATH
- need to be put into
- Subprojects
- We need to turn find_package(Foo) into a no-op
- What about the imported target
Foo::Foo?
when you export Foo in namespace Foo::, also create an alias Foo::Foo
add_library(Foo::Foo ALIAS Foo)
set(CMAKE_PREFIX_PATH "/prefix")
set(as_subproject Foo)
macro(find_package)
if(NOT "${ARG0}" IN_LIST as_subproject)
_find_package(${ARGV})
endif()
endmacro()
add_subdirectory(Foo)
add_subdirectory(App)
If Foo is a ...
- system package:
find_package(Foo)either findsFooConfig.cmakein the system or usesFindFoo.cmaketo find the library in the system.- in either case, the target
Foo::Foois imported
- prebuild library:
find_package(Foo)either findsFooConfig.cmakein theCMAKE_PREFIX_PATHor usesFindFoo.cmaketo find the library in theCMAKE_PREFIX_PATH.- in either case, the target
Foo::Foois imported
- subproject:
find_package(Foo)does nothing.- The target
Foo::Foois part of project
set(CTEST_SOURCE_DIRECTORY "/source")
set(CTEST_BINARY_DIRECTORY "/binary")
set(ENV{CXXFLAGS} "--coverage")
set(CTEST_CMAKE_GENERATOR "Ninja")
set(CTEST_USE_LAUNCHERS 1)
set(CTEST_COVERAGE_COMMAND "gcov")
set(CTEST_MEMORYCHECK_COMMAND "valgrind")
# set(CTEST_MEMORYCHECK_TYPE "ThreadSanitizer")
ctest_start("Continuous")
ctest_configure()
ctest_build()
ctest_test()
ctest_coverage()
ctest_memcheck()
ctest_submit()
CTest scripts are the right place for CI specific settings. Keep that information out out the project.
Don't put logic in toolchain files.