CMake学习笔记

CMake学习笔记

开始

介绍

  • CMake是一个高级编译配置工具,用于团队写作或多语言开发项目的编译工具,所有操作都通过编写编译CMakeLists.txt来完成操作.
  • CMake可以进行编译构建处理大型的C/C++项目,本质是使用编译cmake生成makefile
  • 官网:CMake

安装:

  • Windows:通过官方网站,选择对应系统以及版本进行下载,下载地址:https://cmake.org/download/

  • Linux:Linux系统下可以选择使用包管理器进行下载,也可以选择去官方网站进行下载

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    # ubuntu/debian
    sudo apt-get install cmake

    #centos
    sudo yum install cmake

    # fedora
    sudo dnf install cmake

    #arch linux
    sudo pacman -s cmake
  • Mac os:Macos系统也可以通过官方网站进行下载,或者使用Homebrew进行安装

    1
    brew install cmake

小试牛刀

  • 新建一个文件夹

  • 创建一个main.cpp文件

    1
    2
    3
    4
    5
    6
    7
    #include <iostream>

    int main (int argc, char *argv[]) {
    std::cout<<"Hello world"<<std::endl;

    return 0;
    }
  • 平常我们可能会使用gdb,makefile来进行编译构建cpp文件生成可执行文件,这次我们学会使用cmake来进行编译生成makefile,然后进一步生成可执行文件

  • 创建一个CMakeLists.txt文件,并编辑内容

    1
    2
    3
    4
    5
    6
    7
    cmake_minimum_required(VERSION 3.5)
    project(main)

    set(list main.cpp)


    add_executable(main ${list})
  • 保存退出,在项目目录下输入以下命令

    1
    2
    3
    4
    5
    6
    7
    8
    # 对当前项目目录的CMakeLists.txt进行编译构建
    cmake .

    # 对生成的makefile进行make,生成可执行文件
    make

    # 运行可执行文件
    ./main

CMake编译项目流程

  1. 通过编写CMakeLists.txt文件,来进行编译构建项目
  2. 通过cmake命令生成对应的Makefile
  3. 通过make命令生成项目的可执行文件

CMake语法

语法基本原则

语法基本原则

  1. 变量使用${}方式进行取值,在**IF控制语句中不需要使用${}**,而是直接使用变量名
  2. 指令(参数1 参数2...),参数使用小括号括起,参数之间使用空格或分号分开
  3. 使用#进行行注释
  4. 使用#[[]]形式进行块注释
  5. 指令是大小写无关的(指令写大小写都可),参数和变量是大小写相关的

注意事项

  • set(main main.cpp)可以写成set(main “main.cpp”),如果源文件名中含有空格,就必须要加双引号
  • add_executable(hello main)后缀可以不写,它会自动找寻.c和.cpp文件,但是最好不要这样写,可能会出现文件冲突

CMake命令

cmake [options] [dir]执行构建cmake工程命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# cmake默认是在当前目录下执行,可以指定目录执行
cmake # 使用默认参数执行构建,默认在当前目录下构建
cmake .. # 在上一层目录执行构建

# 指定生成器为Ninja,在上一层目录进行构建cmake工程
cmake -G Ninja ..

# 构建带调试信息的程序或库
cmake -D CMAKE_BUILD_TYPE=Debug ...


# 根据默认构建系统生成程序
cmake --build

# 查看cmake版本
cmake -version

# 查看帮助
cmake --help

**指令参数[options]**:

  1. -B:用于指定构建目录(指定build目录)
  2. -D:用于定义CMake变量
  3. -E:调用CMake内置命令的参数
  4. -G:用于指定生成器
  5. -S:用于指定源代码目录

常用变量

  • CMAKE_SOURCE_DIR:最顶层CMakeLists.txt所在目录
  • CMAKE_CURRENT_SOURCE_DIR:当前CMakeLists.txt所在路径
  • PROJECT_SOURCE_DIR:工程根目录
  • CMAKE_ARCHIVE_OUTPUT_DIRECTORY:静态库的输出目录
  • CMAKE_LIBRARY_OUTPUT_DIRECTORY:动态库的输出目录
  • CMAKE_RUNTIME_OUTPUT_DIRECTORY:可执行文件的输出目录

CMake判断语句

CMake判断语句

1
2
3
4
5
6
7
if(<condition>)
<commands>
elseif(<condition>)
<commands>
else()
<commands>
endif()

比较操作符

  • EQUAL:检查是否相等
  • LESS:检查是否小于
  • GREATER:检查是否大于
  • LESS_EQUAL:检查是否小于或等于
  • GREATER_EQUAL:检查是否大于或等于
  • STREQUAL:检查字符串是否相等
  • STRLESS:检查字符串是否小于(按字典顺序)
  • STRGREATER:检查字符串是否大于(按字典顺序)
  • EXISTS:检查文件或目录是否存在
  • IS_DIRECTORY:检查是否是目录
  • IS_SYMLINK:检查是否是符号链接
  • IS_ABSOLUTE:检查路径是否是绝对路径

逻辑操作符:

  • AND:逻辑与
  • OR:逻辑或
  • NOT:逻辑非

CMake循环语句

CMake循环语句

  1. foreach:用于遍历列表中的每个元素或特定范围的值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    # 遍历s_value-e_value
    foreach(i s_value value1 ... e_value)
    <commands>
    endforeach()

    # 遍历列表遍历
    foreach(i IN LISTS <list_name>)
    <commands>
    endforeach()


    # 示例
    foreach(i 1 2 3 4)
    message(STATUS "${i}")
    endforeach()

    set(test 1 2 3 4)
    foreach(i IN LISTS test)
    message(STATUS "${i}")
    endforeach()
  2. while:用于在给定条件为真时重复执行命令

    1
    2
    3
    while(<condition>)
    <commands>
    endwhile()

CMake函数

CMake中定义函数

1
2
3
4
# 定义函数,<name>为函数名,[<arg>...]为函数参数(可选添加)
function(<name> [<arg> ...])
<commands>
endfunction()

CMake提供常用函数

PROJECT()

  • 用来指定工程的名字、支持的语言以及版本号,默认支持所有语言
  • 并且隐式定义了<projectname>_BINARY_DIR和<projectname>_SOURCE_DIR两个CMake变量,两个变量都指向当前的工作目录
  • 但是工程名变化,隐式定义的两个变量也会随之更改,我们可以直接使用两个预定义的PROJECT_BINARY_DIR和PROJECT_SOURCE_DIR进行代替使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# PROJECT/project
project(<project_name> [<language>...])

#指定了工程的名字为main,并且支持所有语言
PROJECT(MAIN)/project(MAIN)

#指定了工程的名字为main,支持所有语言,版本号为1.0
PROJECT(MAIN VERSION 1.0)

#指定了工程的名字为main,并且支持C++语言
PROJECT(MAIN CXX)/project(MAIN CXX)

#指定了工程名字为main,并且支持C、C++语言
PROJECT(MAIN C CXX)/project(MAIN C CXX)

# 隐式定义了MAIN_BINARY_DIR和MAIN_SOURCE_DIR两个CMake变量

CMAKE_MINIMUM_REQUIRED()设置所需的最低CMake版本

1
2
3
4
5
# CMAKE_MINIMUM_REQUIRED/cmake_minimum_required
cmake_minimum_required(VERSION <version_num>);

# 设置cmake最低版本为3.5
cmake_minimum_required(VERSION 3.5)

SET()用来设置变量的值

1
2
3
4
5
6
7
8
# SET/set
set(<variable> <value>...)

# 将list变量值设置为main.cpp
set(list main.cpp)

# 多值用空格分隔
set(list main.cpp main1.cpp ...)

MESSAGE()向终端输出用户自定义信息

消息的一些类型

  1. SEND_ERROR:产生错误,继续处理,跳过生成过程
  2. STATUS:输出用户自定义信息并添加前缀–
  3. FATAL_ERROR:立即终止所有cmake过程
  4. WARNING:发出警告,继续处理
1
2
3
4
5
# MESSAGE/message
message([<mode>] "message text" ...)

# 以指定STATUS形式向终端输出消息
message(STATUS,"hello")

ADD_COMPILE_OPTIONS()添加编译参数

1
2
3
# ADD_COMPILE_OPTIONS/add_compile_options
# value为参数
add_compile_options(<value> [<value> ...])

ADD_SUBDIRECTORY()可以将指定的存放源文件的子目录加入工程,并在该子目录中执行CMakeLists.txt,并指定编译输出路径

1
2
3
4
5
6
7
8
# ADD_SUBDIRECTORY/add_subdirectory
add_subdirectory(<source_dir> [binary_dir] [EXCLUDE_FROM_ALL])

# [binary_dir]为可选参数,用于指定输出路径,如果不进行指定,默认将存放在build/src目录下
add_subdirectory(src bin) # 将src编译输出到bin目录中

# [EXCLUDE_FROM_ALL]为可选参数,是用于将source_dir目录不参加工程的编译
add_subdirectory(example EXCLUDE_FROM_ALL) # example目录不参加工程构建编译

ADD_LIBRARY()创建一个库(静态库或动态库)

type

  • STATIC:静态库
  • SHARED:动态库
1
2
3
# ADD_LIBRARY/add_library
# <target>为创建的库名,<type>为生成的库类型,<source_files>...为创建该库的源文件
add_library(<target> <type> <source_files>...)

ADD_EXECUTABLE()生成可执行文件

1
2
3
4
5
6
7
8
# ADD_EXECUTABLE/add_executable
add_executable(<target> <source_files>...)

# 可执行文件名为main,其用于编译构建的源文件为main.cpp
add_executable(main main.cpp)

# 可以使用${PROJECT_NAME}自动解析工程名直接代替可执行文件名
add_executable(${PROJECT_NAME} main.cpp)

INSTALL()用于指定在安装时运行的规则,可以用来安装包括目标二进制、动态库、静态库以及文件、目录、脚本等到指定目录,用于执行make install命令下安装对应文件

type

  • TARGETS:目标二进制文件
  • FILES:文件
  • PROGAMS:非目标文件的可执行程序安装(比如脚本之类)
  • DIRECTORY:文件夹
  • SCRIPT:脚本文件
  • CODE:代码

DESTINATION

  • 后面跟着绝对路径
  • 后面跟着相对路径,相对路径默认会给你的路径添加CMAKE_INSTALL_PREFIX作为前缀(默认为/usr/local/),可以自己更改CMAKE_INSTALL_PREFIX的值
1
2
3
# INSTALL/install
# <type>为安装文件的类型,<target>...为要安装的文件,[DESTINATION ...]可选是用来指定各安装文件指定安装路径
install(<type> <target>...[DESTINATION ...])

AUX_SOURCE_DIRECTORY()用于查找指定目录下的源文件

1
2
3
# AUX_SOURCE_DIRECTORY/aux_source_directory
# dir为要查找的目录,value用于存储所有查找到的源文件
aux_source_directory(<dir> <value>)

LINK_DIRECTORIES()在指定路径下寻找库文件

1
2
3
# LINK_DIRECTORIES/link_directories
# path为要找寻的库文件的路径
link_directories(<path>)

FIND_LIBRARY()查找库文件路径

1
2
3
4
5
6
7
8
9
10
# FIND_LIBRARY/find_library

# <variable>为存储找到的库文件路径的变量名,如果找到库,该变量将被设置为库文件的全路径;否则,变量将保留未定义状态或被设置为空字符串
# NAMES后面的<name>为指定要查找的库文件名。通常包括库的基础名称(如 calc)和可能的后缀(如 .a、.so、.dll 等)。可以指定多个备选名称
# HINTS、PATHS: 提供查找库文件的额外路径提示或直接路径。HINTS 的优先级高于 PATHS。这些路径将被添加到 CMake 的默认查找路径中。
find_library(<variable>
NAMES <name1> [<name2> ...]
HINTS <hint1> [<hint2> ...]
PATHS <path1> [<path2> ...]
...])

LINK_LIBRARIES()链接静态库

1
2
3
# LINK_LIBRARIES/link_libraries
# <static lib>为静态库名,可以是全名,也可以是去除lib和文件类型后缀的名字
link_libraries(<statci lib> [<static lib> ...])

TARGET_LINK_LIBRARIES()链接动态库

1
2
3
# TARGET_LINK_LIBRARIES/target_link_libraries
# target为要链接的可执行文件名,<shared lib>为动态库名,可以是全名,也可以是去除lib和文件类型后缀的名字
target_link_libraries(<target> <shared lib> [<shared lib> ...])

set_target_properties/SET_TARGET_PROPERTIES()设置目标属性,可以用来设置输出名称,对于动态库可以指定动态库版本和API版本

1
2
3
4
5
# SET_TARGET_PROPERTIES/set_target_properties
# target为目标,<prop>为属性,<value>为设置的值
set_target_properties(<target> [<target> ...]
PROPERTIES <prop1> <value1>
[<prop2> <value2> ...])

静态/动态库

构建前准备

  1. 创建工程目录
  2. 在根目录下创建一个CMakeLists.txt,并创建子目录src、include、lib和build
  3. 将源文件放在在src目录下
  4. 将源文件引用的头文件放入include目录下
1
2
3
4
5
6
7
8
9
10
# 目录结构
> tree
.
├── build
├── CMakeLists.txt
├── include
│   └── main.h
├── lib
└── src
└── main.cpp

构建静态/动态库

  • 使用add_library函数创建库

  • 静态库

    1
    2
    # 库名就是你要生成的静态库的名字,<source>...为源文件列表
    add_library(<库名> STATIC <source>...)
  • 动态库

    1
    2
    # 库名就是你要生成的动态库的名字,在linux中最终名字会为lib库名.so,<source>...为源文件列表
    add_library(<库名> SHARED <source>...)

编写CMakeLists

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 设定cmake最低版本
cmake_minimum_required(VERSION 3.2)

project(MAIN)

# 在指定目录下搜索所有源文件并将其放入变量SRC_LIST中
aux_source_directory(${PROJECT_SOURCE_DIR}/src SRC_LIST)

# 包含的头文件目录
include_directories(./include)

# 将构建好的库输出到根目录下的lib目录中
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)

# 创建动态库
add_library(mymain SHARED ${SRC_LIST})

# 创建静态库
add_library(mymain_static STATIC ${SRC_LIST})


# [安装构建好的动态库到指定路径],可选
install(TARGETS mymain DESTINATION <path>)

构建

  1. 将以上步骤做好后

  2. 进入到build目录进行输入

    1
    cmake ..
  3. 然后在进行make

  4. 在lib目录查看动态库是否生成成功

构建同名动态库和静态库

1
2
3
4
5
6
7
8
9
10
11
12
add_library(mymain_static STATIC ${SRC_LIST})

# 对mymain_static重命名为mymain
set_target_properties(mymain_static PROPERTIES OUTPUT_NAME "mymain")
# 清理掉其他使用这个名字的库
set_target_properties(mymain_static PROPERTIES CLEAN_DIRECT_OUTPUT 1)


add_library(mymain SHARED ${SRC_LIST})

set_target_properties(mymain PROPERTIES OUTPUT_NAME "mymain")
set_target_properties(mymain PROPERTIES CLEAN_DIRECT_OUTPUT 1)

设置动态库版本号

1
2
# VERSION为动态库版本,SOVERSION为API版本
set_target_properties(<库名> VERSION <ver_num> SOVERSION <sover_num>)

链接静态/动态库

  • 在项目中当我们需要依赖于第三方的静态或动态库时,就需要在CMakeLists.txt中将依赖库链接到项目中

  • 静态库

    1
    2
    # 一般需要使用link_directories/find_library指定库文件所在目录或者查找库文件路径
    link_libraries(<库名/路径>)
  • 动态库

    1
    2
    # 一般需要使用link_directories/find_library指定库文件所在目录或者查找库文件路径
    target_link_libraries(<target> <库名/路径> [<库名/路径>...]])

工程创建

构建方式

概念

  • CMake的构建方式分为内部构建外部构建
  • 内部构建就在工程目录下构建编译生成可执行文件,会产生很多临时文件,不方便清理,我们上面构建工程的方式就是内部构建
  • 外部构建会把项目构建编译生成的临时文件放在build目录中,不会对源文件有任何影响,**(建议使用)**

外部构建

  1. 在工程目录下创建build目录

  2. 进入到build目录

  3. 执行以下命令构建

    1
    2
    # ..表示上级目录也就是工程目录
    cmake ..

注意:

通过外部构建方式构建项目,其隐式定义的<projectname>_BINARY_DIR是指向build目录的,<projectname>_SOURCE_DIR是指向源工程目录

工程规范

概念

  • 为了让我们的项目工程更规范,让其看起来更像一个工程
  • 我们可以选择按以下后面的工程规范进行创建CMake工程

工程规范

  • bin目录:存放生成的可执行文件
  • lib目录:存放生成的中间库文件(静态库输出目录)
  • include目录:存放头文件
  • src目录:存放源文件
  • build目录:存放项目编译过程中产生的临时的中间文件
  • thirdparty目录:存放依赖的第三方库的文件
  • doc目录:用来存放项目的文档
  • example目录:存放示例文件
  • README.MD:项目自述文件

创建工程

工程创建

  1. 在项目目录下创建CMakeLists.txt,还有创建src、include、doc、lib和bin目录

  2. 将源代码放入src目录中,引用的头文件放入include目录下,将项目相关文档放入doc中

  3. 在src中可以新建多个目录放置源文件,然后每个目录创建CMakeLists.txt

  4. 示例目录结构如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    > tree
    .
    ├── bin
    ├── CMakeLists.txt
    ├── doc
    ├── include
    ├── lib
    └── src
    ├── hmod
    │   └── CMakeLists.txt
    └── yule
    └── CMakeLists.txt

编写顶层CMakeLists

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
cmake_minimum_required(VERSION 3.2)

project(YULE LANGUAGES C CXX)

# 设置C++标准
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED on)
# 设置顶层可执行文件、库文件输出目录
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin/)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/lib/)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/lib/)

include_directories(include)
# 添加子目录
add_subdirectory(src/hmod)
add_subdirectory(src/yule)

次级CMakeLists

1
2
3
4
5
6
7
8
9
10
11
12
13
# 搜索源文件
aux_source_directory(. SRC_LIST)

# 例子,引用第三方库
find_library(NET_LIBRARY netfilter_queue)
if(NOT NET_LIBRARY)
message(FATAL_ERROR "netfilter_queue library not found")
endif()


add_executable(yule ${SRC_LIST})

target_link_libraries(yule ${NET_LIBRARY})

编译构建

1
2
3
4
# 采用外部构建方法
mkdir build && cd build
cmake ..
make

CMake学习笔记
https://moonfordream.github.io/posts/CMake学习笔记/
作者
Moon
发布于
2024年9月30日
许可协议