Effective Cmake Daniel Pfeifer Cppnow 05-19-2017
Effective Cmake Daniel Pfeifer Cppnow 05-19-2017
Effective Cmake Daniel Pfeifer Cppnow 05-19-2017
Daniel Pfeifer
May 19, 2017
[email protected]
Opening
Why?
1
CMakes similarities with C++
2
Standards
3
CMake is code.
Use the same principles for CMakeLists.txt
and modules as for the rest of your codebase.
3
Language
Organization
4
Commands
5
Variables
1 set(hello world)
2 message(STATUS "hello, ${hello}")
6
Comments
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 )
8
Custom Commands
Two types of commands
9
Custom command: Function
10
Custom command: Macro
No extra scope.
Text replacements: ${input}, ${output}, ${ARGC},
${ARGV}, ${ARGN}, ${ARG0}, ${ARG1}, ${ARG2},
Example: ${output} is replaced by bar.
11
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_command(${ARGV})
5 endmacro()
12
Deprecate CMake variables
10 variable_watch(hello __deprecated_var)
13
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_sources(Foo PRIVATE foo_win32.cpp)
6 target_link_libraries(Foo PRIVATE Bar::Win32Support)
7 endif()
14
Avoid custom variables
in the arguments of project commands.
14
Dont use file(GLOB) in projects.
14
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
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 )
16
Get your hands off CMAKE_CXX_FLAGS!
16
Build Specication and Usage Requirements
17
Build Specication and Usage Requirements
18
Use target_link_libraries()
to express direct dependencies!
18
Example:
1 target_link_libraries(Foo
2 PUBLIC Bar::Bar
3 PRIVATE Cow::Cow
4 )
19
Example:
1 target_link_libraries(Foo
2 PUBLIC Bar::Bar
3 PRIVATE Cow::Cow
4 )
19
Example:
1 target_link_libraries(Foo
2 PUBLIC Bar::Bar
3 PRIVATE Cow::Cow
4 )
19
Pure usage reqiurements
1 add_library(Bar INTERFACE)
2 target_compile_definitions(Bar INTERFACE BAR=1)
20
Dont abuse requirements!
Eg: -Wall is not a requirement!
20
Project Boundaries
How to use external libraries
21
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
22
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
)
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 ()
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
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, report this as a bug to their authors.
23
Export your library interface!
24
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
Export the right information!
Warning:
The library interface may change during installation. Use the
BUILD_INTERFACE and INSTALL_INTERFACE generator
expressions as lters.
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
Creating Packages
CPack
27
Write your own CPackCong.cmake
and include() the one
that is generated by CMake.
27
CPack secret
1. Build directory
2. Project Name
3. Project Component
4. Directory
28
Packaging multiple congurations
1 set(CMAKE_DEBUG_POSTFIX "-d")
1 include("release/CPackConfig.cmake")
2 set(CPACK_INSTALL_CMAKE_PROJECTS
3 "debug;Foo;ALL;/"
4 "release;Foo;ALL;/"
5 )
29
Package Management
My requirements for a package manager
30
How to use external libraries
31
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 put into CMAKE_PREFIX_PATH.
32
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
Use the your public interface
33
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 "${ARG0}" IN_LIST as_subproject)
6 _find_package(${ARGV})
7 endif()
8 endmacro()
9
10 add_subdirectory(Foo)
11 add_subdirectory(App)
34
How does that work?
If Foo is a ...
system package:
find_package(Foo) either nds FooConfig.cmake in the
system or uses FindFoo.cmake to nd the library in the system.
In either case, the target Foo::Foo is imported.
prebuilt library:
find_package(Foo) either nds FooConfig.cmake in the
CMAKE_PREFIX_PATH or uses FindFoo.cmake to nd 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
CTest
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
CTest scripts are the right place
for CI specic settings.
Keep that information out of the project.
36
Filtering tests by name
1 add_test(NAME Foo.Test
2 COMMAND foo_test --number 0
3 )
37
Follow a naming convention for test names.
This simplies ltering by regex.
37
Fail to compile
38
Running crosscompiled tests
39
Run tests on real hardware
1 #!/bin/bash
2 tester=$1
3 shift
4 # create temporary file
5 filename=$(ssh [email protected] mktemp)
6 # copy the tester to temporary file
7 scp $tester [email protected]:$filename
8 # make test executable
9 ssh [email protected] chmod +x $filename
10 # execute test
11 ssh [email protected] $filename "$@"
12 # store success
13 success=$?
14 # cleanup
15 ssh [email protected] rm $filename
16 exit $success
40
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 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
Dont put logic in toolchain les.
41
Static Analysis
Treat warnings as errors?
41
How do you treat build errors?
You x them.
You reject pull requests.
You hold off releases.
42
Treat warnings as errors!
43
Treat warnings as errors!
You x them.
You reject pull requests.
You hold off releases.
43
Treat warnings as errors!
44
-Werror causes pain
45
Better: Treat new warnings as errors!
46
Pull out all the stops!
47
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.
48
Scanning header les
49
For each header le,
there is an associated source le
that #includes this header le at the top.
Even if that source le would otherwise be empty.
49
Create associated source les
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
Enable warnings from from outside the project
51
Supported by all IDEs
52
Thank You!
52
Personal Wishlist
Personal Wishlist
53
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
55
More Languages!
More Languages!
1 https://github.com/dcarp/cmake-d
56
Even more Languages!
2 https://en.wikipedia.org/wiki/Source-to-source_compiler
57
find_package(Foo PKGCONF)
find_package(Foo PKGCONF)
58
Declarative Frontend and Lua VM
Lua VM
59
Declarative Frontend
4 https://github.com/vstakhov/libucl
60
Tell me your ideas!
60