CMake教程

CMake教程

CMakeLists.txt

1
2
3
cmake_minimum_required(VERSION 3.10)
project(hello)
add_executable(hello main.cpp factorial.cpp printhello.cpp)

CMake 2.x 一般方便删除多余文件,新建Build文件夹用来生成文件

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
mkdir build
cd build
cmake .. -DCMAKE_BUILD_TYPE=Release
make -j4
make install
./hello
cd ../
rm -rf build

cmake .. -DCMAKE_INSTALL_PREFIX=

CMake 3.x

1
2
3
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build --parallel 4
cmake --build build --target install

基础知识

简单项目

1
cmake_minimum_required(VERSION major.minor[.patch[.tweak]])
  1. 指定了项目所需的CMake的最低版本
  2. 强制设置将CMake行为匹配到对应版本
1
# cmake_minimum_required(VERSION 3.10)

指定版本 3.10

1
2
3
4
project(projectName
 [VERSION major[.minor[.patch[.tweak]]]]
 [LANGUAGES languageName ...]
)

projectName:项目名称

LANGUAGES:项目编程语言: C、CXX、JAVA、…多种语言空格分开;默认CCXX

3.0版本之前不支持LANGUAGES关键字,project(hello CXX)

1
# project(hello)

hello项目

可执行文件

1
2
3
4
add_executable(targetName [WIN32] [MACOSX_BUNDLE]
 [EXCLUDE_FROM_ALL]
 source1 [source2 ...]
)

为一组源文件创建一个可执行文件

1
# add_executable(test main.cpp)

为main.cpp构建test可执行文件

定义库

1
2
3
4
add_library(targetName [STATIC | SHARED | MODULE]
 [EXCLUDE_FROM_ALL]
 source1 [source2 ...]
)

STATIC | SHARED | MODULE:静态库,动态库,动态加载库

cmake -DBUILD_SHARED_LIBS=YES /path/to/source-D选项设置是否构建为动态库,否则为静态库。

set(BUILD_SHARED_LIBS YES):要在 add_library() 命令之前设置,(灵活性较差)

1
add_library(collector src1.cpp)

目标链接

考虑A库依赖于B库,因此将A链接到B

1
2
3
4
5
target_link_libraries(targetName
 <PRIVATE|PUBLIC|INTERFACE> item1 [item2 ...]
 [<PRIVATE|PUBLIC|INTERFACE> item3 [item4 ...]]
 ...
)

小总结 1

目标名称与项目名称无关,最好将项目名称和可执行文件名称分开

命名库的目标时,不要用lib作为名称的开头或结尾。lib会自动成为前缀

尽量避免直接将库指定 STATIC 或 SHARED

目标在调用 target_link_libraries() 时需要指定 PRIVATE 、 PUBLIC 和/或 INTERFACE

变量

基本变量

1
set(varName value... [PARENT_SCOPE])

将所有变量都作为字符串处理,给定多个值,这些值将用 分号连接在一起。可以使用转义字符表示引号\"

1
2
3
4
5
set(myVar a b c) # myVar = "a;b;c"
set(myVar a;b;c) # myVar = "a;b;c"
set(myVar "a b c") # myVar = "a b c"
set(myVar a b;c) # myVar = "a;b;c"
set(myVar a "b c") # myVar = "a;b c"

环境变量

1
set(ENV{PATH} "$ENV{PATH}:/opt/myDir")

只会影响当前的CMake实例,很少用到

缓存变量

1
set(varName value... CACHE type "docstring" [FORCE])

布尔缓存变量使用optio()代替set()

1
2
option(optVar heilpstring [initialValue])  # initialValue默认OFF
set(optVar initialValue CACHE BOOL hilpstring) # 等价option

调试变量和诊断

1
2
3
4
message([mode] msg1 [msg2]...)
# 打印记录信息
variable_watch(myVar [command])
# 监控变量,很少用

处理字符串

查找和替换操作、正则表达式匹配、大小写转换、删除空格和其他常见字符串操作

查找

1
string(FIND inputString subString outVar [REVERSE])
1
2
3
4
5
6
7
et(longStr abcdefabcdef)
set(shortBit def)
string(FIND ${longStr} ${shortBit} fwdIndex)
string(FIND ${longStr} ${shortBit} revIndex REVERSE)
message("fwdIndex = ${fwdIndex}, revIndex = ${revIndex}")
# 输出 fwdIndex = 3, revIndex = 9
# 代表子字符串被找到,第一次找到的时候索引为3;最后一次找到的索引为9

替换

1
string(REPLACE matchString replaceWith outVar input [input...])

将使用 replaceWith 替换输入字符串中每个 matchString ,并将结果存储在 outVar

正则表达式

1
2
3
string(REGEX MATCH regex outVar input [input...])
string(REGEX MATCHALL regex outVar input [input...])
string(REGEX REPLACE regex replaceWith outVar input [input...])

列表

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
list(LENGTH listVar outVar)  # 统计
list(GET listVar index [index...]) outVar # 检索
list(APPEND listVar item [item...])   # 追加
list(INSERT listVar index item [item...]) # 插入
list(FIND myList value outVar) # 查找
# 删除
list(REMOVE_ITEM myList value [value...]) # 从列表中删除一个或多个目标项。如果目标项不在列表中,也不会出错
list(REMOVE_AT myList index [index...]) # 指定一个或多个要删除的索引,超过索引报错
list(REMOVE_DUPLICATES myList) # 将确保列表不包含重复项。
# 排序  (按字母顺序)
list(REVERSE myList)  
list(SORT myList)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
set(myList a b c) # Creates the list "a;b;c"
list(LENGTH myList len)
message("length = ${len}")
list(GET myList 2 1 letters)
message("letters = ${letters}")
list(APPEND myList d e f)
message("myList (first) = ${myList}")
list(INSERT myList 2 X Y Z)
message("myList (second) = ${myList}")
list(FIND myList d index)
message("index = ${index}")

# 输出
length = 3
letters = c;b
myList (first) = a;b;c;d;e;f
myList (second) = a;b;X;Y;Z;c;d;e;f
index = 6

数学表达式

1
math(EXPR outVar mathExpr)

第一个参数必须使用关键字 EXPR ,而 mathExpr 定义要计算的表达式,结果将存储在 outVar 中

1
2
3
4
5
6
set(x 3)
set(y 7)
math(EXPR z "(${x}+${y}) / 2")
message("result = ${z}")
# 输出
result = 5

控制流

if()

1
2
3
4
5
6
7
if(expression1)
# commands ...
elseif(expression2)
# commands ...
else()
# commands ...
endif()

循环

1
2
3
4
5
6
7
foreach(loopVar IN [LISTS listVar1 ...] [ITEMS item1 ...])
# ...
endforeach()

while(condition)
# ...
endwhile()

子目录

add_subdirectory()

1
add_subdirectory(sourceDir [ binaryDir ] [ EXCLUDE_FROM_ALL ])

允许项目将另一个目录引入到构建中

CMAKE_SOURCE_DIR :源的最顶层目录(最顶层CMakeLists.txt所在位置)

CMAKE_BINARY_DIR:构建的最顶层目录 就是build

CMAKE_CURRENT_SOURCE_DIR:当前处理的CMakeLists.txt文件的目录

CMAKE_CURRENT_BINARY_DIR:当前处理的CMakeLists.txt文件对应的构建目录

include()

参考阅读

官方手册

Professional-CMake

cmake cookbook

CMake的链接选项:PRIVATE,INTERFACE,PUBLIC - 知乎 (zhihu.com)

An Introduction to Modern CMake

vscode 插件 : clangd

Make命令

python版本的makefile: SCons

make规则:“目标"是必需的,不可省略;“前置条件"和"命令"都是可选的,但是两者之中必须至少存在一个。

每条规则就明确两件事:构建目标的前置条件是什么,以及如何构建。

1
2
<目标> : <前置条件>
	<命令>
  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
    29
    30
    31
    32
    
    // functions.h
    #ifndef _FUNCTIONS_H_
    #define _FUNCTIONS_H_
    void printhello();
    int factorial(int n);
    #endif
    // factorial.cpp
    #include "functions.h"
    int factorial(int n){
     if(n == 1) return 1;
     else return n * factorial(n - 1);
    }
    //printhello.cpp
    #include <iostream>
    #include "functions.h"
    using namespace std;
    
    void printhello(){
     int i;
     cout << "Hello world!" << endl;
    }
    //main.cpp
    #include <iostream>
    #include "functions.h"
    using namespace std;
    
    int main(){
     printhello();
     cout << "This is main: " << endl;
     cout << "This factorial of 5 is: " << factorial(5) << endl;
     return 0;
    }

    正常编译

    1
    2
    
    g++ *.cpp -o hello
    ./hello
  2. makefile文件,管理工程,实现自动化编译(.o

     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
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    
    # VERDION 1
    hello: main.cpp printhello.cpp factorial.cpp
    	g++ -o hello main.cpp printhello.cpp factorial.cpp
    
    
    # VERDION 2
    CXX = g++
    TARGET = hello
    OBJ = main.o printhello.o factorial.o
    
    $(TARGET): $(OBJ)
    	$(CXX) -o $(TARGET) $(OBJ)
    main.o: main.cpp
    	$(CXX) -c main.cpp
    
    printhello.o: printhello.cpp
    	$(CXX) -c printhello.cpp
    
    factorial.o: factorial.cpp
    	$(CXX) -c factorial.cpp
    
    # VERDION 3
    CXX = g++
    TARGET = hello
    OBJ = main.o printhello.o factorial.o
    CXXFLAGS = -c -Wall 
    # Wall 打开警告信息
    $(TARGET): $(OBJ)
    	$(CXX) -o $@ $^
    
    %.o: %.cpp	
    	$(CXX) $(CXXFLAGS) $< -o $@
    
    .PHONY: clean
    clean:
    	rm -f *.o $(TARGET)  
    
    
    # VERDION 4
    CXX = g++
    TARGET = hello
    SRC = $(wildcard *.cpp)
    OBJ = $(patsubst %.cpp, %.o, $(SRC))
    
    CXXFLAGS = -c -Wall
    
    $(TARGET): $(OBJ)
    	$(CXX) -o $@ $^
    
    %.o: %.cpp	
    	$(CXX) $(CXXFLAGS) $< -o $@
    
    .PHONY: clean
    clean:
    	rm -f *.o $(TARGET)
    # del -f *.o $(TARGET).exe
    # windows下要想在Makefile中通过命令行删除中间文件,需要将rm替换为del
    
  3. 使用make命令执行makefile文件中的指令集

    1
    2
    
    make
    # make VERBOSE=1 查看make具体指令
  4. 在当前目录下执行main程序

    1
    2
    
    ./hello
    make clean
1
2
make -n  # 打印make查看具体指令,不运行
.PHONY # 强制每次执行

参考阅读

Makefile Tutorial By Example

GNU make

0%