CMake基础

  • 跨平台的“编译器所需文件的生成器”
  • Make是什么
    • 相当于自动执行多个文件的编译与链接
    • 只需一个配置文件,就可根据配置文件自动生成命令然后执行
    • 同时应用了增量编译的思想:文件没有做出修改就不会重新编译
    • 只显示自动生成的编译命令而不运行:make --dry-run
  • Make有什么缺点?
    • 语法不够复杂,许多高级功能实现不了
    • GNU系软件,没有官方的对Windows的支持
    • 与GCC结合紧密,但与其他编译器结合可能会出现问题
    • 需要明确指明项目与项目间(或项目与子模块)的依赖关系
  • CMake解决了这些问题
    • 高级和复杂的语法,适应多种情况
    • 自动检测源文件和头文件的关联
    • 针对不同平台的不同编译器生成不同的项目文件/编译命令

相关概念

  • 需要将配置写在CMakeLists.txt里

编译模式

  • 源码混杂编译:In-Source,部分编译文件(如Makefile等)会生成在根目录下

    • cmake .
  • 源码分离编译(最常用):Out-of-Source

    • 常常是在源码目录里建build文件夹,做shell的工作目录(Working Directory)
    • 所有编译相关的文件均会放置在build子目录下
    • 删除build文件夹即可完全重新编译
    • cd build/ && cmake ..

头文件

  • 对于C++来说:
    • C++是强声明的语言,在没有声明时 是不知道一个名字是变量、函数还是类的
    • #include是预处理命令,本质上就是文件的插入,相当于把被include的文件的内容复制粘贴过来
    • 因此会遇到 重复定义问题、循环包含问题 和 include顺序不同导致编译结果不一样 等问题
    • 解决办法:
      • include顺序固定
      • 使用宏#pragma once
      • 使用宏#ifdef#ifndef
      • 使用模块(C++20)
    • 两种include的语法
      • 双引号 #include "":先找工作目录,再找系统目录
      • 尖括号 #include <>:直接找系统目录
      • 为什么有双引号include方式还要保留尖括号方式?:可以避免工作目录内同名的其他文件
    • <cxxx><xxx.h>没有必然联系,有些头文件中会检测当前语言是否为C++,是的话自动添加extern "C"{}

  • 分为静态库和动态库
  • 静态库链接时会直接插入到程序中,动态库则只会向程序中插入“插桩”代码(PLT表)
  • 动态链接库的查找位置
    • Windows:程序同文件夹 -> PATH内目录(用户PATH -> 系统PATH)
    • *nix:ELF程序的RPATH -> /usr/lib -> /lib

命令

  • cmake --version:当前CMake版本号
  • cmake -H<path1> -B<path2>:使用CMake生成文件。在path1中查找CMakeLists.txt,在path2子目录中存放所有编译文件
  • cmake -D<flag>:设置CMake编译选项
  • cmake -G<generator>:切换CMake生成针对哪个编译器的项目文件
  • [[TODO]]

函数

  • 函数名是不区分大小写的,但变量名/关键字是区分大小写的。关键字一般都是大写,变量名(除系统定义的之外)建议小写
  • project(xxx):指定项目名
    • 设置项目语言:project(xxx LANGUAGES CXX)CXX:即C++
1
project(hello_world LANGUAGES CXX)
  • cmake_minimum_required(VERSION xxx):指定最低CMake版本号
1
cmake_minimum_required(VERSON 3.22)
  • add_executable(AAA, BBB):把BBB文件编译成AAA,AAA是目标文件名
1
add_executable(hello.elf hello.cc)
  • set(AAA, BBB):把AAA变量(定义并)内容设置为BBB
  • file():查找文件,并将结果写入变量
  • target_include_directories():相当于cmake -I,设置头文件查找文件夹
1
2
3
4
5
# 下面的语句相当于 -I ./include
target_include_directories(target
PRIVATE
${PROJECT_SOURCE_DIR}/include
)

如何给项目添加多个文件?

将多个文件编译为一个目标时,可使用如下方法

  • 直接在add_executable里指定文件,add_executable(xxx a.cc b.cc c.h)
1
2
3
4
5
# 源码文件顺序固定
add_executable(${PROJECT_NAME}
main.cc
hello.cc
)
  • 使用变量,不推荐。set(YYY a.cc b.cc c.h) add_executable(XXX ${YYY})
1
2
3
4
5
6
7
8
9
10
11
# 可以定义单行的变量
set(SOURCES main.cc)
# 也可以定义多行的变量
set(SOURCES
main.cc
hello.cc
)
# 定义好的变量可以在接下来的语句中使用
# 比如添加编译目标
add_executable(${PROJECT_NAME} ${SOURCES})
# 注意,现代CMake中已不推荐使用这种变量的方式
  • 使用file()函数动态检测,即使用通配符的自动添加,不推荐:顺序不固定,且易丢失文件。
1
2
3
4
# GLOB:globbing表达式的结果形成列表
# SOURCES:结果写入SOURCES变量
file(GLOB SOURCES "*.cc")
add_executable(${PROJECT_NAME} ${SOURCES})

变量

  • 使用${xxx}来使用变量
1
${PROJECT_NAME}
  • PROJECT_NAME:由project()产生
  • CMAKE_SOURCE_DIR:源码目录
  • CMAKE_BINARY_DIR:编译目录
  • 子文件夹相关变量
    • CMAKE_CURRENT_SOURCE_DIR:当前源码目录
    • CMAKE_CURRENT_BINARY_DIR:当前编译目录
  • 子模块相关变量
    • PROJECT_SOURCE_DIR:当前项目源码目录
    • PROJECT_BINARY_DIR:当前项目编译目录

子模块

  • 可以将不同模块的源码放置在不同的子目录下
    • 头文件除外。头文件应当放置在同一个目录下,如include/
    • 头文件的目录也可以拥有子目录,如include/hello/
  • 主项目CMakeLists.txt
    • add_library():添加子模块,为库

      • STATIC:指定库的类型是静态链接库
    • target_include_directories():设置包含子模块的头文件文件夹

    • target_link_libraries():将库链接到 目标ELF文件 中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
add_library(hello_library STATIC
hello/hello.cc
)

# PUBLIC:library自己和引用该library的程序同时能够访问这些头文件
target_include_directories(hello_library
PUBLIC
${PROJECT_SOURCE_DIR}/include
)

# 此处的源码文件名不需要包括子模块中的文件名
add_executable(hello.elf
main.cc
)

target_link_libraries(hello.elf
PRIVATE
hello_library
)

头文件在子文件夹内怎么办?

  1. 有如下的子模块和文件
1
2
3
4
main.cc
CMakeLists.txt
mylib/hello.cc
mylib/hello.h

A. 可以不在CMakeLists.txt文件中写target_include_directories()

  • main.cc中可以按如下方式引入mylib/hello.h#include "mylib/hello.h"

B. 也可以将target_include_directories()写成

1
2
3
4
target_include_directories(hello_library
PUBLIC
${PROJECT_SOURCE_DIR}/mylib
)
  • main.cc中可以按如下方式引入mylib/hello.h#include "hello.h"或是#include <hello.h>
  1. 有如下的子模块和文件
1
2
3
4
main.cc
CMakeLists.txt
mylib/hello.cc
include/mylib/hello.h
  • 如下编写target_include_directories()
1
2
3
4
target_include_directories(hello_library
PUBLIC
${PROJECT_SOURCE_DIR}/include
)
  • main.cc中按如下方式引入mylib/hello.h#include "mylib/hello.h"
评论