Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.
Effective CMake
a random selection of best practices
Daniel Pfeifer
June 7, 2017
daniel@pfeifer-mail.de
Opening
Why?
The way you use CMake affects your users!
1
CMake’s similarities with C++
• big userbase, industry dominance
• focus on backwards compatibility
• complex, feature ric...
Standards
0https://xkcd.com/927/
3
CMake is code.
Use the same principles for CMakeLists.txt
and modules as for the rest of your codebase.
3
Language
Organization
Directories that contain a CMakeLists.txt are the entry point for
the build system generator. Subdirectories ...
Commands
1 command_name(space separated list of strings)
• Scripting commands change state of command processor
• set vari...
Variables
1 set(hello world)
2 message(STATUS "hello, ${hello}")
• Set with the set() command.
• Expand with ${}.
• Variab...
Comments
1 # a single line comment
2
3 #[==[
4 multi line comments
5
6 #[=[
7 may be nested
8 #]=]
9 #]==]
7
Generator expressions
1 target_compile_definitions(foo PRIVATE
2 "VERBOSITY=$<IF:$<CONFIG:Debug>,30,10>"
3 )
• Generator e...
Custom Commands
Two types of commands
• Commands can be added with function() or macro().
• Difference is like in C++.
• When a new comman...
Custom command: Function
1 function(my_command input output)
2 # ...
3 set(${output} ... PARENT_SCOPE)
4 endfunction()
5 m...
Custom command: Macro
1 macro(my_command input output)
2 # ...
3 endmacro()
4 my_command(foo bar)
• No extra scope.
• Text...
Create macros to wrap commands
that have output parametes.
Otherwise, create a function.
11
Evolving CMake code
Deprecate CMake commands
1 macro(my_command)
2 message(DEPRECATION
3 "The my_command command is deprecated!")
4 _my_comman...
Deprecate CMake variables
1 set(hello "hello world!")
2
3 function(__deprecated_var var access)
4 if(access STREQUAL "READ...
Variables are so CMake 2.8.12.
Modern CMake is about Targets and Properties!
13
Targets and Properties
Look Ma, no Variables!
1 add_library(Foo foo.cpp)
2 target_link_libraries(Foo PRIVATE Bar::Bar)
3
4 if(WIN32)
5 target_sou...
Avoid custom variables
in the arguments of project commands.
14
Don’t use file(GLOB) in projects.
14
Imagine Targets as Objects
• Constructors:
• add_executable()
• add_library()
• Member variables:
• Target properties (too...
Forget those commands:
add_compile_options()
include_directories()
link_directories()
link_libraries()
15
Example:
1 target_compile_features(Foo
2 PUBLIC
3 cxx_strong_enums
4 PRIVATE
5 cxx_lambdas
6 cxx_range_for
7 )
• Adds cxx_...
Get your hands off CMAKE_CXX_FLAGS!
16
Build Specification and Usage Requirements
• Non-INTERFACE_ properties define
the build specification of a target.
• INTERFAC...
Build Specification and Usage Requirements
• PRIVATE populates the non-INTERFACE_ property.
• INTERFACE populates the INTER...
Use target_link_libraries()
to express direct dependencies!
18
Example:
1 target_link_libraries(Foo
2 PUBLIC Bar::Bar
3 PRIVATE Cow::Cow
4 )
• Adds Bar::Bar to the target properties LIN...
Example:
1 target_link_libraries(Foo
2 PUBLIC Bar::Bar
3 PRIVATE Cow::Cow
4 )
• Adds Bar::Bar to the target properties LIN...
Example:
1 target_link_libraries(Foo
2 PUBLIC Bar::Bar
3 PRIVATE Cow::Cow
4 )
• Adds Bar::Bar to the target properties LIN...
Pure usage reqiurements
1 add_library(Bar INTERFACE)
2 target_compile_definitions(Bar INTERFACE BAR=1)
• INTERFACE librari...
Don’t abuse requirements!
Eg: -Wall is not a requirement!
20
Project Boundaries
How to use external libraries
Always like this:
1 find_package(Foo 2.0 REQUIRED)
2 # ...
3 target_link_libraries(... Foo::...
FindFoo.cmake
1 find_path(Foo_INCLUDE_DIR foo.h)
2 find_library(Foo_LIBRARY foo)
3 mark_as_advanced(Foo_INCLUDE_DIR Foo_LI...
FindPNG.cmake
if(PNG_FIND_QUIETLY)
set(_FIND_ZLIB_ARG QUIET)
endif()
find_package(ZLIB ${_FIND_ZLIB_ARG})
if(ZLIB_FOUND)
f...
Use a Find module for third party libraries
that are not built with CMake.
23
Use a Find module for third party libraries
that are not built with CMake.
that do not support clients to use CMake.
23
Use a Find module for third party libraries
that are not built with CMake.
that do not support clients to use CMake.
Also,...
Export your library interface!
1 find_package(Bar 2.0 REQUIRED)
2 add_library(Foo ...)
3 target_link_libraries(Foo PRIVATE...
Export your library interface!
1 include(CMakePackageConfigHelpers)
2 write_basic_package_version_file("FooConfigVersion.c...
Export the right information!
Warning:
The library interface may change during installation. Use the
BUILD_INTERFACE and I...
Creating Packages
CPack
• CPack is a packaging tool distributed with CMake.
• set() variables in CPackConfig.cmake, or
• set() variables in C...
Write your own CPackConfig.cmake
and include() the one
that is generated by CMake.
27
CPack secret
The variable CPACK_INSTALL_CMAKE_PROJECTS is a list of quadruples:
1. Build directory
2. Project Name
3. Proj...
Packaging multiple configurations
1. Make sure different configurations don’t collide:
1 set(CMAKE_DEBUG_POSTFIX "-d")
2. Cr...
Package Management
My requirements for a package manager
• Support system packages
• Support prebuilt libraries
• Support building dependenci...
How to use external libraries
Always like this:
1 find_package(Foo 2.0 REQUIRED)
2 # ...
3 target_link_libraries(... Foo::...
Do not require any changes to my projects!
• System packages …
32
Do not require any changes to my projects!
• System packages …
• work out of the box.
32
Do not require any changes to my projects!
• System packages …
• work out of the box.
• Prebuilt libraries …
32
Do not require any changes to my projects!
• System packages …
• work out of the box.
• Prebuilt libraries …
• need to be ...
Do not require any changes to my projects!
• System packages …
• work out of the box.
• Prebuilt libraries …
• need to be ...
Use the your public interface
When you export Foo in namespace Foo::,
also create an alias Foo::Foo.
1 add_library(Foo::Fo...
When you export Foo in namespace Foo::,
also create an alias Foo::Foo.
33
The toplevel super-project
1 set(CMAKE_PREFIX_PATH "/prefix")
2 set(as_subproject Foo)
3
4 macro(find_package)
5 if(NOT "$...
How does that work?
If Foo is a ...
• system package:
• find_package(Foo) either finds FooConfig.cmake in the system or
use...
CTest
Run with ctest -S build.cmake
1 set(CTEST_SOURCE_DIRECTORY "/source")
2 set(CTEST_BINARY_DIRECTORY "/binary")
3
4 set(ENV{...
CTest scripts are the right place
for CI specific settings.
Keep that information out of the project.
36
Filtering tests by name
Define like this:
1 add_test(NAME Foo.Test
2 COMMAND foo_test --number 0
3 )
Run like this:
1 ctest...
Follow a naming convention for test names.
This simplifies filtering by regex.
37
Fail to compile
1 add_library(foo_fail STATIC EXCLUDE_FROM_ALL
2 foo_fail.cpp
3 )
4 add_test(NAME Foo.Fail
5 COMMAND ${CMA...
Running crosscompiled tests
• When the testing command is a build target,
the command line is prefixed with
${CMAKE_CROSSCO...
Run tests on real hardware
1 #!/bin/bash
2 tester=$1
3 shift
4 # create temporary file
5 filename=$(ssh root@172.22.22.22 ...
Cross Compiling
Toolchain.cmake
1 set(CMAKE_SYSTEM_NAME Windows)
2
3 set(CMAKE_C_COMPILER x86_64-w64-mingw32-gcc)
4 set(CMAKE_CXX_COMPILER...
Don’t put logic in toolchain files.
41
Static Analysis
Treat warnings as errors?
41
How do you treat build errors?
• You fix them.
• You reject pull requests.
• You hold off releases.
42
Treat warnings as errors!
43
Treat warnings as errors!
• You fix them.
• You reject pull requests.
• You hold off releases.
43
Treat warnings as errors!
To treat warnings as errors, never pass -Werror to the
compiler. If you do, your compiler treats...
-Werror causes pain
• You cannot enable -Werror unless you already reached zero
warnings.
• You cannot increase the warnin...
Better: Treat new warnings as errors!
1. At the beginning of a development cycle (eg. sprint), allow new
warnings to be in...
Pull out all the stops!
clang-tidy is a clang-based C++ “linter” tool. Its purpose is to
provide an extensible framework f...
Target properties for static analysis
• <lang>_CLANG_TIDY
• <lang>_CPPLINT
• <lang>_INCLUDE_WHAT_YOU_USE
• Runs the respec...
Scanning header files
• Most of those tools report diagnostics for the current source file
plus the associated header.
• Hea...
For each header file,
there is an associated source file
that #includes this header file at the top.
Even if that source file ...
Create associated source files
1 #!/usr/bin/env bash
2 for fn in `comm -23 
3 <(ls *.h|cut -d '.' -f 1|sort) 
4 <(ls *.c *....
Enable warnings from outside the project
1 env CC=clang CXX=clazy cmake 
2 -DCMAKE_CXX_CLANG_TIDY:STRING=
3 'clang-tidy;-c...
Supported by all IDEs
• Just setting CMAKE_CXX_CLANG_TIDY will make all clang-tidy
diagnostics appear in your normal build...
Thank You!
52
Personal Wishlist
Personal Wishlist
• For each of the following ideas, I have started a prototype.
• Contibutions welcome!
• You can talk to...
Disclaimer:
No guarantee that the following ideas
will ever be added to CMake.
53
PCH as usage requirements
PCH as usage requirements
1 target_precompile_headers(Foo
2 PUBLIC
3 "foo.h"
4 PRIVATE
5 <unordered_map>
6 )
54
PCH as usage requirements
• Calculate a list of headers per config and language
for the build specification of each target.
...
More Languages!
More Languages!
• CMake’s core is language-agnostic.
• Language support is scripted in modules.
• Rules how to compile obj...
Even more Languages!
• If we allow the output to be a source file of a known language,
we would not need special handling f...
find_package(Foo PKGCONF)
find_package(Foo PKGCONF)
• find_package() has two modes: PACKAGE and CONFIG.
• Let’s add a PKGCONF mode.
• In this mode, ...
Declarative Frontend and Lua VM
Lua VM
• Execute CMake commands on Lua VM.3
• Allow CMake modules to be written in Lua.
3not the other way round. This fai...
Declarative Frontend
• For directories, use a declarative language
that allows procedural subroutines.
• libucl4
is an int...
Tell me your ideas!
60
Upcoming SlideShare
Loading in …5
×

Effective CMake

5,661 views

Published on

While CMake has become the de-facto standard buildsystem for C++, it's siblings CTest and CPack are less well known. This talk gives a lightspeed introduction into these three tools and then focuses on best practices on building, testing, and packaging.

Published in: Software

Effective CMake

  1. 1. Effective CMake a random selection of best practices Daniel Pfeifer June 7, 2017 daniel@pfeifer-mail.de
  2. 2. Opening
  3. 3. Why? The way you use CMake affects your users! 1
  4. 4. CMake’s similarities with C++ • big userbase, industry dominance • focus on backwards compatibility • complex, feature rich, ”multi paradigm” • bad reputation, ”bloated”, ”horrible syntax” • some not very well known features 2
  5. 5. Standards 0https://xkcd.com/927/ 3
  6. 6. CMake is code. Use the same principles for CMakeLists.txt and modules as for the rest of your codebase. 3
  7. 7. Language
  8. 8. Organization Directories that contain a CMakeLists.txt are the entry point for the build system generator. Subdirectories may be added with add_subdirectory() and must contain a CMakeLists.txt too. Scripts are <script>.cmake files that can be executed with cmake -P <script>.cmake. Not all commands are supported. Modules are <script>.cmake files located in the CMAKE_MODULE_PATH. Modules can be loaded with the include() command. 4
  9. 9. Commands 1 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. 5
  10. 10. Variables 1 set(hello world) 2 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. 6
  11. 11. Comments 1 # a single line comment 2 3 #[==[ 4 multi line comments 5 6 #[=[ 7 may be nested 8 #]=] 9 #]==] 7
  12. 12. Generator expressions 1 target_compile_definitions(foo PRIVATE 2 "VERBOSITY=$<IF:$<CONFIG:Debug>,30,10>" 3 ) • 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). 8
  13. 13. Custom Commands
  14. 14. Two types of commands • Commands can be added with function() or macro(). • Difference is like in C++. • When a new command replaces an existing command, the old one can be accessed with a _ prefix. 9
  15. 15. Custom command: Function 1 function(my_command input output) 2 # ... 3 set(${output} ... PARENT_SCOPE) 4 endfunction() 5 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 to bar. 10
  16. 16. Custom command: Macro 1 macro(my_command input output) 2 # ... 3 endmacro() 4 my_command(foo bar) • No extra scope. • Text replacements: ${input}, ${output}, ${ARGC}, ${ARGV}, ${ARGN}, ${ARG0}, ${ARG1}, ${ARG2}, … • Example: ${output} is replaced by bar. 11
  17. 17. Create macros to wrap commands that have output parametes. Otherwise, create a function. 11
  18. 18. Evolving CMake code
  19. 19. Deprecate CMake commands 1 macro(my_command) 2 message(DEPRECATION 3 "The my_command command is deprecated!") 4 _my_command(${ARGV}) 5 endmacro() 12
  20. 20. Deprecate CMake variables 1 set(hello "hello world!") 2 3 function(__deprecated_var var access) 4 if(access STREQUAL "READ_ACCESS") 5 message(DEPRECATION 6 "The variable '${var}' is deprecated!") 7 endif() 8 endfunction() 9 10 variable_watch(hello __deprecated_var) 13
  21. 21. Variables are so CMake 2.8.12. Modern CMake is about Targets and Properties! 13
  22. 22. Targets and Properties
  23. 23. Look Ma, no Variables! 1 add_library(Foo foo.cpp) 2 target_link_libraries(Foo PRIVATE Bar::Bar) 3 4 if(WIN32) 5 target_sources(Foo PRIVATE foo_win32.cpp) 6 target_link_libraries(Foo PRIVATE Bar::Win32Support) 7 endif() 14
  24. 24. Avoid custom variables in the arguments of project commands. 14
  25. 25. Don’t use file(GLOB) in projects. 14
  26. 26. Imagine Targets as Objects • Constructors: • add_executable() • add_library() • Member variables: • 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() 15
  27. 27. Forget those commands: add_compile_options() include_directories() link_directories() link_libraries() 15
  28. 28. Example: 1 target_compile_features(Foo 2 PUBLIC 3 cxx_strong_enums 4 PRIVATE 5 cxx_lambdas 6 cxx_range_for 7 ) • Adds cxx_strong_enums to the target properties COMPILE_FEATURES and INTERFACE_COMPILE_FEATURES. • Adds cxx_lambdas;cxx_range_for to the target property COMPILE_FEATURES. 16
  29. 29. Get your hands off CMAKE_CXX_FLAGS! 16
  30. 30. Build Specification and Usage Requirements • Non-INTERFACE_ properties define the build specification of a target. • INTERFACE_ properties define the usage requirements of a target. 17
  31. 31. Build Specification and Usage Requirements • PRIVATE populates the non-INTERFACE_ property. • INTERFACE populates the INTERFACE_ property. • PUBLIC populates both. 18
  32. 32. Use target_link_libraries() to express direct dependencies! 18
  33. 33. Example: 1 target_link_libraries(Foo 2 PUBLIC Bar::Bar 3 PRIVATE Cow::Cow 4 ) • Adds Bar::Bar to the target properties LINK_LIBRARIES and INTERFACE_LINK_LIBRARIES. • Adds Cow::Cow to the target property LINK_LIBRARIES. 19
  34. 34. Example: 1 target_link_libraries(Foo 2 PUBLIC Bar::Bar 3 PRIVATE Cow::Cow 4 ) • Adds Bar::Bar to the target properties LINK_LIBRARIES and INTERFACE_LINK_LIBRARIES. • Adds Cow::Cow to the target property LINK_LIBRARIES. • Effectively adds all INTERFACE_<property> of Bar::Bar to <property> and INTERFACE_<property>. • Effectively adds all INTERFACE_<property> of Cow::Cow to <property>. 19
  35. 35. Example: 1 target_link_libraries(Foo 2 PUBLIC Bar::Bar 3 PRIVATE Cow::Cow 4 ) • Adds Bar::Bar to the target properties LINK_LIBRARIES and INTERFACE_LINK_LIBRARIES. • Adds Cow::Cow to the target property LINK_LIBRARIES. • Effectively adds all INTERFACE_<property> of Bar::Bar to <property> and INTERFACE_<property>. • Effectively adds all INTERFACE_<property> of Cow::Cow to <property>. • Adds $<LINK_ONLY:Cow::Cow> to INTERFACE_LINK_LIBRARIES. 19
  36. 36. Pure usage reqiurements 1 add_library(Bar INTERFACE) 2 target_compile_definitions(Bar INTERFACE BAR=1) • INTERFACE libraries have no build specification. • They only have usage requirements. 20
  37. 37. Don’t abuse requirements! Eg: -Wall is not a requirement! 20
  38. 38. Project Boundaries
  39. 39. How to use external libraries Always like this: 1 find_package(Foo 2.0 REQUIRED) 2 # ... 3 target_link_libraries(... Foo::Foo ...) 21
  40. 40. FindFoo.cmake 1 find_path(Foo_INCLUDE_DIR foo.h) 2 find_library(Foo_LIBRARY foo) 3 mark_as_advanced(Foo_INCLUDE_DIR Foo_LIBRARY) 4 5 include(FindPackageHandleStandardArgs) 6 find_package_handle_standard_args(Foo 7 REQUIRED_VARS Foo_LIBRARY Foo_INCLUDE_DIR 8 ) 9 10 if(Foo_FOUND AND NOT TARGET Foo::Foo) 11 add_library(Foo::Foo UNKNOWN IMPORTED) 12 set_target_properties(Foo::Foo PROPERTIES 13 IMPORTED_LINK_INTERFACE_LANGUAGES "CXX" 14 IMPORTED_LOCATION "${Foo_LIBRARY}" 15 INTERFACE_INCLUDE_DIRECTORIES "${Foo_INCLUDE_DIR}" 16 ) 17 endif() 22
  41. 41. FindPNG.cmake if(PNG_FIND_QUIETLY) set(_FIND_ZLIB_ARG QUIET) endif() find_package(ZLIB ${_FIND_ZLIB_ARG}) if(ZLIB_FOUND) find_path(PNG_PNG_INCLUDE_DIR png.h /usr/local/include/libpng # OpenBSD ) list(APPEND PNG_NAMES png libpng) unset(PNG_NAMES_DEBUG) set(_PNG_VERSION_SUFFIXES 17 16 15 14 12) if (PNG_FIND_VERSION MATCHES "^([0-9]+).([0-9]+)(..*)?$") set(_PNG_VERSION_SUFFIX_MIN "${CMAKE_MATCH_1}${CMAKE_MATCH_2}") if (PNG_FIND_VERSION_EXACT) set(_PNG_VERSION_SUFFIXES ${_PNG_VERSION_SUFFIX_MIN}) else () string(REGEX REPLACE "${_PNG_VERSION_SUFFIX_MIN}.*" "${_PNG_VERSION_SUFFIX_MIN}" _PNG_VERSION_SUFFIXES "${_PNG_VERSION_SUFFIXES}") endif () unset(_PNG_VERSION_SUFFIX_MIN) endif () foreach(v IN LISTS _PNG_VERSION_SUFFIXES) list(APPEND PNG_NAMES png${v} libpng${v}) list(APPEND PNG_NAMES_DEBUG png${v}d libpng${v}d) endforeach() unset(_PNG_VERSION_SUFFIXES) # For compatibility with versions prior to this multi-config search, honor # any PNG_LIBRARY that is already specified and skip the search. if(NOT PNG_LIBRARY) find_library(PNG_LIBRARY_RELEASE NAMES ${PNG_NAMES}) find_library(PNG_LIBRARY_DEBUG NAMES ${PNG_NAMES_DEBUG}) include(${CMAKE_CURRENT_LIST_DIR}/SelectLibraryConfigurations.cmake) select_library_configurations(PNG) mark_as_advanced(PNG_LIBRARY_RELEASE PNG_LIBRARY_DEBUG) endif() unset(PNG_NAMES) unset(PNG_NAMES_DEBUG) # Set by select_library_configurations(), but we want the one from # find_package_handle_standard_args() below. unset(PNG_FOUND) if (PNG_LIBRARY AND PNG_PNG_INCLUDE_DIR) # png.h includes zlib.h. Sigh. set(PNG_INCLUDE_DIRS ${PNG_PNG_INCLUDE_DIR} ${ZLIB_INCLUDE_DIR} ) set(PNG_INCLUDE_DIR ${PNG_INCLUDE_DIRS} ) # for backward compatibility set(PNG_LIBRARIES ${PNG_LIBRARY} ${ZLIB_LIBRARY}) if (CYGWIN) if(BUILD_SHARED_LIBS) # No need to define PNG_USE_DLL here, because it's default for Cygwin. else() set (PNG_DEFINITIONS -DPNG_STATIC) endif() endif () if(NOT TARGET PNG::PNG) add_library(PNG::PNG UNKNOWN IMPORTED) set_target_properties(PNG::PNG PROPERTIES INTERFACE_COMPILE_DEFINITIONS "${PNG_DEFINITIONS}" INTERFACE_INCLUDE_DIRECTORIES "${PNG_INCLUDE_DIRS}" INTERFACE_LINK_LIBRARIES ZLIB::ZLIB) if(EXISTS "${PNG_LIBRARY}") set_target_properties(PNG::PNG PROPERTIES IMPORTED_LINK_INTERFACE_LANGUAGES "C" IMPORTED_LOCATION "${PNG_LIBRARY}") endif() if(EXISTS "${PNG_LIBRARY_RELEASE}") set_property(TARGET PNG::PNG APPEND PROPERTY IMPORTED_CONFIGURATIONS RELEASE) set_target_properties(PNG::PNG PROPERTIES IMPORTED_LINK_INTERFACE_LANGUAGES_RELEASE "C" IMPORTED_LOCATION_RELEASE "${PNG_LIBRARY_RELEASE}") endif() if(EXISTS "${PNG_LIBRARY_DEBUG}") set_property(TARGET PNG::PNG APPEND PROPERTY IMPORTED_CONFIGURATIONS DEBUG) set_target_properties(PNG::PNG PROPERTIES IMPORTED_LINK_INTERFACE_LANGUAGES_DEBUG "C" IMPORTED_LOCATION_DEBUG "${PNG_LIBRARY_DEBUG}") endif() endif() endif () if (PNG_PNG_INCLUDE_DIR AND EXISTS "${PNG_PNG_INCLUDE_DIR}/png.h") file(STRINGS "${PNG_PNG_INCLUDE_DIR}/png.h" png_version_str REGEX "^#define[ t]+PNG_LIBPNG_VER_STRING[ t]+".+"") string(REGEX REPLACE "^#define[ t]+PNG_LIBPNG_VER_STRING[ t]+"([^"]+)".*" "1" PNG_VERSION_STRING "${png_version_str}") unset(png_version_str) endif () endif() include(${CMAKE_CURRENT_LIST_DIR}/FindPackageHandleStandardArgs.cmake) find_package_handle_standard_args(PNG REQUIRED_VARS PNG_LIBRARY PNG_PNG_INCLUDE_DIR VERSION_VAR PNG_VERSION_STRING) mark_as_advanced(PNG_PNG_INCLUDE_DIR PNG_LIBRARY ) 23
  42. 42. Use a Find module for third party libraries that are not built with CMake. 23
  43. 43. Use a Find module for third party libraries that are not built with CMake. that do not support clients to use CMake. 23
  44. 44. Use a Find module for third party libraries that are not built with CMake. that do not support clients to use CMake. Also, report this as a bug to their authors. 23
  45. 45. Export your library interface! 1 find_package(Bar 2.0 REQUIRED) 2 add_library(Foo ...) 3 target_link_libraries(Foo PRIVATE Bar::Bar) 4 5 install(TARGETS Foo EXPORT FooTargets 6 LIBRARY DESTINATION lib 7 ARCHIVE DESTINATION lib 8 RUNTIME DESTINATION bin 9 INCLUDES DESTINATION include 10 ) 11 install(EXPORT FooTargets 12 FILE FooTargets.cmake 13 NAMESPACE Foo:: 14 DESTINATION lib/cmake/Foo 15 ) 24
  46. 46. Export your library interface! 1 include(CMakePackageConfigHelpers) 2 write_basic_package_version_file("FooConfigVersion.cmake" 3 VERSION ${Foo_VERSION} 4 COMPATIBILITY SameMajorVersion 5 ) 6 install(FILES "FooConfig.cmake" "FooConfigVersion.cmake" 7 DESTINATION lib/cmake/Foo 8 ) 1 include(CMakeFindDependencyMacro) 2 find_dependency(Bar 2.0) 3 include("${CMAKE_CURRENT_LIST_DIR}/FooTargets.cmake") 25
  47. 47. Export the right information! Warning: The library interface may change during installation. Use the BUILD_INTERFACE and INSTALL_INTERFACE generator expressions as filters. 1 target_include_directories(Foo PUBLIC 2 $<BUILD_INTERFACE:${Foo_BINARY_DIR}/include> 3 $<BUILD_INTERFACE:${Foo_SOURCE_DIR}/include> 4 $<INSTALL_INTERFACE:include> 5 ) 26
  48. 48. Creating Packages
  49. 49. CPack • CPack is a packaging tool distributed with CMake. • set() variables in CPackConfig.cmake, or • set() variables in CMakeLists.txt and include(CPack). 27
  50. 50. Write your own CPackConfig.cmake and include() the one that is generated by CMake. 27
  51. 51. CPack secret The variable CPACK_INSTALL_CMAKE_PROJECTS is a list of quadruples: 1. Build directory 2. Project Name 3. Project Component 4. Directory 28
  52. 52. Packaging multiple configurations 1. Make sure different configurations don’t collide: 1 set(CMAKE_DEBUG_POSTFIX "-d") 2. Create separate build directories for debug, release. 3. Use this CPackConfig.cmake: 1 include("release/CPackConfig.cmake") 2 set(CPACK_INSTALL_CMAKE_PROJECTS 3 "debug;Foo;ALL;/" 4 "release;Foo;ALL;/" 5 ) 29
  53. 53. Package Management
  54. 54. My requirements for a package manager • Support system packages • Support prebuilt libraries • Support building dependencies as subprojects • Do not require any changes to my projects! 30
  55. 55. How to use external libraries Always like this: 1 find_package(Foo 2.0 REQUIRED) 2 # ... 3 target_link_libraries(... Foo::Foo ...) 31
  56. 56. Do not require any changes to my projects! • System packages … 32
  57. 57. Do not require any changes to my projects! • System packages … • work out of the box. 32
  58. 58. Do not require any changes to my projects! • System packages … • work out of the box. • Prebuilt libraries … 32
  59. 59. Do not require any changes to my projects! • System packages … • work out of the box. • Prebuilt libraries … • need to be put into CMAKE_PREFIX_PATH. 32
  60. 60. Do not require any changes to my projects! • System packages … • work out of the box. • Prebuilt libraries … • need to be put into CMAKE_PREFIX_PATH. • Subprojects … • We need to turn find_package(Foo) into a no-op. • What about the imported target Foo::Foo? 32
  61. 61. Use the your public interface When you export Foo in namespace Foo::, also create an alias Foo::Foo. 1 add_library(Foo::Foo ALIAS Foo) 33
  62. 62. When you export Foo in namespace Foo::, also create an alias Foo::Foo. 33
  63. 63. The toplevel super-project 1 set(CMAKE_PREFIX_PATH "/prefix") 2 set(as_subproject Foo) 3 4 macro(find_package) 5 if(NOT "${ARG0}" IN_LIST as_subproject) 6 _find_package(${ARGV}) 7 endif() 8 endmacro() 9 10 add_subdirectory(Foo) 11 add_subdirectory(App) 34
  64. 64. How does that work? If Foo is a ... • system package: • find_package(Foo) either finds FooConfig.cmake in the system or uses FindFoo.cmake to find the library in the system. In either case, the target Foo::Foo is imported. • prebuilt library: • find_package(Foo) either finds FooConfig.cmake in the CMAKE_PREFIX_PATH or uses FindFoo.cmake to find the library in the CMAKE_PREFIX_PATH. In either case, the target Foo::Foo is imported. • subproject: • find_package(Foo) does nothing. The target Foo::Foo is part of the project. 35
  65. 65. CTest
  66. 66. Run with ctest -S build.cmake 1 set(CTEST_SOURCE_DIRECTORY "/source") 2 set(CTEST_BINARY_DIRECTORY "/binary") 3 4 set(ENV{CXXFLAGS} "--coverage") 5 set(CTEST_CMAKE_GENERATOR "Ninja") 6 set(CTEST_USE_LAUNCHERS 1) 7 8 set(CTEST_COVERAGE_COMMAND "gcov") 9 set(CTEST_MEMORYCHECK_COMMAND "valgrind") 10 #set(CTEST_MEMORYCHECK_TYPE "ThreadSanitizer") 11 12 ctest_start("Continuous") 13 ctest_configure() 14 ctest_build() 15 ctest_test() 16 ctest_coverage() 17 ctest_memcheck() 18 ctest_submit() 36
  67. 67. CTest scripts are the right place for CI specific settings. Keep that information out of the project. 36
  68. 68. Filtering tests by name Define like this: 1 add_test(NAME Foo.Test 2 COMMAND foo_test --number 0 3 ) Run like this: 1 ctest -R 'Foo.' -j4 --output-on-failure 37
  69. 69. Follow a naming convention for test names. This simplifies filtering by regex. 37
  70. 70. Fail to compile 1 add_library(foo_fail STATIC EXCLUDE_FROM_ALL 2 foo_fail.cpp 3 ) 4 add_test(NAME Foo.Fail 5 COMMAND ${CMAKE_COMMAND} 6 --build ${CMAKE_BINARY_DIR} 7 --target foo_fail 8 ) 9 set_property(TEST Foo.Fail PROPERTY 10 PASS_REGULAR_EXPRESSION "static assert message" 11 ) 38
  71. 71. Running crosscompiled tests • When the testing command is a build target, the command line is prefixed with ${CMAKE_CROSSCOMPILING_EMULATOR}. • When crosscompiling from Linux to Windows, set CMAKE_CROSSCOMPILING_EMULATOR to wine. • When crosscompiling to ARM, set CMAKE_CROSSCOMPILING_EMULATOR to qemu-arm. • To run tests on another machine, set CMAKE_CROSSCOMPILING_EMULATOR to a script that copies it over and executes it there. 39
  72. 72. Run tests on real hardware 1 #!/bin/bash 2 tester=$1 3 shift 4 # create temporary file 5 filename=$(ssh root@172.22.22.22 mktemp) 6 # copy the tester to temporary file 7 scp $tester root@172.22.22.22:$filename 8 # make test executable 9 ssh root@172.22.22.22 chmod +x $filename 10 # execute test 11 ssh root@172.22.22.22 $filename "$@" 12 # store success 13 success=$? 14 # cleanup 15 ssh root@172.22.22.22 rm $filename 16 exit $success 40
  73. 73. Cross Compiling
  74. 74. Toolchain.cmake 1 set(CMAKE_SYSTEM_NAME Windows) 2 3 set(CMAKE_C_COMPILER x86_64-w64-mingw32-gcc) 4 set(CMAKE_CXX_COMPILER x86_64-w64-mingw32-g++) 5 set(CMAKE_RC_COMPILER x86_64-w64-mingw32-windres) 6 7 set(CMAKE_FIND_ROOT_PATH /usr/x86_64-w64-mingw32) 8 9 set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) 10 set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) 11 set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) 12 13 set(CMAKE_CROSSCOMPILING_EMULATOR wine64) 41
  75. 75. Don’t put logic in toolchain files. 41
  76. 76. Static Analysis
  77. 77. Treat warnings as errors? 41
  78. 78. How do you treat build errors? • You fix them. • You reject pull requests. • You hold off releases. 42
  79. 79. Treat warnings as errors! 43
  80. 80. Treat warnings as errors! • You fix them. • You reject pull requests. • You hold off releases. 43
  81. 81. Treat warnings as errors! To treat warnings as errors, never pass -Werror to the compiler. If you do, your compiler treats warnings as errors. You can no longer treat warnings as errors, because you will no longer get any warnings. All you get is errors. 44
  82. 82. -Werror causes pain • You cannot enable -Werror unless you already reached zero warnings. • You cannot increase the warning level unless you already fixed all warnings introduced by that level. • You cannot upgrade your compiler unless you already fixed all new warnings that the compiler reports at your warning level. • You cannot update your dependencies unless you already ported your code away from any symbols that are now [[deprecated]]. • You cannot [[deprecated]] your internal code as long as it is still used. But once it is no longer used, you can as well just remove it… 45
  83. 83. Better: Treat new warnings as errors! 1. At the beginning of a development cycle (eg. sprint), allow new warnings to be introduced. • Increase warning level, enable new warnings explicitly. • Update the compiler. • Update dependencies. • Mark symbols as [[deprecated]]. 2. Then, burn down the number of warnings. 3. Repeat. 46
  84. 84. Pull out all the stops! clang-tidy is a clang-based C++ “linter” tool. Its purpose is to provide an extensible framework for diagnosing and fixing typical programming errors, like style violations, interface misuse, or bugs that can be deduced via static analysis. cpplint is automated checker to make sure a C++ file follows Google’s C++ style guide. include-what-you-use analyzes #includes in C and C++ source files. clazy is a clang wrapper that finds common C++/Qt antipatterns that decrease performance. 47
  85. 85. Target properties for static analysis • <lang>_CLANG_TIDY • <lang>_CPPLINT • <lang>_INCLUDE_WHAT_YOU_USE • Runs the respective tool along the with compiler. • Diagnostics are visible in your IDE. • Diagnostics are visible on CDash. • LINK_WHAT_YOU_USE • links with -Wl,--no-as-needed, then runs ldd -r -u. <lang> is either C or CXX. Each of those properties is initialzied with CMAKE_<property>. 48
  86. 86. Scanning header files • Most of those tools report diagnostics for the current source file plus the associated header. • Header files with no assiciated source file will not be analyzed. • You may be able to set a custom header filter, but then the headers may be analyzed multiple times. 49
  87. 87. For each header file, there is an associated source file that #includes this header file at the top. Even if that source file would otherwise be empty. 49
  88. 88. Create associated source files 1 #!/usr/bin/env bash 2 for fn in `comm -23 3 <(ls *.h|cut -d '.' -f 1|sort) 4 <(ls *.c *.cpp|cut -d '.' -f 1|sort)` 5 do 6 echo "#include "$fn.h"" >> $fn.cpp 7 done 50
  89. 89. Enable warnings from outside the project 1 env CC=clang CXX=clazy cmake 2 -DCMAKE_CXX_CLANG_TIDY:STRING= 3 'clang-tidy;-checks=-*,readability-*' 4 -DCMAKE_CXX_INCLUDE_WHAT_YOU_USE:STRING= 5 'include-what-you-use;-Xiwyu;--mapping_file=/iwyu.imp' 6 .. 51
  90. 90. Supported by all IDEs • Just setting CMAKE_CXX_CLANG_TIDY will make all clang-tidy diagnostics appear in your normal build output. • No special IDE support needed. • If IDE understands fix-it hints from clang, it will also understand the ones from clang-tidy. 52
  91. 91. Thank You! 52
  92. 92. Personal Wishlist
  93. 93. Personal Wishlist • For each of the following ideas, I have started a prototype. • Contibutions welcome! • You can talk to me! 53
  94. 94. Disclaimer: No guarantee that the following ideas will ever be added to CMake. 53
  95. 95. PCH as usage requirements
  96. 96. PCH as usage requirements 1 target_precompile_headers(Foo 2 PUBLIC 3 "foo.h" 4 PRIVATE 5 <unordered_map> 6 ) 54
  97. 97. PCH as usage requirements • Calculate a list of headers per config and language for the build specification of each target. • Generate a header file that #includes each of those headers. • Tell the build system to precompile this header. • Tell the build system to force-include this header. • Require no changes to the code (No #include "stdafx.h"). 55
  98. 98. More Languages!
  99. 99. More Languages! • CMake’s core is language-agnostic. • Language support is scripted in modules. • Rules how to compile object files, link them together. • The output of the compiler must be an object file. • CMake can be used with D by putting necessary files in CMAKE_MODULE_PATH.1 1https://github.com/dcarp/cmake-d 56
  100. 100. Even more Languages! • If we allow the output to be a source file of a known language, we would not need special handling for Protobuf, Qt-resources, or any other IDL. • This would also allow using CMake for BASIC, BCX, Chapel, COBOL, Cython, Eiffel, Genie, Haxe, Java, Julia, Lisaac, Scheme, PHP, Python, X10, Nim, Vala.2 2https://en.wikipedia.org/wiki/Source-to-source_compiler 57
  101. 101. find_package(Foo PKGCONF)
  102. 102. find_package(Foo PKGCONF) • find_package() has two modes: PACKAGE and CONFIG. • Let’s add a PKGCONF mode. • In this mode, CMake parses .pc files and generates one IMPORTED library per package. 58
  103. 103. Declarative Frontend and Lua VM
  104. 104. Lua VM • Execute CMake commands on Lua VM.3 • Allow CMake modules to be written in Lua. 3not the other way round. This failed before. 59
  105. 105. Declarative Frontend • For directories, use a declarative language that allows procedural subroutines. • libucl4 is an interesting option. 4https://github.com/vstakhov/libucl 60
  106. 106. Tell me your ideas! 60

×