CMake基本用法

生成器:

构建步骤

生成Makefile文件

下面两种方法二选一

source code选择项目根目录,binaries选择项目根目录/build,再点击左下方的Configure按钮,选择生成器MinGW Makefiles,点击确定后,会产生下面的列表待填写,其中有三项基本的参数

Name Value 含义
CMAKE_BUILD_TYPE Release 构建类型: Debug或Release
CMAKE_GNUtoMS 不勾选 是否同时生成适用于MSVC编译器的.lib库
CMAKE_INSTALL_PREFIX 项目根目录/install 构建后的可执行程序或库文件的安装目录,可自定义

填写完参数后,点击左下方的Generate按钮,会自动在项目根目录/build中生成Makefile文件。

如果想要修改参数,需要点击左上角菜单栏的File: Delete Cache删除缓存,再重复上面的操作。

# 进入到项目根目录后执行下面的命令
cmake -G "MinGW Makefiles" `
    -B build `
    -S . `
    -DCMAKE_BUILD_TYPE=Release `
    -DCMAKE_GNUtoMS=false `
    -DCMAKE_INSTALL_PREFIX="./install"
# -G: 选择生成器 ("MinGW Makefiles"或"Ninja")
# -B: 指定构建的目录
# -S: 指定源码目录 (CMakeLists.txt所在目录)
# -D: 定义CMake变量

开始构建

cmake --build build

安装构建好的库或可执行程序

cmake --install build --prefix ./install

CMake语法

智能代码提示

CMakeLists.txt使用方法

设置要求使用的CMake工具的最低版本

cmake_minimum_required(VERSION 3.10.0)

设置项目名称

project(project_demo)

设置C++使用标准

# 设置 C++ 标准为 C++20
set(CMAKE_CXX_STANDARD 20)
# 强制要求编译器支持所选的 C++ 标准
set(CMAKE_CXX_STANDARD_REQUIRED ON)

生成compile_commands.json文件

让每次配置时,都重新生成compile_commands.json文件,防止智能提示失败。

set(CMAKE_EXPORT_COMPILE_COMMANDS True)

设置变量

就是生成一个键值对,方便后面使用。

# 第一个参数是变量名,之后的多个参数是变量值(不支持通配符)。
# 用空格或;或换行隔开。
set(SRC_LIST main.cpp add.cpp div.cpp mult.cpp sub.cpp)

搜索目录或文件

如果项目里的源文件很多,手动罗列出来太麻烦,就可以用此方法自动搜索一个目录下的所有文件。

搜索指定的目录

# 第一个参数是待搜索的目录(不可以指定文件),第二个参数是把搜索到的结果赋值给一个变量。(相当于set的功能)
aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/src SRC_LIST)

搜索指定的文件

# 第一个参数是GLOB或GLOB_RECURSE,第二个参数是变量名,第三个参数是待搜索的文件(可以用通配符)。
# GLOB_RECURSE表示递归搜索目录,会自动搜索每个子目录。 
file(GLOB SRC_LIST ${CMAKE_CURRENT_SOURCE_DIR}/src/main.cpp)
file(GLOB HEAD_LIST ${CMAKE_CURRENT_SOURCE_DIR}/include/*.cpp)
file(GLOB HEAD_LIST ${CMAKE_CURRENT_SOURCE_DIR}/include/*.h)

添加头文件

为当前项目添加头文件所在的目录,以便编译器能找到头文件。
如果需要添加多个目录,要分多行写。

# ${PROJECT_SOURCE_DIR}表示当前项目的根目录
include_directories(${PROJECT_SOURCE_DIR}/include)
# ../表示当前CMakeLists.txt的上一层目录
include_directories("../../../src/calculate/include")
# 下面的方法表示绝对路径,注意Windows下要使用\\或者/
include_directories("C:\\c_extensions\\include\\googletest\\googlemock\\include")

生成静态、动态库

# 库文件的全名分为三部分:lib+库名字+.a,只需要指定出库的名字就可以了,另外两部分会自动生成。
# 第二个参数表示:静态库STATIC、动态库SHARED。如果不写,则默认生成静态库。
add_library(math STATIC src/add.cpp src/sub.cpp)
add_library(math SHARED src/add.cpp src/sub.cpp)

设置库生成后输出的目录

适用于动态库

对于生成的库文件来说和可执行程序一样都可以指定输出路径。由于在Linux下生成的动态库默认是有执行权限的,所以可以按照生成可执行程序的方式去指定它生成的目录。

set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)

其实就是通过set命令给EXECUTABLE_OUTPUT_PATH宏设置了一个路径,这个路径就是可执行文件生成的路径。

都适用

由于在Linux下生成的静态库默认不具有可执行权限,所以在指定静态库生成的路径的时候就不能使用EXECUTABLE_OUTPUT_PATH宏了,而应该使用LIBRARY_OUTPUT_PATH,这个宏对应静态库文件和动态库文件都适用。

set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)

生成可执行程序

# 其中demo表示最后生成的可执行程序的名称(不需要与项目名称相同),即会生成demo.exe(如果是Linux,则没有.exe后缀)
# 后面可以添加任意多个源码文件,用空格或;或换行隔开。
add_executable(
    demo
    main.cpp
    ${SRC_FILES}
    ${TEST_SRCS}
)

设置可执行程序生成后的输出目录

# 如果中间有子目录,CMake工具会自动生成,无需自己手动创建。
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)

链接库

搜索库目录或库文件

如果是系统的静态、动态库,则不需要此方法。
如果是第三方库或自己制作的库,则需要手动指定静态、动态库所在的路径。

注意:搜索库的命令需要写在生成可执行程序命令的前面,否则会出现各种未定义函数的报错。

下面两种方法二选一即可。

指定库目录
link_directories(${PROJECT_SOURCE_DIR}/lib)
指定库文件

如果需要搜索多个库,要分多行写。

# 参数分别是:库名称、库文件名称、库文件所在的目录(可以是绝对路径或相对路径)
find_library(gtest libgtest.a "C:\\c_extensions\\include\\googletest\\build\\lib")
find_library(gmock libgmock.a "C:\\c_extensions\\include\\googletest\\build\\lib")

还有一种写法

find_package(Qt5 COMPONENTS Core Gui Widgets REQUIRED)

链接静态库

# 链接静态库(可以用短的库名,也可以用库文件全名)
link_libraries(add sub)

链接动态库

静态库会在生成可执行程序的链接阶段被打包到可执行程序中,所以可执行程序启动,静态库就被加载到内存中了。

动态库在生成可执行程序的链接阶段不会被打包到可执行程序中,当可执行程序被启动并且调用了动态库中的函数的时候,动态库才会被加载到内存。

注意:在CMake中指定要链接动态库的时候,应该将命令写到生成可执行程序之后。

target_link_libraries既可以链接动态库,也可以链接静态库。

target_link_libraries(
    <target> 
    <PRIVATE|PUBLIC|INTERFACE> <item>... 
    [<PRIVATE|PUBLIC|INTERFACE> <item>...]...)
# 第一个参数是可执行程序的名称,之后的参数是库名。
# pthread是可执行程序要加载的动态库,这个库是系统提供的线程库,全名为libpthread.so。
target_link_libraries(
    calculate_test
    pthread
    gtest
    gtest_main
    gmock
    gmock_main
)

如果搜索库目录中同时包含静态库和动态库,不论target_link_libraries命令中用的是简写库名、静态库全名、动态库全名,都是链接静态库。

其它

日志

日志类型:STATUS、WARNING、AUTHOR_WARNING、FATAL_ERROR、SEND_ERROR

(无) :重要消息
STATUS :非重要消息
WARNING :CMake 警告, 会继续执行
AUTHOR_WARNING :CMake 警告 (dev), 会继续执行
SEND_ERROR :CMake 错误, 继续执行,但是会跳过生成的步骤
FATAL_ERROR :CMake 错误, 终止所有处理过程

# 输出一般日志信息
message(STATUS "source path: ${PROJECT_SOURCE_DIR}")
# 输出警告信息
message(WARNING "source path: ${PROJECT_SOURCE_DIR}")
# 输出错误信息
message(FATAL_ERROR "source path: ${PROJECT_SOURCE_DIR}")

拼接变量

有时候项目中的源文件并不一定都在同一个目录中,但是这些源文件最终却需要一起进行编译来生成最终的可执行文件或者库文件。如果我们通过file命令对各个目录下的源文件进行搜索,最后还需要做一个字符串拼接的操作,可以使用set命令也可以使用list命令。

# 把三个变量SRC1、SRC2、TEMP合到一起,赋值给SRC1,相当于SRC1被覆盖了。
set(SRC_1 ${SRC_1} ${SRC_2} ${TEMP})
# APPEND表示追加
list(APPEND SRC_1 ${SRC_1} ${SRC_2} ${TEMP})
# REMOVE_ITEM表示移除某个不需要的文件(之后的参数必须是路径,而不是文件名)
list(REMOVE_ITEM SRC_1 ${PROJECT_SOURCE_DIR}/main.cpp)

编译时使用宏

如果在源文件代码中定义了宏,以DEBUG宏举例:

#include <stdio.h>

int main()
{
    #ifdef DEBUG
        printf("我是一个程序猿, 我不会爬树...\n");
    #endif

    return 0;
}

在使用g++编译的时候,想要打印该宏下的代码,需要指定编译参数-D加宏的名称

g++ main.cpp -DDEBUG -o main

在使用CMake构建时,也可以在CMakeLists.txt中打印该宏下的代码

# 写在生成可执行程序的命令之前(参数-D和宏名称之间不要有空格)
add_definitions(-DDEBUG)

CMake内置的宏

功能
PROJECT_SOURCE_DIR 使用cmake命令后紧跟的目录,一般是工程的根目录
PROJECT_BINARY_DIR 执行cmake命令的目录
CMAKE_CURRENT_SOURCE_DIR 当前处理的CMakeLists.txt所在的路径
CMAKE_CURRENT_BINARY_DIR target 编译目录
EXECUTABLE_OUTPUT_PATH 重新定义目标二进制可执行文件的存放位置
LIBRARY_OUTPUT_PATH 重新定义目标链接库文件的存放位置
PROJECT_NAME 返回通过PROJECT指令定义的项目名称
CMAKE_BINARY_DIR 项目实际构建路径,假设在build目录进行的构建,那么得到的就是这个目录的路径

嵌套的CMakeLists

如果项目很大,或者项目中有很多的源码目录,在通过CMake管理项目的时候如果只使用一个CMakeLists.txt,那么这个文件相对会比较复杂,有一种化繁为简的方式就是给每个源码目录都添加一个CMakeLists.txt文件(头文件目录不需要),这样每个文件都不会太复杂,而且更灵活,更容易维护。

项目根目录下的就是根节点。

建立各个CMake文件之间的联系

add_subdirectory(source_dir [binary_dir] [EXCLUDE_FROM_ALL])

流程控制

在 CMake 的 CMakeLists.txt 中也可以进行流程控制,也就是说可以像写 shell 脚本那样进行条件判断和循环。

if(condition)
    ...
elseif(condition)
    ...
else()
    ...
endif()

condition条件有以下符号

foreach(item RANGE 10)
    message(STATUS "当前遍历的值为: ${item}" )
endforeach()

foreach(item RANGE 10 30 2)
    message(STATUS "当前遍历的值为: ${item}" )
endforeach()
# 创建 list
set(WORD a b c d)
set(NAME ace sabo luffy)
# 遍历 list
foreach(item IN LISTS WORD NAME)
    message(STATUS "当前遍历的值为: ${item}" )
endforeach()
# 创建一个列表 NAME
set(NAME luffy sanji zoro nami robin)
# 得到列表长度
list(LENGTH NAME LEN)
# 循环
while(${LEN} GREATER  0)
    message(STATUS "names = ${NAME}")
    # 弹出列表头部元素
    list(POP_FRONT NAME)
    # 更新列表长度
    list(LENGTH NAME LEN)
endwhile()

调试

设置调试目标:按Ctrl Shift P,输入CMAke: Set Debug Target,选择对应的目标。
F7构建生成目标。(要先有可执行程序,才能进行调试)

简单的调试

Ctrl F5打开调试。或者点击左下角CMake插件的调试按钮。

构建网上克隆的源码

CMake基本用法

构建步骤

CMake语法

CMakeLists.txt使用方法

调试