文章目录
- 一、CMake 环境搭建
- 二、简单入门
- 1. 项目结构 与 示例源码
- 2. 运行查看
- 3. 然后在终端下输入 make
- 4. 然后运行 main
- 三、编译多个源文件 (同一目录下 简单版)
- 1. 项目结构 与 示例源码
- 2. 运行查看
- 四、编译多个源文件 (同一目录下 复杂版)
- 1. 新指令 `aux_source_directory`
- 2. 项目结构 与 示例源码
- 3. 运行查看
- 五、编译多个源文件 (不同目录下)
- 1. 新指令 `include_directories`
- 2. 项目结构 与 示例源码
- 3. 运行查看
- 六、项目级的组织结构
- 1. 新指令 `add_subdirectory`
- 2. 项目结构 与 示例源码
- 3. 新指令 `set`
- 4. 运行查看
- 七、动态库和静态库的编译控制
- 1. 生成库文件
- 1.1 新指令 `add_library`
- 1.2 新指令 `set_target_properties`
- 1.3 项目结构 与 示例源码
- 1.4 运行查看
- 2. 链接库文件
- 2.1 新指令 `find_library`
- 2.2 新指令 `target_link_libraries`
- 2.3 项目结构 与 示例源码
- 2.4 运行查看
CMake是开源、跨平台的构建工具,可以让我们通过 编写简单的配置文件 去生成 本地的 Makefile,这个配置文件是 独立于运行平台和编译器 的,这样就不用亲自去编写 Makefile 了,而且配置文件可以直接拿到其它平台上使用,无需修改,非常方便。
这里主要讲述在 Linux 下如何使用 CMake 来编译我们的程序。
一、CMake 环境搭建
Ubuntu 下安装 CMake 的命令如下
sudo apt install cmake// 安装完成后可以输入下面代码查看
cmake -version
二、简单入门
1. 项目结构 与 示例源码
|
|——CMakeLists.txt
|——main.cpp
// main.cpp
#include <iostream>int main()
{std::cout << "Hello World" << std::endl;return 0;
}
// CMakeLists.txt
cmake_minimum_required (VERSION 2.8)project (demo)add_executable(main main.cpp)
2. 运行查看
在终端下切到 main.cpp
所在的目录下,然后输入以下命令运行 cmake:
cmake .
此时,建议留意一下这个文件夹下多生成的文件都有哪些。可以看到成功生成了 Makefile
,还有一些 cmake 运行时自动生成的文件。
3. 然后在终端下输入 make
make
可以看到执行 cmake 生成的 Makefile
可以显示进度,并带颜色。再看下目录下的文件可以看到我们需要的 可执行文件 main 也成功生成了!
4. 然后运行 main
./main
三、编译多个源文件 (同一目录下 简单版)
1. 项目结构 与 示例源码
|
|——CMakeLists.txt
|——main.cpp
|——testFunc.cpp
|——testFunc.h
基本方法就是,在 add_executable
的参数里把 testFunc.c
加进来,值得在意的是,只要加 .cpp
文件,即源文件,即可。
// testFunc.h#ifndef _TEST_FUNC_H_
#define _TEST_FUNC_H_void func(int data);#endif
// testFunc.cpp#include <iostream>
#include "testFunc.h"void func(int data)
{std::cout << "data is " << data << std::endl;
}
// main.cpp#include <iostream>
#include "testFunc.h"int main()
{func(100);return 0;
}
// CMakeLists.txt
cmake_minimum_required (VERSION 2.8)project (demo)add_executable(main main.cpp testFunc.cpp)
2. 运行查看
cmake .
make
./main
四、编译多个源文件 (同一目录下 复杂版)
1. 新指令 aux_source_directory
可以类推,如果在 同一目录下 有 多个源文件,那么只要在 add_executable
里把 所有源文件都添加进去 就可以了。
但是如果有一百个源文件,再这样做就有点坑了,无法体现 cmake 的优越性。因此 cmake 提供了一个命令可以 把指定目录下所有的源文件存储在一个变量中 ,这个命令就是:
aux_source_directory(dir var)
dir
就是指定目录的路径,一般是相对路径,当前路径./
指的是CMakeLists.txt
所在的目录。var
就是变量。
2. 项目结构 与 示例源码
|
|——CMakeLists.txt
|——main.cpp
|——testFunc1.cpp
|——testFunc1.h
|——testFunc.cpp
|——testFunc.h
// testFunc.h#ifndef _TEST_FUNC_H_
#define _TEST_FUNC_H_void func(int data);#endif
// testFunc.cpp#include <iostream>
#include "testFunc.h"void func(int data)
{std::cout << "data is " << data << std::endl;
}
//testFunc1.h#ifndef _TEST_FUNK_H_
#define _TEST_FUNC1_H_void func1(int data);#endif
// testFunc1.cpp#include <iostream>
#include "testFunc1.h"void func1(int data)
{std::cout << "data is " << data << std::endl;
}
// main.cpp#include <iostream>
#include "testFunc.h"
#include "testFunc1.h"int main()
{func(100);func1(200);return 0;
}
// CMakeLists.txt
cmake_minimum_required (VERSION 2.8)project (demo)aux_source_directory(. SRC_LIST)add_executable(main ${SRC_LIST})
3. 运行查看
cmake .
make
./main
五、编译多个源文件 (不同目录下)
一般来说,当程序文件比较多时,我们会进行分类管理,把代码根据功能放在不同的目录下,这样方便查找。那么这种情况下如何编写 CMakeLists.txt
呢?
1. 新指令 include_directories
是 CMake 中用来 指定 头文件搜索路径 的命令,路径之间用空格分隔。语法格式为:
include_directories([AFTER|BEFORE] [SYSTEM] dir1 dir2 ...)
参数 | 含义 |
---|---|
dir1 dir2 ... | 要添加的头文件路径(可以是 对于 CMakeLists.txt 的相对路径 或 绝对路径) |
BEFORE | 将路径添加到搜索路径的前面(优先查找) |
AFTER | 将路径添加到搜索路径的末尾(默认行为) |
SYSTEM | 标记为系统路径(可避免显示头文件警告) |
因为 main.cpp
里 include
了 testFunc.h
和 testFunc1.h
,如果没有这个命令来指定头文件所在位置,就会无法编译。当然,也可以在 main.cpp
里使用 include
来指定路径,如下
#include "test_func/testFunc.h"
#include "test_func1/testFunc1.h"
一切以 main.cpp
的位置为起点。另外,我们使用了 2 次 aux_source_directory
,因为源文件分布在 2 个 目录下,所以添加 2 次。
2. 项目结构 与 示例源码
|
|—CMakeLists.txt
|—main.cpp
|—test_func
| |—testFunc.cpp
| |—testFunc.h
|—test_func1|—testFunc1.cpp|—testFunc1.h
CMakeLists.txt 一定要和 main.cpp 在同一目录下。
把之前的 testFunc.cpp
和 testFunc.h
放到 test_func
目录下,testFunc1.cpp
和 testFunc1.h
则放到 test_func1
目录下。然后写 CMakeLists.txt
文件,其中,CMakeLists.txt
和 main.cpp
在同一目录下,内容修改成如下所示:
// CMakeLists.txt
cmake_minimum_required (VERSION 2.8)project (demo)include_directories (./test_func ./test_func1)aux_source_directory (test_func SRC_LIST)
aux_source_directory (test_func1 SRC_LIST1)add_executable (main main.cpp ${SRC_LIST} ${SRC_LIST1})
3. 运行查看
cmake .
make
./main
六、项目级的组织结构
正规一点来说,一般会把 源文件 放到 src
目录下,把 头文件 放入到 include
文件下,生成的对象文件 放入到 build
目录下,最终输出的 可执行程序文件 会放到 bin
目录下,这样整个结构更加清晰。
1. 新指令 add_subdirectory
ass_subdirectory()
是 CMake 中用于 添加子目录 的指令,它的作用是:将某个子目录 (包含 CMakeLists.txt
) 纳入当前项目的构建系统,并构建该子目录中的源文件、目标文件等内容。
基本语法如下:
add_subdirectory(<source_dir> [binary_dir] [EXCLUDE_FROM_ALL])
source_dir
(必选):
要添加的子目录路径 (相对于当前CMakeLists.txt
文件的位置)。
如果子目录再次包含的CMakeLists.txt
,则将继续处理里层的CMakeLists.txt
,否则就继续处理当前源代码。binary_dir
(可选):
构建该子目录时,生成 中间文件 的地方 (通常是build
的临时目录)。
如果不指定,默认 等于source_dir
。EXCLUDE_FROM_ALL
(可选):
表示这个目录中的目标不会被默认加入all
目标(即不会被make
默认构建)。
根据上面的说明,特别是对 source_dir
的说明可以知道,实际上该指令告诉 CMake:进入这个子目录,处理该目录中的 CMakeLists.txt
文件,像处理主目录一样继续处理构建逻辑。这是一个 递归逻辑。
2. 项目结构 与 示例源码
|
|—bin
|—build
|—CMakeLists.txt
|—include
| |—testFunc1.h
| |—testFunc.h
|—src|—CMakeLists.txt|—main.cpp|—testFunc1.cpp|—testFunc.cpp
testFunc.h
、testFunc.cpp
、testFunc1.h
、testFunc1.cpp
和 main.cpp
的内容不变,需要修改 CMakeLists.txt
:
//CMakeLists.txt
cmake_minimum_required (VERSION 2.8)project (demo)add_subdirectory (src)
这里指定 src
目录下存放了源文件,当执行 cmake
时,就会进入 src
目录下去找 src
目录下的 CMakeLists.txt
,所以在 src
目录下也建立一个 CMakeLists.txt
,内容如下:
// src/CMakeLists.txt
aux_source_directory (. SRC_LIST)include_directories (../include)add_executable (main ${SRC_LIST})set (EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
3. 新指令 set
在 CMake 中,set()
是用于 定义变量 或 修改变量值 的命令。其基本语法如下:
set(<variable> <value>... [PARENT_SCOPE | CACHE <type> <docstring> [FORCE]])
参数 | 说明 |
---|---|
<variable> | 要定义的变量名 |
<value>... | 要赋的值,可以是单个字符串,也可以是多个,表示一个“CMake 列表” |
PARENT_SCOPE | 将变量值赋给上一层作用域(例如在函数或子目录中向上传递) |
CACHE | 将变量保存到 CMake 缓存中(供 cmake-gui 或其他模块使用) |
FORCE | 强制更新缓存中的已有值 |
接下来解析一下 set (EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
的含义:
- 将变量
EXECUTABLE_OUTPUT_PATH
设置为${PROJECT_SOURCE_DIR}/bin
。 EXECUTABLE_OUTPUT_PATH
是一个 CMake 内置变量,表示 所有可执行文件 (add_executable
产生的) 要放到哪个目录。PROJECT_SOURCE_DIR
是一个 CMake 内置变量,表示主 CMakeLists.txt
所在的目录,即 项目根目录。/bin
表示你希望所有可执行文件最终输出到 项目根目录下的 bin 文件夹 中。
整个语句的含义就是,“把构建出来的可执行文件统一放到项目根目录下的 bin/ 目录中”。
4. 运行查看
cd build
cmake ..
make
这里解释一下为什么在build目录下运行cmake?
还记得在第一个例子里cmake和make之后会生成很多文件,但是跟我们的运行并没有什么关系,因此,如果能把编译隔离在某个文件夹,这样cmake的时候所有的中间文件都将在这个目录下生成,删除的时候也很好删除,非常方便。
如果不这样做,cmake运行时生成的附带文件就会跟源码文件混在一起,这样会对程序的目录结构造成污染。
七、动态库和静态库的编译控制
有时 只需要编译 出 动态库 和 静态库,然后 等着让其它程序去使用。让我们看下这种情况该如何使用 cmake。
1. 生成库文件
1.1 新指令 add_library
在 CMake 中,add_library()
用于 创建一个 库目标 (library target),可以是:
- 静态库 (Static Library):
.a
或.lib
文件 - 动态库 (Shared Library / 共享库):
.so
或.dll
文件 - 模块库 (Module Library):动态加载的插件
.so/.dll
这些库可以被其他目标(如可执行文件或其他库)链接使用。
其基本语法格式为:
add_library(<name> [STATIC | SHARED | MODULE | OBJECT | INTERFACE] [EXCLUDE_FROM_ALL] source1 source2 ...)
参数 | 说明 |
---|---|
<name> | 创建的库的名字(供 target_link_libraries() 使用) |
STATIC | 静态库(默认类型) |
SHARED | 共享库(动态库) |
MODULE | 模块库(插件库) |
OBJECT | 对象库(仅编译 .o 文件,不链接) |
INTERFACE | 接口库(无源码,仅用于头文件和依赖传播) |
EXCLUDE_FROM_ALL | 不在默认构建目标中构建 |
source1 source2 ... | 指定生成库的源文件 |
1.2 新指令 set_target_properties
set_target_properties()
是 CMake 用来 设置构建目标 (target) 的一些属性 的指令,如 输出路径、编译选项、版本号、命名规则、是否是可导出库 等。下面的示例中该语句的作用是 设置最终生成的库的名称,还有其它功能,如设置库的版本号等。
其基本语法是:
set_target_properties(<target1> <target2> ... PROPERTIES <prop1> <value1> <prop2> <value2> ...)
参数 | 说明 |
---|---|
<target1> <target2> ... | 一个或多个目标(可执行、库等) |
PROPERTIES <prop1> <value1> <prop2> <value2> ... | 列出要设置的属性名与对应的值 |
常见属性及作用
属性名 | 作用 | 示例值 |
---|---|---|
RUNTIME_OUTPUT_DIRECTORY | 可执行文件输出目录 | ${CMAKE_BINARY_DIR}/bin |
LIBRARY_OUTPUT_DIRECTORY | 动态库 .so/.dll 输出目录 | ${CMAKE_BINARY_DIR}/lib |
ARCHIVE_OUTPUT_DIRECTORY | 静态库 .a/.lib 输出目录 | ${CMAKE_BINARY_DIR}/lib |
OUTPUT_NAME | 最终目标名称(自定义输出文件名) | mylib_custom |
VERSION | 库版本号(一般用于共享库) | 1.2.3 |
SOVERSION | 库的 API 版本(与 VERSION 搭配使用) | 1 |
POSITION_INDEPENDENT_CODE | 是否编译为位置无关代码(PIC),通常用于构建共享库 | ON / OFF |
LINKER_LANGUAGE | 指定链接语言(通常自动推断) | CXX 、C |
1.3 项目结构 与 示例源码
|
|—build
|—CMakeLists.txt
|—lib
|—testFunc|—testFunc.cpp|—testFunc.h
我们会在 build
目录下运行 cmake,并把生成的库文件存放到 lib
目录下。
// CMakeLists.txt
cmake_minimum_required (VERSION 3.5)project (demo)set (SRC_LIST ${PROJECT_SOURCE_DIR}/testFunc/testFunc.cpp)add_library (testFunc_shared SHARED ${SRC_LIST})
add_library (testFunc_static STATIC ${SRC_LIST})set_target_properties (testFunc_shared PROPERTIES OUTPUT_NAME "testFunc")
set_target_properties (testFunc_static PROPERTIES OUTPUT_NAME "testFunc")set (LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)
LIBRARY_OUTPUT_PATH
是一个 CMake 内置变量,表示 库文件的 默认输出路径,这里设置为工程目录下的lib
目录。
使用
set_target_properties
重新定义了库的输出名称,如果不使用set_target_properties
也可以,那么库的名称就是add_library
里定义的名称,只是连续2次使用add_library
指定库名称时(第一个参数),这个名称不能相同,而set_target_properties
可以把名称设置为相同,只是最终生成的库文件后缀不同(一个是.so
,一个是.a
),这样相对来说会好看点。
1.4 运行查看
cd build/
cmake ..
make
cd ../lib/
ls
然后输出如下图所示,.a
是静态库,.so
是动态库:
2. 链接库文件
既然我们已经生成了库,那么就进行链接测试下。
2.1 新指令 find_library
find_library()
是 CMake 用来 查找本地或系统中已存在的库文件,如 .so
、.a
、.lib
,的命令。它常用于 查找系统预装库或第三方安装库的位置,并 将路径保存到变量 中以供 target_link_libraries()
使用。
其基本语法如下:
find_library(<VAR> name1 [name2 ...][PATHS path1 path2 ...][HINTS hint1 hint2 ...][PATH_SUFFIXES suffix1 suffix2 ...][DOC "description"][NO_DEFAULT_PATH | NO_CMAKE_PATH | NO_SYSTEM_ENVIRONMENT_PATH | ...])
参数 | 含义 |
---|---|
<VAR> | 用来 保存搜索结果 的变量名 |
name1 name2 ... | 要查找的库名(不包含前缀 lib 和扩展名 .so/.a/.lib ) |
PATHS | 指定搜索路径 |
HINTS | 提示路径(比 PATHS 更优先) |
PATH_SUFFIXES | 搜索路径的后缀,比如 lib , lib64 |
DOC | 文档说明,主要用于 cmake-gui 中的说明 |
NO_DEFAULT_PATH | 禁用默认搜索路径(更严格控制搜索位置) |
NO_CMAKE_PATH | 禁用 CMAKE_INSTALL_PREFIX/lib 等路径 |
NO_SYSTEM_ENVIRONMENT_PATH | 禁用系统环境变量路径搜索 |
2.2 新指令 target_link_libraries
target_link_libraries
在 CMake 中用于 指定某个 target 需要链接的库文件。它是现代 CMake 推荐的做法,因为它能很好地控制依赖关系,支持作用域 (PUBLIC/PRIVATE/INTERFACE
)。
其基本语法如下:
target_link_libraries(<target> [<scope>] <lib1> <lib2> ...)
参数 | 说明 |
---|---|
<target> | 需要链接库的目标(可执行文件或库) |
<scope> | 可选,作用域关键字(PUBLIC / PRIVATE / INTERFACE ) |
<lib1> <lib2> ... | 需要链接的库,可以是 库名、变量 或 路径 |
作用域的含义:
作用域 | 含义 |
---|---|
PRIVATE | 仅 target 需要 lib ,但不会影响其他依赖它的 target 。 |
PUBLIC | target 需要 lib ,依赖 target 的其他 target 也需要 lib 。 |
INTERFACE | target 不需要 lib ,但依赖 target 的其他 target 也需要 lib 。 |
2.3 项目结构 与 示例源码
重新建一个工程目录,然后把上节生成的库 拷贝 过来,然后在在工程目录下新建 src
目录和 bin
目录,在 src
目录下添加一个 main.cpp
,整体结构如下:
|
|—bin
|—build
|—CMakeLists.txt
|—src
| |—main.cpp
|—testFunc|—inc| |—testFunc.h|—lib|—libtestFunc.a|—libtestFunc.so
// main.cpp
#include <iostream>
#include "testFunc.h"int main()
{func(100);return 0;
}
// CMakeList.cpp
cmake_minimum_required (VERSION 3.5)project (demo)set (EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)set (SRC_LIST ${PROJECT_SOURCE_DIR}/src/main.c)# find testFunc.h
include_directories (${PROJECT_SOURCE_DIR}/testFunc/inc)find_library(TESTFUNC_LIB testFunc HINTS ${PROJECT_SOURCE_DIR}/testFunc/lib)add_executable (main ${SRC_LIST})target_link_libraries (main ${TESTFUNC_LIB})
2.4 运行查看
cd build/
cmake ..
make
cd ../bin/
./main
输出如下图所示:
在
lib
目录下有testFunc
的静态库和动态库,find_library(TESTFUNC_LIB testFunc…
默认 是查找 动态库。
如果想直接指定使用动态库还是静态库,可以写成find_library(TESTFUNC_LIB libtestFunc.so …
或者find_library(TESTFUNC_LIB libtestFunc.a …
。