CMake 简介

2023-01-07
5 min read
  1. https://cmake.org/cmake/help / CMake 命令汇总 /
  2. 《Modern CMake for C++》 & 中文翻译 & Codes / 《CMake Cookbook》 & Codes /
  3. 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 构建系统分为三个步骤:

  1. 配置(configure),配置变量,例如编译选项、是否开启某些功能等

  2. 生成(Generate),生成特定构建工具(例如:make、ninja 等)所需的文件,例如为 make 生成 Makefile

  3. 构建(Build),使用构建工具与对应的文件生成目标,例如 make -j7

目标

目标(target)是 CMake 中非常重要的概念,相同类型的 target 可以有多个,编译时系统会同时生成这些 target,也可以使用命令指定需要生成的 target。示例可以参考这里

CMake 的编译过程可以看作是一棵编译树(上图是 bazel 的示例,CMake 与之类似),编译树的根节点一般是可执行文件或者 .o 或 lib,也可以是源文件,例如:protobuf 。父节点依赖子节点

使用 CMake 命令可以可视化依赖:cmake --graphviz=test.dot . ,将生成的文件导入 Graphviz 工具即可查看依赖

依赖管理

大部分系统都包含了第三方模块,一些模块可能已经存在于系统之中,而另一些则没有。结合 find_packageFetchContent 可以避免不必要的下载过程(细节请参考 《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 AnalyzerCppCheck 等,使用示例可以参考这里

其他一些静态分析,例如 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

  1. cpp-template 是一个使用 cmake 和 catch2 的 c++ 项目模板,可以使用这个项目作为 C++ 工程的起始环境

  2. 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
Previous Make&GCC 简介