一,cmake概念
CMake是开源、跨平台的构建工具,可以让我们通过编写简单的配置文件去生成本地的Makefile,这个配置文件是独立于运行平台和编译器的,这样就不用亲自去编写Makefile了,而且配置文件可以直接拿到其它平台上使用,无需修改,非常方便。
要使用cmake,需要编写 CMakeLists.txt 文件,这个文件会被 cmake 工具解析。
执行cmake:cmake CMakeLists文件所在路径
二,cmake使用
CMakeLists.txt基本命令:
- CMAKE_MINIMUM_REQUIRED(VERSION 版本号):指定使用cmake的最低版本
- PROJECT(工程名):定义工程的名称
- ADD_EXECUTABLE(可执行文件名 所有需要用到的源文件):定义工程生成的可执行文件
bash
# 例如:(需要通过三个源文件生成一个可执行文件)
CMAKE_MINIMUM_REQUIRED(VERSION 3.0)
PROJECT(MAIN)
ADD_EXECUTABLE(main main.cpp a.cpp b.cpp)
SET
上面例子中,将所有需要用到的源文件全部写到ADD_EXECUTABLE中,这会导致维护起来十分困难,尤其在项目比较大的时候,源文件比较多的情况下。
定义变量:
- SET(变量名 值):定义一个变量并初始化其值
使用变量:${变量名}
在上面的情况下我们可以:
bash
# ADD_EXECUTABLE(main main.cpp a.cpp b.cpp)改为:
SET(SRC main.cpp a.cpp b.cpp)
ADD_EXECUTABLE(main ${SRC})
指定C++标准
- CMAKE_CXX_STANDARD:定义C++标准的宏
bash
# 例如设置C++14标准
SET(CMAKE_CXX_STANDARD 14)
指定可执行文件的输出路径
- EXECUTABLE_OUTPUT_PATH:定义可执行文件路径的宏
bash
# 例如:将生成的可执行文件放到一个不存在的目录下
SET(EXECUTABLE_OUTPUT_PATH ./output/)
搜索文件
其实上述的不论是在ADD_EXECUTABLE中加源文件,还是在SET中添加源文件都没有解决根本问题(源文件数量很大时难以维护)因此如果可以直接查找到目录下所有的源文件并保存在变量中,那么可以解决上述的问题
常用两个宏:
PROJECT_SOURCE_DIR:保存cmake命令后跟随的路径,也就是CMakeLists.txt所在的路径
CMAKE_CURRENT_SOURCE_DIR:保存CMakeLists.txt所在的路径
- AUX_SOURCE_DIRECTORY(搜索路径 变量名):将搜索路径下所有源文件存储在变量中。
bash
# 例如:查找CMakeLists.txt所在目录下所有源文件,存储在变量中
AUX_SOURCE_DIRECTORY(${PROJECT_SOURCE_DIR} SRC)
- FILE(GLOB/GLOB_RECURSE 变量名 搜索路径和搜索文件类型):将搜索路径下的所有满足类型的文件名存储在变量中(GLOB_RECURSE是进行递归查找,当前文件下的子目录也会进行查找)
bash
# 例如:查找CMakeLists.txt所在目录下所有源文件,存储在变量中
FILE(GLOB SRC ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp)
指定头文件搜索路径
当工程中源文件与头文件不在一个目录下时,为了使源文件能够找到头文件:我们直接改变源文件中的 #include "../include/head.h",但是这种方法在源文件很多时非常麻烦。因此如果我们能直接让所有源文件都去自己找到头文件,就可以解决
- INCLUDE_DIRECTORIES(所有头文件路径):在所有设置的路径下查找头文件
bash
# 例如:按照上图中,在include下找头文件
INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/include)
cmake制作库文件
在Linux下库文件分为两种:动态库(libxxx.so) 和 静态库(libxxx.a)
- ADD_LIBRARY(库名称 STATIC/SHARED 所有需要用到的源文件):将所有源文件制作成库文件(动态库:SHARED 静态库:STATIC)
- LIBRARY_OUTPUT_PATH:设置库文件输出路径的宏
bash
# 例如:目前需要将SRC(保存所有源文件的变量)制作为静/动态库并放到CMakeLists.txt所在目录的lib下
SET(LIBRARY_OUTPUT_PATH ${CMAKE_CURRENT_SOURCE_DIR}/lib)
# 静态库 libmain.a
ADD_LIBRARY(main STATIC ${SRC})
# 动态库 libmain.so
ADD_LIBRARY(main SHARED ${SRC})
cmake链接库文件
链接静态库
- LINK_LIBRARIES(所有需要库文件):为程序链接所有需要的静态库
- LINK_DIRECTORIES(所有库文件的搜索路径):指定库文件(静/动态库)搜索路径
bash
# 如上图,我需要链接静态库libmain.a去生成可执行文件
CMAKE_MINIMUM_REQUIRED(VIRSION 3.0)
PROJECT(MAIN)
AUX_SOURCE_DIRECTORY(${CMAKE_CURRENT_SOURCE_DIR}/ SRC)
SET(EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_SOURCE_DIR}/build/output)
INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/invlude)
LINK_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/lib.a)
LINK_LIBRARIES(libmain.a)
ADD_EXECUTABLE(main ${SRC})
链接动态库
TARGET_LINK_LIBRARIES(目标 PRIVATE/PUBLIC/INTERFACE 动态库1 ...):为程序链接所有需要动态库
- 目标:源文件,动态库文件,可执行文件
- PRIVATE/PUBLIC/INTERFACE:动态库的访问权限(不写默认为PUBLIC)
- 注意:一般链接库都放在CMakeLists.txt的文件末尾
其中
PUBLIC:其后面的库会被link到目标中,并且里面的符号会被导出,供第三发使用(a链接了b,c;d链接了a;d可以使用b,c的函数)
PRIVATE:其后面的库会被link到目标中,并且终结掉,第三方不知道你调了什么库(a链接了b,c;d链接了a;d不可以使用b,c的函数)
INTERFACE:其后面的库不会被link到目标中,只会导出符号(可以使用函数,但不知道哪个库的)
bash
CMAKE_MINIMUM_REQUIRED(VERSION 3.0)
PROJECT(MAIN)
AUX_SOURCE_DIRECTORY(${CMAKE_CURRENT_SOURCE_DIR}/ SRC)
SET(EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_SOURCE_DIR}/build/output)
INCLUDE_LIBRARIES(${CMAKE_CURRENT_SOURCE_DIR}/include)
LINK_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/lib/a ${CMAKE_CURRENT_SOURCE_DIR}/lib/so)
ADD_EXECUTABLE(main ${SRC})
TARGET_LINK_LIBRARIES(main libmain.so)
打印日志
- MESSAGE((无) | STATUS | WARNING | AUTHOR_WARNING | SEND_ERROR | FATAL_ERROR "日志信息"):根据不同的等级按照不同的形式输出日志
其中:
(无):什么都不写代表重要信息
STATUS:非重要信息,输出时前面带有"-- "
WARNING:警告信息
AUTHOR_WARNING:警告信息(dev)
SEND_ERROR:错误信息,不会终止执行
FATAL_ERROR:错误信息,终止执行
字符串操作
追加字符串
- SET(变量名 字符串/${变量} ...):将字符串拼接起来存储在变量中
- LIST(APPEND 变量名 字符串/${变量} ...):将字符串拼接起来存储在变量中
bash
# 例如:
AUX_SOURCE_DIRECTORY(${CMAKE_CURRENT_SOURCE_DIR}/src SRC)
# SET拼接元素
SET(TMP hello world)
SET(TMP1 ${TMP} ${SRC})
MESSAGE(${TMP})
MESSAGE(${TMP1})
# LIST拼接元素
LIST(APPEND TMP "xxx1" "sss2" ${SRC})
删除字符串
- LIST(REMOVE_ITEM 变量名 移除字符串 ...):移除变量中的字符串
bash
# 例如:移除5个源文件中没有用的main.cpp,使用其他4个源文件生成库文件
AUX_SOURCE_DIRECTORY(${CMAKE_CURRENT_SOURCE_DIR}/src SRC)
MESSAGE(${SRC})
LIST(REMOVE_ITEM SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/main.cpp)
MESSAGE(${SRC})
嵌套cmake
- ADD_SUBDIRECTORY(目录名称):将该目录构建为当前节点的子节点
父节点CMakeLists.txt中定义的变量,在子节点中是可见的,可以访问到
举例如下:
父节点:
bash
CMAKE_MINIMUM_REQUIRED(VERSION 3.15)
PROJECT(TEST)
# 创建父子节点关系
ADD_SUBDIRECTORY(calc)
ADD_SUBDIRECTORY(sort)
ADD_SUBDIRECTORY(test1)
ADD_SUBDIRECTORY(test2)
calc子节点:
bash
CMAKE_MINIMUM_REQUIRED(VERSION 3.15)
PROJECT(CALC)
AUX_SOURCE_DIRECTORY(${CMAKE_CURRENT_SOURCE_DIR}/ SRC)
INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/../include)
SET(LIBRARY_OUTPUT_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../lib)
# 生成静态库libcalc.a
ADD_LIBRARY(calc STATIC ${SRC})
sort子节点:
bash
CMAKE_MINIMUM_REQUIRED(VERSION 3.15)
PROJECT(SORT)
AUX_SOURCE_DIRECTORY(${CMAKE_CURRENT_SOURCE_DIR}/ SRC)
INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/../include)
SET(LIBRARY_OUTPUT_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../lib)
# 生成静态库libsort.a
ADD_LIBRARY(sort STATIC ${SRC})
test1子节点:
bash
CMAKE_MINIMUM_REQUIRED(VERSION 3.15)
PROJECT(TEST1)
AUX_SOURCE_DIRECTORY(${CMAKE_CURRENT_SOURCE_DIR}/ SRC)
INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/../include)
LINK_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/../lib)
# 链接静态库libcalc.a
LINK_LIBRARIES(calc)
SET(EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../bin)
# 生成可执行文件app1
ADD_EXECUTABLE(app1 ${SRC})
test2子节点:
bash
CMAKE_MINIMUM_REQUIRED(VERSION 3.15)
PROJECT(TEST2)
AUX_SOURCE_DIRECTORY(${CMAKE_CURRENT_SOURCE_DIR}/ SRC)
INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/../include)
LINK_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/../lib)
# 链接静态库libsort.a
LINK_LIBRARIES(sort)
SET(EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../bin)
# 生成可执行文件app2
ADD_EXECUTABLE(app2 ${SRC})