我在学习过程中参阅了ttroy50的CMake-Examples教程,我觉得他的教程非常容易理解。在此,我表示对他们的感谢。
在本篇教程中,我只和大家分享一些常见情形的处理。
基本知识扫盲
- CMake是一个跨平台的用于构建C/C++项目的工具,类似于Java项目项目中常用的Maven、Gradel
- CMake需要和make、gcc/g++一起使用,它们在构建工程中的作用不同。
- gcc/g++是linux下的一个编译器,用于将C/C++源码编译链接成可执行文件或者是库文件。(它的一个windows移植版本是mingw64,大家熟悉的C/C++ IDE 【CodeBlocks】【Dev-C++】中就内置了这个ming64)
- gcc/g++可以用于直接编译单文件,然后在进行链接工作,最终形成可执行程序。但编译复杂工程时需要按照特定的顺序执行这些gcc/g++指令,人工敲指令不太方便,因此人们希望通过制定依赖规则等来自动化地编译链接,因此有了make这一工具
- 使用make工具需要编写makefile文件来指定规则,make会在当前目录自动找到这个文件执行批处理构建工作;但对于复杂工程来说,依赖关系可能比较复杂,编写makefile也比较麻烦。这是,人们又设计了CMake用于生成makefile文件
- 使用CMake工具需要编写CMakeLists.txt文件,用于在较高的抽象层次上指明构建任务,基本上类似于直接在IDE中设定表格选项。
构建:单文件工程
项目结构
PROJECT_A
├── CMakeLists.txt
└── main.cpp
CMakelists写法
# 用于指定需要的CMamke的最低版本
cmake_minimum_required(VERSION 3.5)
# 用于指定项目名称,名字可以和项目根目录名字不同
project (hello_cmake)
# 用于创建可执行文件,第一个参数是可执行文件名字,后面的参数是用于执行需要用到的源码文件
add_executable(hello_cmake main.cpp)
命令行
# 为了保持目录整洁,创建build文件夹用于存放各种构建生成的文件
mkdir build
cd build
# cmake的参数为项目CMakeList.txt所在目录,生成的文件会放在cmake执行的目录中
cmake ..
# 当前目录中已生成makefile,make可以自动发现它并执行
make
# 可执行文件已生成,直接运行即可
./hello_cmake
构建:多文件(含有头文件)的工程
项目结构
Project_B
├── CMakeLists.txt
├── include
│ └── Hello.h
└── src
├── Hello.cpp
└── main.cpp
CMakelists写法
# 用于指定需要的CMamke的最低版本
cmake_minimum_required(VERSION 3.5)
# 用于指定项目名称,名字可以和项目根目录名字不同
# 相当于设置变量 ${PROJECT_NAME} = "hello_headers"
project (hello_headers)
# 设定一个变量方便后续使用,相当于 ${SOURCES} = "src/Hello.cpp src/main.cpp"
set(SOURCES
src/Hello.cpp
src/main.cpp
)
# 用于创建可执行文件,第一个参数是可执行文件名字,源码参数用变量代替
add_executable(hello_headers ${SOURCES})
# 用于指明构建目标的include目录,第一个参数为构建目标,第三个参数为include目录
target_include_directories(hello_headers PRIVATE ${PROJECT_SOURCE_DIR}/include)
说明
- CMakeLists.txt中的语法规则和shell差不多,所有的变量、参数默认都是字符串类型,变量通过$进行引用
- CMakeLists.txt中可以使用一些系统变量,如 ${PROJECT_SOURCE_DIR} 、${PROJECT_BINARY_DIR}等
- ${PROJECT_SOURCE_DIR} 为源码所在目录,也就是cmake … 的指定的…,即./PROJECT_B
- ${PROJECT_BINARY_DIR}为二进制文件所在目录,即cmake指令执行的目录./PROJECT_B/build
构建:静态/动态库的构建
项目结构
PROJECT_C
├── CMakeLists.txt
├── include
│ └── Hello.h
└── src
├── Hello.cpp
└── main.cpp
CMakelists写法
cmake_minimum_required(VERSION 3.5)
project(hello_library)
############################################################
# 创建静态库
############################################################
# 用于创建库文件,类型为STATIC(静态库)/ SHARED(动态库),第三个参数指定所需源文件
# add_library(hello_library STATIC src/Hello.cpp)
add_library(hello_library SHARED src/Hello.cpp)
# 【可选】用于给库指定别名(ALIAS)
add_library(hello::library ALIAS hello_library)
# 用于指明构建目标的include目录,第一个参数为构建目标,第三个参数为include目录
target_include_directories(hello_library PUBLIC ${PROJECT_SOURCE_DIR}/include)
############################################################
# 创建可执行文件
############################################################
# 用于创建可执行文件,第一个参数是可执行文件名字,第二个参数为需要用到的源文件
add_executable(hello_binary src/main.cpp)
# 用于指明构建目标的依赖库,第一个参数为构建目标,第三个参数为依赖库
target_link_libraries( hello_binary PRIVATE hello_library)
说明
- 静态、动态库的构建基本一致,只需要在add_library()中间STATIC替换为SHARED
- 库的名字就是我们在add_library()中指定的这个,也可以给它指定别名;但生成的库文件后自动添加前缀lib和后缀.a(静态库)或.so(动态库),比如 libhello_library.a 和 libhello_library.so
- 上面这种引用库的方式仅限在同一工程中使用,更通用的写法我会在后面给出
- 可以看出,在一个CMakeLists.txt中是可以有多个构建任务的,可以未它们分别指明构建信息
构建:自定义静态/动态库的调用
项目结构
PROJECT_D
├── CMakeLists.txt
├── lib
| └── libhello.so
├── include
│ └── Hello.h
└── src
└── main.cpp
CMakelists写法
cmake_minimum_required(VERSION 3.5)
project(hello_link_library)
# 用于创建可执行文件,第一个参数是可执行文件名字,第二个参数为需要用到的源文件
add_executable(hello_binary src/main.cpp)
# 用于指明构建目标的依赖库,第一个参数为构建目标,第三个参数为依赖库
target_link_libraries(hello_binary PRIVATE ${PROJECT_SOURCE_DIR}/lib/libhello.so)
# 注意!还需要指明依赖库提供的include文件目录
target_include_directories(hello_binary PRIVATE ${PROJECT_SOURCE_DIR}/include)
说明
- 在使用依赖库时,需要指定依赖库位置和依赖库提供的头文件
- 为了方便,也可以通过设定变量的方式来避免直接在target_link_libraries() 中指明路径
构建:第三方依赖库的调用
项目结构
PROJECT_D
├── CMakeLists.txt
├── lib
| └── libtorch
│ ├── bin
│ ├── build-hash
│ ├── build-version
│ ├── include
│ ├── lib
│ └── share
├── include
│ └── Hello.h
└── src
└── main.cpp
CMakelists写法
cmake_minimum_required(VERSION 3.0 FATAL_ERROR)
project(hello-thrid_part)
# 在系统PATH和用户指定的CMAKE_PREFIX_PATH中寻找指定库的信息
find_package(Torch REQUIRED)
# 用于创建可执行文件
add_executable(hello-binary src/main.cpp)
# 用于指明构建目标的依赖库
target_link_libraries(libtest PRIVATE ${TORCH_LIBRARIES})
说明
-
find_package()在系统PATH和用户指定的CMAKE_PREFIX_PATH中寻找指定库的信息
-
CMAKE_PREFIX_PATH 无法再CMakeLists.txt中设置,需要在执行cmake命令时设定
cmake -DCMAKE_PREFIX_PATH=../lib/libtorch ..
-D可以后和信息挨着,也可以有间隔,表示定义变量
总结
-
设定变量
set(<variable> <value...>
-
创建输出实体(可执行文件/函数库)
add_execuable(<name> <sources...>) add_library(<name> <[STATIC | SHARED]> <sources...>)
-
指明依赖信息(包含信息、依赖库信息)
target_include_directories(<target> <[PUBLIC PRIVATE INTERFACE]> <include_path...>) target_link_libraries(<target> <[PUBLIC PRIVATE INTERFACE]> <library...>)
-
常用变量
${PROJECT_NAME} #项目名称 ${PROJECT_SOURCE_DIR} # 项目源码目录,在cmake命令中指明 ${PROJECT_BINARY_DIR} # 项目二进制文件目录,cmake执行目录
参考资料
- cmake-examples, ttroy50, https://github.com/ttroy50/cmake-examples
- cmake:target_** 中的 PUBLIC,PRIVATE,INTERFACE, 大川搬砖, https://zhuanlan.zhihu.com/p/82244559