最简单的情况

main.cpp

1
2
3
4
5
6
7
8
#include <cstdio>

void hello();

int main() {
hello();
return 0;
}

hellp.cpp

1
2
3
4
5
#include <cstdio>

void hello() {
printf("Hello, world\n");
}

CMakeLists.txt

1
2
3
4
cmake_minimum_required(VERSION 3.12)
project(hellocmake LANGUAGES CXX)

add_executable(a.out main.cpp hello.cpp)

hello.cppmain.cpp 生成一个可执行文件

静态库

静态库可以当成多个 .o 文件的打包,相当于直接把代码插入到生成的可执行文件中,会导致生成的可执行文件体积变大。

而动态库则只在生成的可执行文件中生成“插桩”函数,当可执行文件被加载时会读取指定目录中的 .dll 文件(或 .so),加载到内存中空闲的位置,并且替换相应的“插桩”指向的地址为加载后的地址,这个过程称为“重定向”。

CMakeLists.txt

1
2
3
4
5
6
cmake_minimum_required(VERSION 3.12)
project(hellocmake LANGUAGES CXX)

add_library(hellolib STATIC hello.cpp)
add_executable(a.out main.cpp)
target_link_libraries(a.out PUBLIC hellolib)

通过 add_library 生成一个静态库,add_executable 生成可执行文件,target_link 将静态库链接到可执行文件

动态库

CMakeLists.txt

1
2
3
4
5
6
cmake_minimum_required(VERSION 3.12)
project(hellocmake LANGUAGES CXX)

add_library(hellolib SHARED hello.cpp)
add_executable(a.out main.cpp)
target_link_libraries(a.out PUBLIC hellolib)

此处会生成一个动态库 libhellolib.so,编译后,通过命令 ldd a.out 可见:

1
2
3
4
linux-vdso.so.1 (0x00007ffe8b96d000)
libhellolib.so => /home/lushuangning/CodeHub/CMake/course/01/06/build/libhellolib.so (0x00007fed7ba8e000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fed7b800000)
/lib64/ld-linux-x86-64.so.2 (0x00007fed7ba9a000)

头文件递归引用

容易出现“菱形”引用问题,解决方法有两种,一种是在每个头文件的开始都加入 #pragma once,另一种用宏定义的方式。

CMake中的子模块

1
2
3
4
5
6
7
── CMakeLists.txt
├── hellolib
│   ├── CMakeLists.txt
│   ├── hello.cpp
│   └── hello.h
├── main.cpp
└── run.sh

hellolib/CMakeLists.txt

1
add_library(hellolib STATIC hello.cpp)

根目录下 CMakeLists.txt

1
2
3
4
5
6
7
cmake_minimum_required(VERSION 3.12)
project(hellocmake LANGUAGES CXX)

add_subdirectory(hellolib)

add_executable(a.out main.cpp)
target_link_libraries(a.out PUBLIC hellolib)

要在根目录使用,可以用 CMakeadd_subdirectory 添加子目录,子目录也包含一个 CMakeLists.txt,其中定义的库在 add_subdirectory 之后就可以在外面使用

因为 hello.h 被移到了 hellolib 子文件夹里,因此 main.cpp 里也要改成 #include "hellolib/hello.h"的形式。如果要避免修改代码,可以通过 target_include_directories指定 a.out 的头文件搜索目录。

这样甚至可以用 #include <hello.h> 来引用这个头文件。

hellolib/CMakeLists.txt

1
2
add_library(hellolib STATIC hello.cpp)
target_include_directories(hellolib PUBLIC .)

根目录下 CMakeLists.txt

1
2
3
4
5
6
7
cmake_minimum_required(VERSION 3.12)
project(hellocmake LANGUAGES CXX)

add_subdirectory(hellolib)

add_executable(a.out main.cpp)
target_link_libraries(a.out PUBLIC hellolib)

只需在生成 hellolib 库的时候定义头文件搜索路径,引用他的可执行文件 CMake 会自动添加这个路径。

其他选项

target_include_directories(myapp PUBLIC /usr/include/eigen3) # 添加头文件搜索目录
target_link_libraries(myapp PUBLIC hellolib) # 添加要链接的库
target_add_definitions(myapp PUBLIC MY_MACRO=1) # 添加一个宏定义
target_compile_options(myapp PUBLIC -fopenmp) # 添加编译器命令行选项
target_sources(myapp PUBLIC hello.cpp other.cpp) # 添加要编译的源文件

以及可以通过以下指令(不推荐使用),把选项加到所有接下来的目标去:

include_directories(/opt/cuda/include) # 添加头文件搜索目录
link_directories(/opt/cuda) # 添加库文件的搜索路径
add_definitions(MY_MACRO=1) # 添加一个宏定义
add_compile_options(-fopenmp) # 添加编译器命令行选项

这些命令是全局的。

引用系统中预安装的第三方库,如使用 aptpacman 等安装的库,可以使用下面的方式:

1
2
find_package(fmt REQUIRED)
target_link_libraries(myexec PUBLIC fmt::fmt)

现代 CMake 认为一个package可以提供多个components,如TBB这个包,就包含了tbb, tbbmalloc, tbbmalloc_proxy 这三个components,为避免冲突,每个package都享有一个独立的名字空间,以 :: 分割。可以手动指定需要的组件:

1
2
find_package(TBB REQUIRED COMPONENTS tbb tbbmalloc REQUIRED)
target_link_libraries(myexec PUBLIC TBB::tbbmalloc)