6. cmake编程
6.1. 控制流
if(variable) # If variable is `ON`, `YES`, `TRUE`, `Y`, or non zero number else() # If variable is `0`, `OFF`, `NO`, `FALSE`, `N`, `IGNORE`, `NOTFOUND`, `""`, or ends in `-NOTFOUND` endif() # If variable does not expand to one of the above, CMake will expand it then try again
There are a variety of keywords as well, such as:
- Unary:
NOT
,TARGET
,EXISTS
(file),DEFINED
, etc.
- Binary:
STREQUAL
,AND
,OR
,MATCHES
(regular expression),VERSION_LESS
,VERSION_LESS_EQUAL
(CMake 3.7+), etc.
- Parentheses can be used to group
generator-expressions
target_compile_options(MyTarget PRIVATE "$<$<CONFIG:Debug>:--my-flag>")
当使用debug编译时,加上–my-flag编译选项
That last one is very common. You'll see something like this in almost every package that supports installing:
target_include_directories( MyTarget PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include> $<INSTALL_INTERFACE:include> )
6.2. 宏和函数
function(SIMPLE REQUIRED_ARG) message(STATUS "Simple arguments: ${REQUIRED_ARG}, followed by ${ARGN}") set(${REQUIRED_ARG} "From SIMPLE" PARENT_SCOPE) endfunction() simple(This Foo Bar) message("Output: ${This}")
The output would be:
7. cmake交互
7.1. 配置文件
Version.h.in
#pragma once #define MY_VERSION_MAJOR @PROJECT_VERSION_MAJOR@ #define MY_VERSION_MINOR @PROJECT_VERSION_MINOR@ #define MY_VERSION_PATCH @PROJECT_VERSION_PATCH@ #define MY_VERSION_TWEAK @PROJECT_VERSION_TWEAK@ #define MY_VERSION "@PROJECT_VERSION@"
configure_file ( "${PROJECT_SOURCE_DIR}/include/My/Version.h.in" "${PROJECT_BINARY_DIR}/include/My/Version.h"
7.2. 读取文件
# Assuming the canonical version is listed in a single line # This would be in several parts if picking up from MAJOR, MINOR, etc. set(VERSION_REGEX "#define MY_VERSION[ \t]+\"(.+)\"") # Read in the line containing the version file(STRINGS "${CMAKE_CURRENT_SOURCE_DIR}/include/My/Version.hpp" VERSION_STRING REGEX ${VERSION_REGEX}) # Pick out just the version string(REGEX REPLACE ${VERSION_REGEX} "\\1" VERSION_STRING "${VERSION_STRING}") # Automatically getting PROJECT_VERSION_MAJOR, My_VERSION_MAJOR, etc. project(My LANGUAGES CXX VERSION ${VERSION_STRING})
8. 如何组织工程
- project - .gitignore - README.md - LICENCE.md - CMakeLists.txt - cmake - FindSomeLib.cmake - something_else.cmake - include - project - lib.hpp - src - CMakeLists.txt - lib.cpp - apps - CMakeLists.txt - app.cpp - tests - CMakeLists.txt - testlib.cpp - docs - CMakeLists.txt - extern - googletest - scripts - helper.py
将cmake/目录加入到工程中.
set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake" ${CMAKE_MODULE_PATH})
9. 运行其他程序
9.1. 在配置时运行命令
find_package(Git QUIET) if(GIT_FOUND AND EXISTS "${PROJECT_SOURCE_DIR}/.git") execute_process(COMMAND ${GIT_EXECUTABLE} submodule update --init --recursive WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} RESULT_VARIABLE GIT_SUBMOD_RESULT) if(NOT GIT_SUBMOD_RESULT EQUAL "0") message(FATAL_ERROR "git submodule update --init --recursive failed with ${GIT_SUBMOD_RESULT}, please checkout submodules") endif() endif()
9.2. 在编译时运行命令
find_package(PythonInterp REQUIRED) add_custom_command(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/include/Generated.hpp" COMMAND "${PYTHON_EXECUTABLE}" "${CMAKE_CURRENT_SOURCE_DIR}/scripts/GenerateHeader.py" --argument DEPENDS some_target) add_custom_target(generate_header ALL DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/include/Generated.hpp") install(FILES ${CMAKE_CURRENT_BINARY_DIR}/include/Generated.hpp DESTINATION include)
10. 一个简单示例
# Almost all CMake files should start with this # You should always specify a range with the newest # and oldest tested versions of CMake. This will ensure # you pick up the best policies. cmake_minimum_required(VERSION 3.1...3.22) # This is your project statement. You should always list languages; # Listing the version is nice here since it sets lots of useful variables project( ModernCMakeExample VERSION 1.0 LANGUAGES CXX) # If you set any CMAKE_ variables, that can go here. # (But usually don't do this, except maybe for C++ standard) # Find packages go here. # You should usually split this into folders, but this is a simple example # This is a "default" library, and will match the *** variable setting. # Other common choices are STATIC, SHARED, and MODULE # Including header files here helps IDEs but is not required. # Output libname matches target name, with the usual extensions on your system add_library(MyLibExample simple_lib.cpp simple_lib.hpp) # Link each target with other targets or add options, etc. # Adding something we can run - Output name matches target name add_executable(MyExample simple_example.cpp) # Make sure you link your targets with this command. It can also link libraries and # even flags, so linking a target that does not exist will not give a configure-time error. target_link_libraries(MyExample PRIVATE MyLibExample)
11. 使用技巧
选择c++标准
set_target_properties(myTarget PROPERTIES CXX_STANDARD 11 CXX_STANDARD_REQUIRED YES CXX_EXTENSIONS NO )
启用pisition independent code
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
集成ccache
find_program(CCACHE_PROGRAM ccache) if(CCACHE_PROGRAM) set(CMAKE_CXX_COMPILER_LAUNCHER "${CCACHE_PROGRAM}") set(CMAKE_CUDA_COMPILER_LAUNCHER "${CCACHE_PROGRAM}") # CMake 3.9+ endif()
集成clang-tidy
~/package # cmake -S . -B build-tidy -DCMAKE_CXX_CLANG_TIDY="$(which clang-tidy);-fix"
打印变量
message(STATUS "MY_VARIABLE=${MY_VARIABLE}")
include(CMakePrintHelpers) cmake_print_variables(MY_VARIABLE)
cmake_print_properties( TARGETS my_target PROPERTIES POSITION_INDEPENDENT_CODE )
trace cmake
cmake -S . -B build --trace-source=CMakeLists.txt
debug编译
-DCMAKE_BUILD_TYPE=Debug
12. 测试
add_test( NAME ExampleCMakeBuild COMMAND "${CMAKE_CTEST_COMMAND}" --build-and-test "${My_SOURCE_DIR}/examples/simple" "${CMAKE_CURRENT_BINARY_DIR}/simple" --build-generator "${CMAKE_GENERATOR}" --test-command "${CMAKE_CTEST_COMMAND}"