当前位置: 首页 > news >正文

CMake简单教程

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录

  • CMake
  • 常用命令及语法
    • message()
    • set()/unset()
    • list()
  • 控制流程
    • 条件流程控制
    • 循环控制
  • Scope作用域
    • 函数作用域
    • 文件夹作用域
  • 函数
  • 构建项目
    • 直接写入源码路径
    • 基本CMakeeLists构建
      • 基本项目结构
      • 基础配置
      • 添加库
      • 添加子目录(模块化)
      • 构建与编译
    • CMakeLists嵌套构建
      • 复杂项目结构
      • 根目录 CMakeLists.txt
      • 核心库 (src/core/CMakeLists.txt)
      • 命令行工具 (src/cli/CMakeLists.txt)
      • 单元测试 (tests/CMakeLists.txt)
      • 处理第三方依赖(以 JSON 库为例)
      • 跨平台特殊处理
      • 构建与安装


CMake

常用命令及语法

  1. CMake中的变量分为两种:CMake提供;自定义
  2. CMake变量的命名区分大小写
  3. CMake中的变量在存储时都是字符串
  4. CMake获取变量:${变量名}
  5. 变量的基础操作是set()与unset(),但也可以用list或是string操作变量
  6. 路径分隔符在 Windows 上是 ;,而在 Unix 上是 :

message()

打印调试信息,支持message(XXX)、message(“XXX”)、message([[XXX]]),跨行不能用第一种,message结尾自带换行

set()/unset()

set() 可定义变量

# 语法
set(<variable> <value>...[PARENT__SCOPE]) # 不加PARENT__SCOPE只在当前作用域生效,类似值传递,加了后可从函数内修改外部变量# 用例
set(CMAKE_CXX_STANDARD 11) # 定义变量CMAKE_CXX_STANDARD并赋值为1
set("My Var" YYY) # 设置变量My Var并赋值为YY,某些平台可能要对"做转义
set([[My Var]] ZZZ) # 设置变量My Var并赋值为ZZ。
# 对于含有空格的变量读取时要转义:message(${My\ Var}) 故变量名尽量不要带空格# 设置多个值
set(LISTVALUE a1 a2) # 内部存储时使用了;分割,定义时也可写成set(LISTVALUE a1;a2)
message(${LISTVALUE}) # 打印结果为a1a2

可重复设值,会覆盖原值。
set可以给一个变量设置多个值,此时相当于list,变量内部存储时使用”; ”分割,但显示时直接连接显示。

unset() 用于移除普通或缓存变量

# 语法
unset(<variable> [CACHE | PARENT_SCOPE])# 用例
unset(MY_VARIABLE) # 移除普通变量
unset(MY_CACHE_VARIABLE CACHE) # CMake缓存中移除指定的变量
unset(MY_PARENT_VARIABLE PARENT_SCOPE) # 从父作用域中移除指定变量
# 移除不存在的变量不会报错

设置环境变量
在 CMake 中,环境变量可以通过 $ENV{VAR_NAME} 进行读取,并通过 set(ENV{VAR_NAME} value) 进行临时修改(仅在当前 CMake 进程中有效)。

message($ENV{PATH}) # 打印PATH环境变量
set(ENV{CXX} "g++") # 临时指定C++编译器为g++
message($ENV{CXX}) # 打印结果g++
unset(ENV{CXX})  # 清除C++的环境变量
message($ENV{CXX})  # 此时再打印会报错该行,因为CXX变成空值

list()

遍历文件目录通常会得到一个list

# 语法,ACTION为操作类型,<list>为列表变量名
list(ACTION <list> [args...])list(APPEND <list> [<element>...]) # 列表添加元素
list(APPEND LIST1 ${LIST2})  # 合并 LIST2 到 LIST1
list(REMOVE_ITEM <list> <value> [value...]) # 列表删除元素
list(LENGTH <list> <output variable>) # 获取列表元素个数并返回到out variable中(如果提供了)
list(FIND <list> <value> <out-var>) # 在列表中查找元素并返回索引
list(INSERT <list> <index> [<element>...]) # 在index位置插入
list(REVERSE <list>) # 反转list
list(SORT <list> [...]) # 排序list(按字符串排序)# 用例
list(APPEND port p1 p2 p3) # 定义列表port={p1,p2,p3},和set(port p1 p2 p3)结果一致
list(LENGTH port len) # 读取列表port的元素个数并返回到len,此时len=3
list(FIND port p2 index) # 从列表port中查找元素p2,若找到则返回索引到index,此时index=1
list(REMOVE_ITEM port p2) # 从列表port中删除元素p2,此时port={p1,p3}
list(INSERT port 1 p2) # 在索引1位置插入元素p2,此时port={p1,p2,p3},也可将常量1替换为变量{index}

控制流程

条件流程控制

if…else…

# 语法
if(<condition>)
<commands>
elseif(<condition>)
<commands>
else() # 括号别忘了
<commands>
endif()# 用例
set(VARBOOL TRUE)
if(NOT VARBOOL OR VARBOOL) # 必为真,AND则必为假。还有LESS、EQUAL等message(TRUE)
else()message(FALSE)
endif()
# 打印结果为TRUE

循环控制

for

foreach(<loop_var> RANGE <max>) # 定义循环变量loop_var,从0到max<commands>
endforeach()foreach(<loop_var> RANGE <min> <max> [<step>]) # 从min到max,不写step默认步长为1foreach(<loop_variable> IN [LISTS <lists>] [lTEMS <items>]) # 先遍历列表,再遍历ITEMS后的变量# 用例
foreach(VAR RANGE 3)message(&{VAR})
endforeach()
# VAR从0到3,打印结果为0 1 2 3,有换行foreach(VAR IN LISTS MY_LIST ITEMS 4 f) # VAR先遍历取值LISTS,再遍历4、fmessage(${VAR})
endforeach()
# 打印结果为1 2 3 4 f

while

while(<condition>)<commands>
endwhile()

Scope作用域

函数作用域

类似C/C++等编程语言的作用域概念,函数内的值操作默认不改变外部环境,定义的变量只在当前作用域(函数体)内有效。

文件夹作用域

子文件夹CMakeLists.txt可以继承使用父文件夹CMakeLists.txt的变量

函数

# 定义函数
function(<name> [<argument>...]) # 第一个参数name为函数名称,必写,后续为参数<commands> # 可以用 return() 提前返回
endfunction()# 调用函数
<name>(<argument>...)

参数列表在定义时也可以不写,传入时照样传,然后用ARGV0、ARGV1、ARGV2来使用,定义时写的话等于定义了一个形参

function(MyFunc FirstArg)message("MyFunc Name: ${CMAKE_CURRENT_FUNCTION}") # 打印当前函数名message("FirstArg: ${FirstArg}") # 打印FirstArg(FirstArg接收了调用时传入的第一个参数)set(FirstArg "new value") # 修改函数内的局部变量message("FirstArg again: ${FirstArg}") # 再次打印FirstArg,结果为New valuemessage("ARGVO: ${ARGVO}") # 打印第一个参数,并非打印形参FirstArg,ARGV0是直接对实参取值(也是值传递)message("ARGV1: ${ARGV1}") # 打印第二个参数,若没传则为空message("ARGV2: ${ARGV2}") # 打印第三个参数,同上
endfunction()set(Arg "first value")
MyFunc(${Arg} "value")
message("Arg: ${Arg}")
# 打印结果为
MyFunc Name: MyFunc
FirstArg: first value
FirstArg again: new value
ARGVO: first value # 打印第一个实参,而非FirstArg
ARGV1: value # 传入的第二个实参
ARGV2:  # 没传打印空
Arg: first value # 函数内部没有改变外部变量

${Arg}是值传递,MyFunc(Arg “value”)是显式传递变量,同时用set的PARENT_SCOPE才可影响外部,只有其一还不行。

# 语法
macro(<name> [<argument>...])<commands>
endmacro()# 调用, 无返回值,可通过修改外部变量实现类似效果
<name>(<argument>...)

类似C++中的宏函数,是文本替换(等于引用传递),没有独立作用域,直接修改参数会影响外部变量,适合全局变量或简单代码,不建议,直接用函数。

构建项目

目前有四种方式:直接写入源码路径(基本)、调用子目录的cmake脚本、CMakeLists嵌套(常用)、Object Library。这里只介绍基本的和常用的方法。

直接写入源码路径

add_executable() 添加可执行文件,在源码引入头文件需写明相对路径。

# 语法
add_executable(<target_name> [WIN32] [MACOSX_BUNDLE] [EXCLUDE_FROM_ALL] source1 [source2 ...])
# <target_name>:可执行文件的名称。[]为配置属性。source 源文件,直接列出所有 .cpp/.c 文件(或通过变量传递)。# 用例
add_executable(my_app src/main.cpp src/helper.cpp)
# 编译src下的main.cpp和helper.cpp文件,将编译生成的目标文件链接在一起,最终生成my_app可执行文件set(SOURCESsrc/main.cppsrc/helper.cpp
) # 换行写更直观,add_executable()内也可换行写
add_executable(my_app ${SOURCES}) # 使用变量传递源文件

基本CMakeeLists构建

CMake 构建一个项目通常包括以下步骤:从项目配置、源代码管理、构建目标定义,到最终的编译和安装。

基本项目结构

 my_project/
├── CMakeLists.txt          # 根配置文件
├── include/                # 公共头文件
│   └── mylib.h
├── src/                    # 源代码
│   ├── main.cpp
│   └── mylib.cpp
└── build/                  # 构建目录(推荐)

基础配置

cmake_minimum_required(VERSION 3.10)  # 最低 CMake 版本
project(MyProject VERSION 1.0         # 项目名称和版本DESCRIPTION "A simple project"LANGUAGES CXX)               # 语言:CXX(C++), Cset(CMAKE_CXX_STANDARD 11)			  # 设置 C++ 标准
set(CMAKE_CXX_STANDARD_REQUIRED ON)# 添加可执行文件
add_executable(my_app src/main.cpp)# 添加头文件路径
target_include_directories(my_app PRIVATE include)# 可选:添加编译选项
target_compile_options(my_app PRIVATE -Wall -Wextra)

添加库

# 创建静态库
add_library(mylib STATIC src/mylib.cpp)
target_include_directories(mylib PUBLIC include)  # PUBLIC 头文件对外可见# 将库链接到可执行文件
target_link_libraries(my_app PRIVATE mylib)

添加子目录(模块化)

如果项目有子模块:

add_subdirectory(lib)  # 子目录需有自己的 CMakeLists.txt

构建与编译

生成构建系统
在项目根目录下:

mkdir build && cd build   # 推荐外部构建(保持源码干净)
cmake ..                 # 生成构建文件(Makefile/MSVC等)

编译项目

cmake --build .          # 跨平台编译(等效于 make/ninja)
# 或直接调用原生工具:
make                     # Linux/macOS
msbuild MyProject.sln    # Windows (Visual Studio)

运行程序

./my_app                 # Linux/macOS
.\Debug\my_app.exe       # Windows (默认 Debug 模式)

CMakeLists嵌套构建

复杂项目结构

data_tool/
├── CMakeLists.txt                # 根配置
├── cmake/                        # 自定义 CMake 模块
│   └── FindMathFunctions.cmake
├── include/
│   └── data_tool/
│       ├── core.h                # 核心库头文件
│       └── utils.h
├── src/
│   ├── core/                     # 核心库代码
│   │   ├── CMakeLists.txt
│   │   └── core.cpp
│   ├── cli/                      # 命令行工具
│   │   ├── CMakeLists.txt
│   │   └── main.cpp
│   └── utils/                    # 工具函数
│       ├── CMakeLists.txt
│       └── utils.cpp
├── tests/                        # 单元测试
│   ├── CMakeLists.txt
│   └── test_core.cpp
├── thirdparty/                   # 第三方依赖(可选)
└── build/                        # 构建目录

根目录 CMakeLists.txt

cmake_minimum_required(VERSION 3.14)
project(DataToolVERSION 1.0.0DESCRIPTION "A data processing tool with core library and CLI"LANGUAGES CXX
)# 设置 C++17 标准
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)# 全局编译选项
if(MSVC)add_compile_options(/W4 /WX)
else()add_compile_options(-Wall -Wextra -Wpedantic -Werror)
endif()# 启用测试
option(BUILD_TESTING "Build unit tests" ON)
if(BUILD_TESTING)enable_testing()include(CTest)
endif()# 添加子目录
add_subdirectory(src/core)
add_subdirectory(src/utils)
add_subdirectory(src/cli)if(BUILD_TESTING)add_subdirectory(tests)
endif()# 安装配置
install(DIRECTORY include/data_toolDESTINATION includeFILES_MATCHING PATTERN "*.h"
)# 导出库配置(供其他项目使用)
include(CMakePackageConfigHelpers)
configure_package_config_file(cmake/DataToolConfig.cmake.in${CMAKE_CURRENT_BINARY_DIR}/DataToolConfig.cmakeINSTALL_DESTINATION lib/cmake/DataTool
)install(FILES ${CMAKE_CURRENT_BINARY_DIR}/DataToolConfig.cmakeDESTINATION lib/cmake/DataTool
)

核心库 (src/core/CMakeLists.txt)

# 创建动态库(将 SHARED 改成 STATIC 即为静态库)
add_library(data_tool_core SHAREDcore.cpp
)# 头文件路径(PUBLIC 表示依赖此库的目标自动包含头文件)
target_include_directories(data_tool_corePUBLIC${CMAKE_CURRENT_SOURCE_DIR}/../../include
)# 链接其他内部库(如 utils)
target_link_libraries(data_tool_corePRIVATEdata_tool_utils
)# 安装库和头文件
install(TARGETS data_tool_coreEXPORT DataToolTargetsLIBRARY DESTINATION lib      # 动态库(Linux/Mac)ARCHIVE DESTINATION lib      # 静态库RUNTIME DESTINATION bin      # Windows 动态库
)

命令行工具 (src/cli/CMakeLists.txt)

# 创建可执行文件
add_executable(data_tool_climain.cpp
)# 链接核心库
target_link_libraries(data_tool_cliPRIVATEdata_tool_core
)# 安装到 bin 目录
install(TARGETS data_tool_cliDESTINATION bin
)

单元测试 (tests/CMakeLists.txt)

# 使用 GoogleTest(通过 FetchContent 自动下载)
include(FetchContent)
FetchContent_Declare(googletestGIT_REPOSITORY https://github.com/google/googletest.gitGIT_TAG release-1.11.0
)
FetchContent_MakeAvailable(googletest)# 添加测试可执行文件
add_executable(test_coretest_core.cpp
)# 链接核心库和 GTest
target_link_libraries(test_corePRIVATEdata_tool_coreGTest::GTestGTest::Main
)# 注册测试
add_test(NAME core_test COMMAND test_core)

处理第三方依赖(以 JSON 库为例)

# 方法1:使用 find_package(需提前安装)
find_package(nlohmann_json 3.10 REQUIRED)# 方法2:直接嵌入源码(通过 FetchContent)
include(FetchContent)
FetchContent_Declare(jsonURL https://github.com/nlohmann/json/releases/download/v3.10.5/json.tar.xz
)
FetchContent_MakeAvailable(json)target_link_libraries(data_tool_corePRIVATEnlohmann_json::nlohmann_json
)

跨平台特殊处理

# Windows 特定配置
if(WIN32)target_compile_definitions(data_tool_corePRIVATE_CRT_SECURE_NO_WARNINGS)
endif()# MacOS 动态库安装路径
if(APPLE)set_target_properties(data_tool_corePROPERTIESINSTALL_RPATH "@loader_path/../lib")
endif()

构建与安装

# 生成构建系统
mkdir build && cd build
cmake .. -DCMAKE_INSTALL_PREFIX=/usr/local# 编译
cmake --build . --config Release# 运行测试
ctest --output-on-failure# 安装到系统
cmake --install .
http://www.dtcms.com/a/302798.html

相关文章:

  • 智能指挥调度系统:数字化时代的协同决策中枢
  • 从0到1学PHP(一):PHP 基础入门:开启后端开发之旅
  • 基于 OpenCV 与 sklearn 的数字识别:KNN 算法实践
  • 【CDA干货】金融超市电商App经营数据分析案例
  • 星辰大海的征途:星宸科技的中国芯片突围战
  • 【行测】常识判断1
  • 【Unity笔记03】#if的用法和命名空间
  • EXCEL怎么提取表名
  • 在CentOS上以源码编译的方式安装PostgreSQL
  • 【51单片机2位数码管跑马灯】2022-9-25
  • 时间数字转换器TDC的FPGA方案及核心代码
  • 51单片机如何实现round函数
  • Java 大视界 -- 基于 Java 的大数据实时流处理在智能电网分布式能源接入与电网稳定性保障中的应用(368)
  • 【Linux】重生之从零开始学习运维之mysql用户管理
  • live-server的使用以及离线环境安装
  • CMake、CMakeLists.txt 基础语法
  • Linux系统之Ansible安装与入门
  • WPF,窗口拖动事件与窗口内控件点击事件
  • c++ 中的字符串相关的操作
  • python办自动化--利用vba或者python按需求读取excel文件指定列,更改列名后,按照要求将列排序,最后填充空白单元格
  • k8s中Nvidia节点驱动的配置问题
  • Go 语言-->指针
  • 2025年人工智能三大突破:多模态推理、具身智能与全球治理
  • ATF简介
  • 汽车膨胀水箱(副水箱)液位传感器的作用
  • Linux DNS解析3 -- DNS解析代理配置使用
  • Android 媒体播放开发完全指南
  • 量子计算新势力,微美全息FPGA方案解锁大幅优化与性能提升密码
  • 在Windows下读写Linux EXT文件系统文件
  • 为什么bert是双向transformer