Modern CMake Modules - Bret Brown - CppCon 2021
CppCon 2017: Mathieu Ropert “Using Modern CMake Patterns to Enforce a Good Modular Design”
$ clang++ main.cpp -o vsdmars -Wreturn-type
is same as:
target_compile_options(
vsdmars PRIVATE
$<$<COMPILE_LANG_AND_ID:CXX,Clang,GNU>:-Werror=return-type>
)
Find Modules
Loaded by the find_package()Find<PackageName>.cmake // can have find_library(...) command
Use Modern CMake
- declare module with ADD_LIBRARY or ADD_EXECUTABLE
- declare build flags with TARGET_xxx()
- declare dependencies with TARGET_LINK_LIBRARIES
- Specify what is PUBLIC and what is PRIVATE
- Consider people are using your library through CMake
Don't
- breaking cmake target model
- breaking cmake features
dev workflows
functions
standard modules - making changes in source directories
- avoid version control operations
- toolchain details
linkers
compiler - build requires magic flags like -DMAGIC_VAR
- Hijacking CMake variables
- Requiring special build targets
- Expecting certain environments
$ cmake && cmake --build && ctest && cmake --install
Write a module
name the file
vsdmars.cmake // include(vsdmars)
FindVsdmars.cmake // find_package(vsdmars REQUIRED)
vsdmarsConfig.cmake // find_package(vsdmars REQUIRED) <--- Most powerful
e.g.
vsdmarsConfig.cmake
function(target_needs_news)
cmake_parse_arguments(parsed
"" # options
"TARGET" # one-value keywords
"" # multi-value keywords
${ARGN} # strings to parse
)
# TODO: defensive argument parsing
set(target ${parsed_TARGET})
message(VERBOSE "done, ${target}")
endfunction()
Defensive argument parsing
if(parsed_UNPARSED_ARGUMENTS)
message(FATAL_ERROR
"BAD ARGUMENT: ${parsed_UNPARSED_ARGUMENTS}"
)
endif()
if(parsed_KEYWORDS_MISSING_VALUES)
message(...)
endif()
Essential for CMake modules
- API documentation
README - Testing the cmake module we just wrote
https://crascit.com/2016/10/18/test-fixtures-with-cmake-ctest/ - message() with verbosity
$ cmake --log-level=verbose
Installing a CMake modules
- share/cmake/vsdmars/vsdmarsConfig.cmake
- ship version files as well if needed
CMakeLists.txt for CMake modules
cmake_minimum_required(VERSION 3.21)
project(vsdmars LANGUAGES NONE) # if module is lang agnostic
enable_testing() # every CMakeLists.txt should have this
install(
FILES vsdmarsConfig.cmake
DESTINATION share/cmake/vsdmars
COMPONENT vsdmars
)
Development Setup
CMAKE_GENERATOR=Ninja
CMAKE_TOOLCHAIN_FILE=path/to/ToolChain.cmake
CMAKE_BUILD_TYPE=Debug
DESTDIR=your/staging/dir
Development Workflow
$ git clone ...
$ cmake -B build -S vsdmars
$ cmake --build build
$ ctest --test-dir build
$ cmake --instal build \
--prefix /opt/...
--component vsdmars
Integration Workflow
$ git clone ...
$ cmake -B cxx-build \
-S cxx-project \
-DVSDMARS_DIR=./vsdmars/
$ cmake --build cxx-build
$ ctest --ctest-dir cxx-build
$ cmake --install cxx-build \
--prefix /opt/vsdmars \
--component cxx-project
Build flags don't scale
- every change in public flags has to be propagated upwards
- Most people give up and put every include director in a common/root build file.
- Correct way of doing so is to put all 3rd party
my/include/namespace
into
./include/my/include/namespace
Modern build systems
- Forbid/report circular and hidden dependencies
- Help developer reason at module level
- Do more than build as you are told
How?
- Define your module build flags
- Define your module dependencies
- Keep out of other modules internals
- Each module has a set of private flags
- Each module has a set of public flags(required to build against its interface)
- Build interfaces are transitive
- Private means .cpp includes other headers
- Public means .h includes other headers
- Build flags are not gone but encapsulated
- Can still with CPPFLAGS, CXXFLAGS and LDFLAGS, and external flags are not our concern anymore
- Declare module with ADD_LIBRARY or ADD_EXECUTABLE
- Declare build flags with TARGET_xxx()
- Declare dependencies with TARGET_LINK_LIBRARIES
- Specify what is PUBLIC and PRIVATE
Global setup
cmake_minimum_required(VERSION 3)
add_compile_options(-Wall -Werror etc.) // including flags effect ABI
add_library(vsdmarsLib src/xxx.cpp)
Declare flags
target_include_directories(vsdmarsLib PUBLIC include)
target_include_directories(vsdmarsLib PRIVATE src)
if (SOME_SETTING)
target_compile_definitions(vsdmarsLIB
PUBLIC WITH_SOME_SETTING
endif()
Declare dependencies
target_link_libraries(vsdmarsLib PUBLIC abc)
target_link_libraries(vsdmarsLib PRIVATE xyz)
Header only libraries
# nothing to build actually, just export headers during install/being pulled by other modules
add_library(vsdmarsLibHeaderOnly INTERFACE)
target_include_directories(vsdmarsLibHeaderOnly INTERFACE include)
target_link_libraries(vsdmarsLibHeaderOnly INTERFACE Boost::Boost)
Antipatterns
- Don't use macros that affect all targets
INCLUDE_DIRECTORIES()
ADD_DEFINITIONS()
LINK_LIBRARIES() - Don't use TARGET_INCLUDE_DIRECTORIES() with a path outside your module
- Don't use TARGET_LINK_LIBRARIES() without specifying PUBLIC, PRIVATE or INTERFACE
- Don't use TARGET_COMPILE_OPTIONS() to set flags that affect the ABI
3rd party
cmake_minimum_required(VERSION 3.5)
find_package(GTest)
add_executable(foo ...)
target_link_libraries(foo GTest::GTest GTest::Main)
find_library(XXX_LIB xxx HINTS ${XXX_DIR}/lib)
add_library(xxx SHARED IMPORTED LOCATION ${XXX_LIB})
target_include_directories(xxx INTERFACE ${XXX_DIR}/include)
target_link_libraries(xxx INTERFACE Boost::boost)
No comments:
Post a Comment
Note: Only a member of this blog may post a comment.