一、CMake基础
比Makefile的优势:支持跨平台。CMake大多数情况下会调用生成Makefile。
1.编译流程
- 预处理(
-E
宏替换等) - 编译 gcc/msvc/clang(
-S
) - 汇编(
-C
生成.o或.obj文件) - 连接(将多个二进制文件连接成一个可执行文件)
2.CMake流程
- 编写CMakeLists.txt文件,下面是最基本的配置
cmake_minimum required(VERSION 3.20)
#最小版本project(Hello)
#项目名add_executable(Hello hello.cpp)
#由源文件生成一个可执行的程序
cmake -B build
- 创建一个build并在此目录下生成makefile或其他文件
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>)
反转listlist(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.直接写入源码路径
- 在main.cpp 源码中需要写清头文件的相对路径
- 在CMakeLists.txt页写清楚需要编译构建的cpp文件路径
/*CMakeLists.txt*/
cmake_minimum_required(VERSION 3.20)
project(Hello)
add_executable(Hello hello.cpp bin/hi.cpp)//源码中需要写清头文件的相对路径
2.调用子目录中cmake脚本
- 子目录中的.cmake文件,也是可以被CMakeLists.txt调用的。
- CMakeLists.txt中使用include方法可以引入cmake后缀的配置文件。
- 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嵌套
- 在子目录的CMakeList.txt中生成库文件
add_library
- 在主目录的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.静态库调用流程
- 引入头文件
- 声明库目录
- 连接静态库
- 生成可执行的二进制文件
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.动态库调用流程
- 引入头文件
- 声明库目录
- 生成可执行二进制文件
- (运行时)连接动态库
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.流程
- 用option定义变量
- 在子CMakeLists.txt中根据变量ON还是OFF来修改SRC(源文件)以及target_compile_definitions
- 修改源文件根据变量选择代码
- 执行命令时-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()