CMake 简介
- https://cmake.org/cmake/help / CMake 命令汇总 /
- 《Modern CMake for C++》 & 中文翻译 & Codes / 《CMake Cookbook》 & Codes /
- An Introduction to Modern CMake / 渐进式 CMake 项目示例 / CMake Example / C++ Starter Prj /
CMake Example / C++ Starter Prj 是不错的 CMake 示例仓库,包含了绝大部分常用 CMake 模板代码,因为 Github 访问较慢,可以下载到本地,或者构建自己的镜像
CMake Example basic 目录包含了基本的 CMake 用法,例如设置 lib 的编译方式(静态或动态 lib)、Header Only lib 的处理方式、Release/Debug 等编译模式的设置、第三方 lib 的引入方式、编译器选择与设置等。CMake Example 包含的其他示例包含 Target 使用、代码生成、静态分析和包管理等
cmake_minimum_required(VERSION 3.17)
project(ModernCMakeExample VERSION 1.0 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
add_library(MyLibExample simple_lib.cpp simple_lib.hpp)
add_executable(MyExample simple_example.cpp)
target_link_libraries(MyExample PRIVATE MyLibExample)
重要概念
CMake 自己不能构建任何东西——它依赖于系统中的其他工具来执行实际的编译、链接等任务。CMake 构建系统分为三个步骤:
-
配置(configure),配置变量,例如编译选项、是否开启某些功能等
-
生成(Generate),生成特定构建工具(例如:make、ninja 等)所需的文件,例如为 make 生成 Makefile
-
构建(Build),使用构建工具与对应的文件生成目标,例如
make -j7
目标
目标(target)是 CMake 中非常重要的概念,相同类型的 target 可以有多个,编译时系统会同时生成这些 target,也可以使用命令指定需要生成的 target。示例可以参考这里
CMake 的编译过程可以看作是一棵编译树(上图是 bazel 的示例,CMake 与之类似),编译树的根节点一般是可执行文件或者 .o 或 lib,也可以是源文件,例如:protobuf 。父节点依赖子节点
使用 CMake 命令可以可视化依赖:cmake --graphviz=test.dot .
,将生成的文件导入 Graphviz 工具即可查看依赖
依赖管理
大部分系统都包含了第三方模块,一些模块可能已经存在于系统之中,而另一些则没有。结合 find_package
和 FetchContent
可以避免不必要的下载过程(细节请参考 《Modern CMake for C++》 第 7.6 节)
CMake 提供了一些内置的 Find 模块,例如 Protobuf、Boost、curl 等,更多内容可以参考官网。很多 lib 提供了自己的 Find 模块,安装相关 lib 时 Find 模块也会被安装到系统中,可以使用 --debug-find
模块开启 CMake 的 find debug,从而显示 CMake 使用的本地模块路径(cmake --debug-find ..
)
功能
CMake 调试
参考 《Modern CMake for C++》 第一章,后文 《Modern CMake for C++》 简称 《MCC》
# 开启日志
cmake --system-information [file]
cmake --log-level=xxx
...
CMake 脚本
为了配置项目构建,CMake 提供了一种平台无关的编程语言,并附带了许多有用的命令。可以使用此工具编写项目附带的,或完全独立的脚本。可以使用 -P 选项执行脚本 cmake -P script.cmake
设置编译模式
参考:编译模式。生成命令:cmake .. -DCMAKE_BUILD_TYPE=Release
。顶层 cmake 文件添加如下内容
# # set(CMAKE_BUILD_TYPE Debug)
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
message("Setting build type to 'RelWithDebInfo' as none was specified.")
set(CMAKE_BUILD_TYPE RelWithDebInfo CACHE STRING "Choose the type of build." FORCE)
# Set the possible values of build type for cmake-gui
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" ...)
endif()
静态分析
CMake 除了编译,也可以帮助我们实现一些常用功能,例如代码格式化、Clang Static Analyzer、CppCheck 等,使用示例可以参考这里
其他一些静态分析,例如 Clang-tidy、Cpplint、include/link-what-you-use 可以参考《MCC》 9.3 节
cppcheck
参考:https://thatonegamedev.com/cpp/cmake-static-analyser-with-cppcheck/
find_program(CMAKE_CXX_CPPCHECK NAMES cppcheck)
if(CMAKE_CXX_CPPCHECK)
list(APPEND CMAKE_CXX_CPPCHECK "--enable=all"
"--suppressions-list=${PROJECT_SOURCE_DIR}/cppcheck_suppressions.txt"
"--error-exitcode=10")
endif()
cppcheck_suppressions.txt 示例
missingInclude
missingIncludeSystem
unusedFunction
unmatchedSuppression
clang-tidy
参考:https://ortogonal.github.io/cmake-clang-tidy/
set(CMAKE_CXX_CLANG_TIDY clang-tidy; -header-filter=.; -checks=*;
-warnings-as-errors=*;)
macos 安装方式:
brew install llvm
# add /opt/homebrew/opt/llvm/bin/ to PATH
动态分析
使用 ASAN/TSAN 等工具可以实现代码的动态分析,ASAN/TSAN 结合 CMake 的使用,可以参考 《CMake Cookbook》第 14 章
Valgrind 也可以结合 CMake,实现动态内存、线程等功能检查,Valgrind 执行效率相比于 ASAN/TSAN 要慢很多,所以不能每次 CI 都执行。相关细节可以参考《MCC》9.4 节
测试与打包
CMake 同时支持测试与打包(CTest&CPack)。CTest 与 CPack 请参考 这里
C++ 比较典型的两个测试框架是 Catch2 和 Gtest/Gmock,Catch2 比较适合小项目且较为简单(Catch 没有提供 Mock 工具)。Catch2 在 CMake 中的使用可以参考这里,文件下载完成后先后执行 cmake ..
、cmake --build .
和 ctest 即可触发单元测试,使用 ctest --output-on-failure
可以减少终端信息的输出
CMake 生态系统包括项目可以依赖的外部包,允许开发人员以无缝的、跨平台的方式使用库和工具。支持 CMake 的包应该提供一个配置文件,以便 CMake 理解如何使用它们
插件
CMake 项目可以使用外部模块来增强它们的功能。模块是用 CMake 语言编写的,包含宏定义、变量和执行各种功能的命令。范围从相当复杂的脚本 (CPack 和 CTest 也提供模块) 到相当简单的脚本
附录
CMake Starter
-
cpp-template 是一个使用 cmake 和 catch2 的 c++ 项目模板,可以使用这个项目作为 C++ 工程的起始环境
-
ModernCppStarter 是一个更完整也更复杂的 cmake 起始环境
常用命令/函数
转载:https://www.cnblogs.com/shuimuqingyang/p/13992358.html
set(CMAKE_GENERATOR "Ninja" CACHE INTERNAL "" FORCE)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}")
link_directories("./libs")
include_directories("./include")
add_subdirectory("./3dparty/opencv") # 也就是包含opencv文件夹下面的makelist.txt
add_library(faceattribute ${sourcefile})
add_executable(lightpredict test/lightpredict.cpp)
target_link_libraries(predict ${OpenCV_LIBS})
find_package(OpenCV REQUIRED ) # REQUIRED 表示 opencv 是必须找到的,如果找不到就会报错
aux_source_directory("./src" SRC) # 搜索目s录 ./src 下面所有源文件,赋值给变量 src