CMake学习笔记

发布于:2025-07-06 ⋅ 阅读:(21) ⋅ 点赞:(0)

一、CMake基础

比Makefile的优势:支持跨平台。CMake大多数情况下会调用生成Makefile。

1.编译流程

  1. 预处理(-E 宏替换等)
  2. 编译 gcc/msvc/clang(-S
  3. 汇编(-C 生成.o或.obj文件)
  4. 连接(将多个二进制文件连接成一个可执行文件)

2.CMake流程

  1. 编写CMakeLists.txt文件,下面是最基本的配置
    • cmake_minimum required(VERSION 3.20)#最小版本
    • project(Hello)#项目名
    • add_executable(Hello hello.cpp)#由源文件生成一个可执行的程序
  2. cmake -B build
    • 创建一个build并在此目录下生成makefile或其他文件
  3. cmake --build build
    • 生成项目

也可以使用 cmake -P *.cmake直接构建项目

3.Windows使用

3.1.流程

  • 默认MSVC(vs2022与vs2019)
  • 可以安装 MinGW(gcc与clang)
  • cmake参数:
    • -G 指定生成器
    • -T 指定生成器的工具集
    • -B 指定生成目录 用于构建系统
    • -S 指定源文件目录
    • -P 指定.cmake脚本用于运行 CMake 脚本文件,而不需要生成构建系统或构建项目
    • --build 指定目录 用于构建项目
  • 通过指定-G "MinGW Makefiles"来指定cmake使用gcc

3.2.案例

CMakeLists.txt代码:

//指定CMake最低版本
cmake_minimum_required(VERSION 3.20) 
//指定项目名称
project(Hello) 
//生成可执行文件,输入分别为生成的可执行文件名称,源文件
add_executable(Hello hello.cpp) 

命令行编译链接:

cmake -B build #构建项目方法1,默认使用msvc,build为指定生成目录
cmake -B build -G "MinGW Makefiles" #构建项目方法2,可选使用gcc

cmake --build build #生成可执行文件

build/Debug/Hello.exe #执行msvc构建的可执行文件
build/Hello.exe #执行gcc构建的可执行文件

4.Linux使用

4.1.源码安装

sudo apt-get install build-essential
tar -zxvf cmake-3.28.0.tar.gz
sudo wget https://cmake.org/files/v3.28/cmake-3.28.0.tar.gz
*cd cmake-3.28.0
./configure
sudo make
sudo make install
cmake --version #检查是否安装成功

4.2.完整案例

CMakeLists.txt代码:

cmake_minimum_required(VERSION 3.20)
project(Hello)
add_executable(Hello hello.cpp)

命令行:

cmake -B build #构建项目
cmake --build build #生成可执行文件
build/hello #执行构建的可执行文件
rm -rf build #清除缓存

二、CMake语法

1.message

打印。

cmake_minimum_required(VERSION 3.20)
//常规打印
message("hello")
message(hello)
messgae("abc
def")
messgae([[abc
def]])

//使用 ${}获取变量
message(${CMAKE_VERSION}) //版本号
message($ENV{PATH}) //环境变量

2.set

可以给一个变量设置多个值。变量以字符串形式存储。

cmake_minimum_required(VERSION 3.20)
//常规设置
set(Var1 YZZY)
set(Var1 "YZZY") //又没有引号都可以

//有空格的变量
set("My Var1" "YZZY")
set([[My Var1]] "YZZY")
message(${My\ Var}) //需要用到转义字符

//设置多个变量,重复定义为覆盖
set(Var1 a1 a2)
set(Var1 a1;a2)

//设置环境变量
set(ENV{CXX} "g++") //作用域为当前项目
message($ENV{CXX})

//去除变量
unset(ENV{CXX}) 

3.list

  • list(APPEND <list>[<element>...]) 列表添加元素
  • list(REMOVE_ITEM <list><value>[value...]) 列表删除元素
  • list(LENGTH <list><output variable>) 获取列表元素个数
  • list(FIND <list><value><out-var>) 在列表中查找元素返回索引
  • list(INSERT <list> <index>[<element>...]) 在index位置插入
  • list(REVERSE <list>) 反转list
  • list(SORT <list> [..]) 排序list
cmake_minimum_required(VERSION 3.20)
//常规变量定义
set(var1 a1 a2 a3)
list(APPEND var2 a1 a2 a3)

//长度获取
list(LENGTH var2 len)//计算var2的长度,结果返回给len

//查找对象
list(FIND var2 a2 index)//查找var2中的a2,索引结果返回给index

//删除
list(REMOVE_ITEM var2 a3)//删除var2中的a3

//插入
list(INSERT var2 3 a4)//在索引3后边插入a4

//反转
list(REVERSE var2)//反转

//排序
list(SORT var2)//反转

4.流程控制

4.1.if 条件控制

  • LESS 小于
  • EQUAL 等于
  • LESS_EQUAL 小于等于
  • GREATER 大于
  • GREATER_EQUAL 大于等于
  • STREQUAL, STRLESS, STRLESS_EQUAL, STRGREATER, STRGREATER_EQUAL 字符串比较
cmake_minimum_required(VERSION 3.20)
//常规变量定义
set(var1 TRUE)
set(var2 FALSE)
set(var3 TRUE)
//与或非
if(NOT var1 AND var2 OR var3)
	message("haha")
elseif(var1 AND var2)
	message("ok")
else()
	message("ohno")
endif()

4.2.foreach 循环控制

foreach(<loop_var> RANGE <max>)
<commands>
endforeach()

foreach(<loop_var> RANGE <min> <max> [<step>])

foreach(<loop variable>IN [LISTS <lists>] [lTEMS <items>]) //LISTS声明后续为列表;ITEMS声明后续为元素

foreach(<loop variable>IN [ZIP_LISTS <lists>]) //ZIP_LISTS声明后续为压缩列表

ZIP_LISTS示例:

set(L1 one two three)
set(L2 1 2 3)
foreach(num IN ZIP_LIST L1 L2)
	message("word = ${num_0}, num = ${num_1}")
endforeach()
//太抽象了,这个索引方式也很抽象……

4.3.while 循环控制

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

5.函数

${ARG0}${ARG1} 取第一、二个参数值。

//函数定义
function(<name> [<argument>...])//[<argument>...]的名字可以省略
<commands>
endfunction()

6.作用域

1.Function scope 函数作用域,和C++差不多。
2. Directory scope 当从add_subdirectory()命令执行嵌套目录中的CMakeLists.txt列表文件时,注意父CMakeLists.txt其中的变量可以被子CMakeLists.txt使用

7.宏

过于灵活,尽量不要用宏。和函数对文件整体的作用域不同。

macro(<name> [<argument>...])
<commands>
endmacro()

三、项目构建

1.直接写入源码路径

  1. 在main.cpp 源码中需要写清头文件的相对路径
  2. 在CMakeLists.txt页写清楚需要编译构建的cpp文件路径
/*CMakeLists.txt*/
cmake_minimum_required(VERSION 3.20)
project(Hello)
add_executable(Hello hello.cpp bin/hi.cpp)//源码中需要写清头文件的相对路径

2.调用子目录中cmake脚本

  1. 子目录中的.cmake文件,也是可以被CMakeLists.txt调用的。
  2. CMakeLists.txt中使用include方法可以引入cmake后缀的配置文件。
  3. main.cpp中需要写清头文件目录。

子目录的.cmake中:

set(sources bin/dog.cpp bin/cat.cpp)

CMakeList.txt中:

cmake_minimum_required(VERSION 3.20)
project(Hello)
//直接指定文件
include(bin/son.cmake)

add_executable(Hello main.cpp ${sources})

3.CMakeLists嵌套

  1. 在子目录的CMakeList.txt中生成库文件add_library
  2. 在主目录的CMakeList.txt中连接库文件
  • include_directories 全局的头文件目录的声明
  • target_include_directories 可指定可见性的头文件目录的声明
  • link_libraries 全局的库文件连接
  • target_link_libraries 可指定可见性的库文件连接
  • add_subdirectory 添加子目录(用于放子目录的CMakeList.txt)
  • add_library 生成库文件(默认 STATIC library)

在子目录/bin中,CMakeList.txt中:

//指定库名称,以及源文件
add_library(HelloLib cat.cpp dog.cpp)

在main.cpp中,无需写清头文件相对路径。
在主目录CMakeList.txt中:

cmake_minimum_required(VERSION 3.20)
project(Hello)
//声明子目录的CMakeList.txt(库文件)所在位置
add_subdirectory(bin)
//构建可执行文件
add_executable(Hello main.cpp)
//连接库文件
target_link_libraries(Hello PUBLIC HelloLib)
//声明头文件所在目录
target_include_directories(Hello PUBLIC ${PROJECT_SOURCE_DIR}/bin)
  • PROJECT_BINARY_DIR build二进制文件目录
  • PROJECT_SOURCE_DIR 源码目录

4.Object Library

CMake 3.12起步。
子目录CMakeList.txt中:

//写法1:一起写
add_library(HelloLib cat.cpp dog.cpp)
//写法2:分开写
add_library(CatLib cat.cpp)
add_library(DogLib dog.cpp)
//用于设置库所包含的目录,以及可见性;
//可以将头文件放在此目录,免去main.cpp中写相对路径
target_include_directories(HelloLib PUBLIC .)

主目录CMakeList.txt中:

cmake_minimum_required(VERSION 3.20)
project(Hello)
//声明子目录的CMakeList.txt(库文件)所在位置
add_subdirectory(bin)
add_executable(Hello main.cpp)
//连接库文件
target_link_libraries(Hello PUBLIC HelloLib)

四、静态库与动态库

  • 静态库 lib<name>.so/dll
    在连接阶段,会将汇编生成的目标文件.o与引用到的库一起链接打包到可执行文件中。因此对应的链接方式称为静态连接。对函数库的连接是在编译时完成的,静态库对空间的浪费是巨大的。

  • 动态库 lib<name>.a/lib
    动态库不是在编译时被连接到目标代码中,而是运行时是才被载入。

1.生成

  • file()常用于搜索源文件
    • file(GLOB SOURCES "*.cpp" "*.h") 查找与指定模式匹配的文件
    • file(GLOB_RECURSE SOURCES "src/*.cpp" "include/*.h") 递归查找与指定模式匹配的文件
  • add_library(animal STATIC ${SRC})生成静态库
  • add_library(animal SHARED ${SRC})生成动态库
  • ${LIBRARY OUTPUT PATH}导出目录

主目录的CMakeList.txt中:

cmake_minimum_required(VERSION 3.20)
project(Hello)

// 搜索指定文件夹下,符合*.cpp格式的文件,放入到SRC中
file(GLOB SRC ${PROJECR_SOURCE_DIR}/src/*.cpp)
include_directories(${PROJECT_SOURCE_DIR}/include)
// 指定静态库生成到该目录。静态库不可执行(存疑)
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/a)
add_library(MyProject STATIC ${SRC})

// 指定动态库生成到该目录,动态库可以执行
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/so)
add_library(MyProject SHARED ${SRC})

2.调用

2.1.静态库调用流程

  1. 引入头文件
  2. 声明库目录
  3. 连接静态库
  4. 生成可执行的二进制文件

CMakeList.txt中:

cmake_minimum_required(VERSION 3.20.0)
project(Animal CXX)
//全局声明头文件目录
include_directories(${PROJECT SOURCE DIR}/include)
//全局声明库文件目录,可以替代add_subdirectory
link_directories(${PROJECT SOURCE DIR}/a)
//连接库文件
link_libraries(animal)
add_executable(app main.cpp)

2.2.动态库调用流程

  1. 引入头文件
  2. 声明库目录
  3. 生成可执行二进制文件
  4. (运行时)连接动态库
cmake_minimum_required(VERSION 3.20.0)
project(Animal CXX)
//全局声明头文件目录
include_directories(${PROJECT SOURCE DIR}/include)
//全局声明库文件目录,可以替代add_subdirectory
link_directories(${PROJECT SOURCE DIR}/so)

add_executable(app main.cpp)
//动态库必须使用target_(存疑)
target_link_libraries(app PUBLIC animal)

五、源文件交互

常规的CMake文件开头设置C++标准:

set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)

相当于一个模板,使得.cpp文件可以获取CMakeLists.txt中的一些信息。

  • configure_file(输入文件目录,输出文件目录)常用于配置文件的生成

CMakeList.txt中:

cmake_minimum_required(VERSION 3.20.0)
project(Animal CXX)
//设置C++标准
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)
//生成配置文件
configure_file(config.h.in config.h)
//声明库所在的子文件
add_subdirectory(bin)
add_executable(Hello main.cpp)

//连接库
target_link_libraries(Hello PUBLIC HelloLib)
//声明头文件所在目录
target_include_directories(Hello PUBLIC ${PROJECT_SOURCE_DIR} ${PROJECT_SOURCE_DIR}/bin)

config.h.in中:

//获取当前设定的C++版本
#define CMAKE_CXX_STANDARD ${CMAKE_CXX_STANDARD}

main.cpp,便可以直接调用:

#include config.h
....
cout<<CMAKE_CXX_STANDARD<<endl;
...

六、条件编译

通过不同的传入参数,编译不同的文件。

1.流程

  1. 用option定义变量
  2. 在子CMakeLists.txt中根据变量ON还是OFF来修改SRC(源文件)以及target_compile_definitions
  3. 修改源文件根据变量选择代码
  4. 执行命令时-D<变量>=ON/OFF来进行条件编译

2.可见性限定

  • PUBLIC 本目标需要用,依赖这个目标的其他目标也需要
  • INTERFACE 本目标不需要,依赖本目标的其他目标需要
  • PRIVATE 本目标需要,依赖这个目标的其他目标不需要

3.基于CMakeLists嵌套

子文件夹的CMakeLists.txt中,可以用条件判断:

option(USE_CATTWO "Use cat two" ON)
if(USE_CATTWO)
	set(SRC cat.cpp cattwo.cpp)
else()
	set(SRC cat.cpp)
endif()

add_library(MyLib ${SRC})

if(USE_CATTWO)
	targrt_compile_definitions(MyLib PRIVATE "USE_CATTWO")
endif()