Cmake

4/8/2024 CMake

该笔记是对CMake的初级入门,基于b站up主原子之音相关视频

# Cmake

# 第一章 认识Cmake

# 1.1 Cmake构建项目的流程

C++生成toolchain文件的流程

  1. 预处理(-E参数 宏替换等)
  2. 编译 gcc/msvc/clang (-S参数)
  3. 汇编(-C参数 linux生成.o文件、windows生成.obj文件)
  4. 连接(将多个二进制文件连接生成一个可执行的文件)

Cmake命令行执行的流程

1.编写CMakeLists.txt文件,下面是最基本的配置

  • cmake_minimum_required(VERSION 3.20) #最小版本
  • project(Hello) #项目名
  • add_executable(Hello hello.cpp) #由源文件生成一个可执行的程序

2.cmake -B build

  • 创建一个build并在此目录下生成makefile或其他文件

3.cmake --build build

  • 生成项目

# 1.2 Windows下使用Cmake

  • 默认MSVC(vs2022与vs2019)
  • 可以安装MinGW(gcc与clang)
  • cmake参数:cmake -G <generator-name> -T <toolset-spec> -A <platform-name><path-to-source>
  • 通过指定-G"MinGW Makefiles"来指定cmake使用gcc

使用cmake步骤

  1. 创建.cpp文件

  2. 创建CMakeLists.txt文件

  3. 在文件中分别输入

   cmake_minimum_required(VERSION 3.20)
   project(Hello)
   add_executable(Hello hello.cpp)
1
2
3

​ 4.在命令行中分别输入

   cmake -B `name`
   cmake build `name`
1
2

# 1.3 Linux下使用Cmake

源码安装

sudo apt-get install build-essential
sudo wget https://cmake.org/files/v3.28/cmake-3.28.0.tar.gz
tar -zxvf cmake-3.28.0.tar.gz
cd cmake-3.28.0
./configure
sudo make
sudo make install
cmake --version 检查是否安装成功
1
2
3
4
5
6
7
8

其余和在Window下操作相同,但是需要注意的是在Linux下操作没有自动保存,要手动保存才能运行程序

# 第二章 Cmake的基础语法

# 2.1Cmake语法 message

使用cmake -P *.cmake可以不通过CMakeLists.txt来运行CMake,便于语法的学习

在文件夹下创立.cmake文件(注意需要手动保存)

#在first.cmake文件下进行操作

message("hello")
message(hello)
#两种方式均可以执行

#加了""可以实现多行打印
message("hel
lo")

#多行打印的第二种方式
message([[hel
lo]])

#获取cmake中的信息
message(${CMAKE_VERSION})

#在终端中输入 cmake -P first.cmake
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 2.2Cmake语法set和list

  • CMake中的变量分为两种

    ​ CMake提供 ​ 自定义

  • CMake变量的命名区分大小写

  • CMake中的变量在存储时都是字符串

  • CMake获取变量:变量名

  • 变量的基础操作是set()unset(),但你也可以用list或是string操作变量

set

  • set(<variable> <value>... [PARENT SCOPE])
  • set可以给一个变量设置多个值
  • 变量内部存储时使用”;”分割,但显示时只进行连接处理
## set(变量 变量值)
set(Var1 aaa)

# 在设置set变量时可以存在空格,但是在调用的过程中需要加上转移字符`\`
set("My Var" aaa)
set([[My Var]] aaa)

message(${My\ Var})
# 在cmake中允许变量的多次定义。多次定义时,新的一次定义时对上一次定义的覆盖

# set设置多个值,有两种定义的方式
set(LISTVALUE a1 a2)
set(LISTVALUE a1;a2)
message(${LISTVALUE})

# 打印系统变量,如$PATH
message($ENV{PATH})
# 设置环境变量
set(ENV{CXX} "g++")
message($ENV{CXX})

#unset
unset(ENV{CXX})

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

list

  • list(APPEND <list> [<element>...]) 列表添加元素
  • list(REMOVE TEM <list> <value> [value...]) 列表删除元素
  • list(LENGTH <list> <output variable>) 获取列表元素个数
  • list(FIND <list> <value> <out-var>)在列表中查找元素返回索引
  • list(INSERT <list> <index> [<element>...])index位置插入
  • list(REVERSE <list>) 反转list
  • list(SORT <list> [...]) 排序list
# 两种方式来创建Var
set(LISTVALUE a1 a2 a3)
message(${LISTVALUE})

list(APPEND port p1 p2 p3)
message(${port})

# 获取长度
list(LENGTH LISTVALUE len)
message(${len})
# 结果:3

# 寻找位置
list(FIND LISTVALUE a2 index)
message(${index})
# 结果:1

# 删除变量
list(REMOVE_ITEM port p1)
message(${port})
# 结果:p2p3

# 增加变量
list(APPEND LISTVALUE a5)
message(${LSITVALUE})
list(INSERT LISTVALUE 3 a4)
message(${LSITVALUE})
# 结果:a1a2a3a5
# 结果:a1a2a3a4a5
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

# 2.3CMake流程控制

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

if(VARBOOL)
	message(TRUE)
else()
	message(FALSE)
endif()

if(NOT VARBOOL)
	message(TRUE)
else()
	message(FALSE)
endif()

# OR与"||"的作用相同
if(NOT VARBOOL OR VARBOOL)
	message(TRUE)
else()
	message(FALSE)
endif()

# AND与"&&"的作用相同
if(NOT VARBOOL AND VARBOOL)
	message(TRUE)
else()
	message(FALSE)
endif()

# 在cmake中LESS是"<"的意思
if(1 LESS 2)
	message("1 LESS 2")
endif()

# 在cmake中EQUAL是"=="的意思
# 在cmake中2和"2"是相等的
if(2 EQUAL "2")
	message("EQUAL")
endif()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

For

foreach(<loop_var> RANGE <max>)
<commands>
endforeach()

foreach(<loop_var> RANGE <min> <max>[<step>])

foreach(<loop_variable> IN [LISTS <lists>] [ITEMS <items>])
1
2
3
4
5
6
7

While

while(<condition>)
<commands>

endwhile()
1
2
3
4
foreach(VAR RANG 3)
	message(${VAR})
endforeach()
#结果:  0
#       1	
#	    2
#	    3		

set(MY_LIST 1 2 3)
foreach(VAR IN LISTS MY_LIST ITEMS 4 f)
	message(${VAR})
endforeach()
#结果:   0
#		1
#		2
#		3
#		4
#		f

# zip
set(Ll one two three four)
set(L2 1 2 3 4 5)

foreach(num IN ZIP_LISTS L1 L2)
	message("word = ${num_0}, num = ${num_1}")
endforeach()

#结果:
#word = one,num = 1
#word = two,num = 2
#word = three,num = 3
#word = four,num = 4
#word = ,num = 5
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

cmakecontinuebreak也能和其他语言一样正常使用。

# 2.4CMake语法 函数

定义函数的语法

function(<name>[<argument>...])
<commands>
endfunction()
1
2
3
cmake_minimum_required(VERSION 3.20.0)

# 在cmake函数中MyFunc为函数名,FirstArg是形参,在cmake中允许多个参数的传入。
function(MyFunc FirstArg)
	# CMAKE_CURRENT_FUNCTION是cmake给的一个内置变量,用来表示函数的名称
    message("MyFunc Name: ${CMAKE_CURRENT_FUNCTION}")
    # 打印传的第一个参数
    message("FirstArg ${FirstArg}")
    
    set(FirstArg "New value")
    message("FirstArg again:${FirstArg}")
    #ARGV+数字 为cmake内置变量,用来表示传参进来的第几个变量
    message("ARGV0 ${ARGV0}")
    message("ARGV1 ${ARGV1}")
    message("ARGV2 ${ARGV2}")
endfunction(MYFunc FirstArg)


set(FirstArg "first value")
MyFunc(${FirstArg} "value")
#可以看出在函数中对变量进行赋值对其本身并没有影响,及在函数中对变量的改变只在函数的作用域中生效
message("FirstArg ${FirstArg}")

# MyFunc Name: MyFunc
# FirstArg first value
# FirstArg again:New value
# ARGV0 first value
# ARGV1 value
# ARGV2 
# FirstArg first value
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

# 2.5CMake作用域

CMake 有两种作用域

  1. Function scope 函数作用域
  2. Directory scope 当从add subdirectory() 命令执行嵌套目录中的CMakeLists.txt列表文件时,注意父CMakeLists.txt其中的变量可以被子CMakeLists.txt使用
# 函数的作用域只在函数体内,函数参数传值是值传递。
cmake_minimum_required(VERSION 3.20.0)
project(scope)

function(OutFunc)
    message("-> Out: ${Var}")
    set(Var 2)
    InFunc()
    message("<- Out:${Var}")
endfunction()

function(InFunc)
    message("-> In:${Var}")
    set(Var 3)
    message("<- In:${Var}")
endfunction()

set(Var 1)
message("-> Global:${Var}")
OutFunc()
message("<- Global:${Var}")

# -> Global:1
# -> Out: 1
# -> In:2
# <- In:3
# <- Out:2
# <- Global:1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

# 2.6CMake宏

CMake中的宏

macro(<name>[<argument>...])
<commands>
endmacro()
1
2
3

注意:尽量不要写宏,只要会读就好

注意:尽量不要写宏,只要会读就好

macro(Test myVar)
    set(myVar "new value") # 创建了一个新的myVar变量
    message("argument: ${myVar}")
endmacro()

set(myVar "Fist value")
message("myVar: ${myVar}")
# 宏相当于把宏那部分的代码黏贴到这里
Test("value")
message("myVar: ${myVar}")

# myVar: Fist value
# argument: value
# myVar: new value
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 第三章 CMake构建项目的四张方式

# 3.1直接写入源码的方式

  • add executable中直接写入相对路径
  • 在源码中引入头文件时需要写相对路径
//dog.h
#pragma once

#include <string>

class Dog{
public:
    std::string barking();
};

//dog.cpp
#include "dog.h"

std::string Dog::barking()
{
    return "YH wang wang";
}

//main.cpp
#include <iostream>
#include "animal/dog.h"//直接写入的源码的方式要求在头文件引入时写上相对路径

int main(int argc, char const *argv[])
{
    Dog yh;
    std::cout << yh.barking() << std::endl;
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 在CMakeLists.txt文件中
cmake_minimum_required(VERSION 3.20.0)
project(Animal CXX) # CXX就是cpp
add_executable(Animal main.cpp animal/dog.cpp)
1
2
3
4

# 3.2调用子目录中的cmake脚本

  • include方法可以引入子目录中的cmake后缀的配置文件
  • 将配置加入add executable
//cat.h
#pragma once

#include <string>

class Cat{
public:
    std::string barking();
};

//cat.cpp
#include "cat.h"

std::string Cat::barking()
{
    return "WKM miao miao";
}

//main.cpp
#include <iostream>
#include "animal/dog.h"
#include "animal/cat.h"

int main(int argc, char const *argv[])
{
    Dog yh;
    Cat wkh;
    std::cout << yh.barking() << std::endl;
    std::cout << wkh.barking() << std::endl;
    return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# animal.cmake
set(animal_scources animal/dog.cpp animal/cat.cpp) #定义了一个名为animal_scources的变量


# CMakeLists.txt
cmake_minimum_required(VERSION 3.20.0)
project(Animal CXX)
include(animal/animal.cmake) # 注意在使用的过程要先进行声明
add_executable(Animal main.cpp ${animal_scources})
1
2
3
4
5
6
7
8
9

# 3.3CmakeLists嵌套

  • target_include_directories 头文件目录的声明

  • target_link_libraries 连接库文件

  • add_subdirectory 添加子目录

  • add_library生成库文件 默认 STATIC library

# animal/CMakeLists.txt
add_library(AnimalLib cat.cpp dog.cpp)

#CMakeLists.txt
cmake_minimum_required(VERSION 3.20.0)
project(Animal CXX)

# 这个命令将在animal目录下查找并执行Animal库的CMakeLists.txt文件
add_subdirectory(animal)

# 定义了一个名为Animal的可执行文件,并指定了它的源文件为main.cpp
add_executable(Animal main.cpp)

# 将Animal可执行文件与AnimalLib库链接,使得Animal可执行文件可以使用AnimalLib库中定义的功能。
target_link_libraries(Animal PUBLIC AnimalLib)

# 将Animal可执行文件的包含目录设置为"${PROJECT_SOURCE_DIR}/animal",这样Animal可执行文件就可以包含Animal库的头文件。
target_include_directories(Animal PUBLIC "${PROJECT_SOURCE_DIR}/animal")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 3.4Object Library

  • add_library OBJECTObject Library 是一个特殊的库类型,它将目标文件编译成一个库,但不会生成最终的链接文件。这意味着你可以在后续的 add libraryadd executable() 命令中,将Object Library 作为源文件进行链接,从而生成最终的可执行文件或库文件。
  • target_include_directories移入到子CMakeLists
# animal/CMakeLists.txt
add_library(AnimalLib OBJECT cat.cpp dog.cpp)
target_include_directories(AnimalLib PUBLIC .)

#CMakeLists.txt
cmake_minimum_required(VERSION 3.20.0)
project(Animal CXX)

# 这个命令将在animal目录下查找并执行Animal库的CMakeLists.txt文件
add_subdirectory(animal)

# 定义了一个名为Animal的可执行文件,并指定了它的源文件为main.cpp
add_executable(Animal main.cpp)

# 将Animal可执行文件与AnimalLib库链接,使得Animal可执行文件可以使用AnimalLib库中定义的功能。
target_link_libraries(Animal PUBLIC AnimalLib)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 第四章 CMake和库

# 4.1CMake生成静态库和动态库

  • 静态库 在连接阶段,会将汇编生成的目标文件.o与引用到的库一起链接打包到可执行文件中。因此对应的链接方式称为静态连接。对函数库的连接是在编译时完成的!

  • 动态库 动态库不是在编译时被连接到目标代码中,而是运行时是才被载入静态库对空间的浪费是巨大的!

  • 动态库的命名 lib<name>.so/dll

  • 静态库的命名 lib<name>.a/lib

命令

  • file()常用于搜索源文件
  • add library(animal STATIC SSRC))生成静态库
  • add library(animal SHARED SSRC)生成动态库
  • SKLIBRARY_OUTPUT PATHI导出目录
cmake_minimum_required(VERSION 3.20.0)
project(Animal CXX)

file(GLOB SRC ${PROJECT_SOURCE_DIR}/src/*.cpp)
include_directories(${PROJECT_SOURCE_DIR}/include)

# 静态
# set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/a)
# add_library(animal STATIC ${SRC})

# 动态
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/so)
add_library(animal SHARED ${SRC})
1
2
3
4
5
6
7
8
9
10
11
12
13

# 4.2CMake链接动态和静态库

  • 静态库调用流程 1.引入头文件 2连接静态库 3.生成可执行二进制文件
  • 动态库调用流程 1.引入头文件 2.声明库目录 3.生成可执行二进制文件 4.连接动态库
cmake_minimum_required(VERSION 3.20.0)
project(Animal CXX)

# 静态
include_directories(${PROJECT_SOURCE_DIR}/include)
link_directories(${PROJECT_SOURCE_DIR}/a)
link_libraries(animal)
add_executable(app main.cpp)

#动态
include_directories(${PROJECT_SOURCE_DIR}/include)
link_directories(${PROJECT_SOURCE_DIR}/so)
add_executable(app main.cpp)
target_link_libraries(app PUBLIC animal)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 第五章 CMake与源文件交互

​ 需要先创建一个交互文件.h.in文件,在交互文件中可以设定变量、宏等

#define CMAKE_CXX_STANDARD ${CMAKE_CXX_STANDARD}
1

​ 在CMakeLists.txt中需要声明变量

cmake_minimum_required(VERSION 3.20.0)
project(Animal CXX)

# 设置c++标准
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED T)

configure_file(config.h.in config.h)

add_subdirectory(animal)

add_executable(Animal main.cpp)

target_link_libraries(Animal PUBLIC AnimalLib)

target_include_directories(Animal PUBLIC "${PROJECT_BINARY_DIR}" "${PROJECT_SOURCE_DIR}/animal")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

​ 在main文件中引用

#include <iostream>
#include "dog.h"
#include "cat.h"
#include "config.h"
int main(int argc, char const *argv[])
{
    Dog yh;
    Cat wkh;
    std::cout << yh.barking() << std::endl;
    std::cout << wkh.barking() << std::endl;
    std::cout << CMAKE_CXX_STANDARD << std::endl;
    return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 第六章 CMake条件编译

通过不同传入参数编译不同的文件

  1. option定义变量
  2. 在子CMakeLists.txt中根据变量ON还是OFF来修改SRC (源文件)以及target_compile_definitions
  3. 修改源文件根据变量选择代码
  4. 执行命令时-D<变量>=ON/OFF来进行条件编译

说明 PUBLIC INTERFACE 和PRIVATE的区别

  • PUBLIC 本目标需要用,依赖这个目标的其他目标也需要
  • INTERFACE 本目标不需要,依赖本目标的其他目标需要
  • PRIVATE 本目标需要,依赖这个目标的其他目标不需要
//cattwo.h
#include "cattwo.h"

std::string cattwo::two()
{
    return "two two mimi";
}

//cattwo.cpp
#include <string>

namespace cattwo
{
    std::string two();
}

//cat.cpp
#include "cat.h"
#ifdef USE_CATTWO
    #include "cattwo.h"
#endif
std::string Cat::barking()
{
#ifdef USE_CATTWO
    return cattwo::two();
    #else
    return "cat mimi";
#endif
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# animal/CMakeLists
option(USE_CATTWO "Use cat two" ON)

if(USE_CATTWO)
    set(SRC cat.cpp dog.cpp cattwo.cpp)
else()
    set(SRC cat.cpp dog.cpp)
endif()


add_library(AnimalLib ${SRC})

if(USE_CATTWO)
    target_compile_definitions(AnimalLib PRIVATE "USE_CATTWO")
endif()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15