Linux下CMake使用方法

在 linux 平台下使用 CMake 生成 Makefile 并编译的流程如下:

  1. 编写 CMake 配置文件 CMakeLists.txt 。
  2. 执行命令 cmake PATH 或者 ccmake PATH 生成 Makefile 1 1ccmakecmake 的区别在于前者提供了一个交互式的界面。。其中, PATH 是 CMakeLists.txt 所在的目录。
  3. 使用 make 命令进行编译。

本文将从实例入手,一步步讲解 CMake 的常见用法,文中所有的实例代码可以在这里找到。如果你读完仍觉得意犹未尽,可以继续学习我在文章末尾提供的其他资源。

1.单个源文件

对于简单的项目,只需要写几行代码就可以了。例如,假设现在我们的项目中只有一个源文件 main.c ,首先编写 CMakeLists.txt 文件,并保存在与 main.c 源文件同个目录下:

1
2
3
4
5
6
# CMake 最低版本号要求
cmake_minimum_required (VERSION 2.8)
# 项目信息
project (Demo1)
# 指定生成目标
add_executable(Demo1 main.cc)

CMakeLists.txt 的语法比较简单,由命令、注释和空格组成,其中命令是不区分大小写的。符号 # 后面的内容被认为是注释。命令由命令名称、小括号和参数组成,参数之间使用空格进行间隔。

对于上面的 ==CMakeLists.txt== 文件,依次出现了几个命令:

  1. cmake_minimum_required:指定运行此配置文件所需的 CMake 的最低版本;
  2. project:参数值是 Demo1,该命令表示项目的名称是 Demo1
  3. add_executable: 将名为 main.c 的源文件编译成一个名称为 Demo 的可执行文件。

之后,在当前目录执行 cmake . ,得到 Makefile 后再使用 make 命令编译得到 Demo1 可执行文件。

2.多个源文件

1) 同一目录,多个源文件

上面的例子只有单个源文件。现在假如把 power 函数单独写进一个名为 MathFunctions.c 的源文件里,使得这个工程变成如下的形式:

1
2
3
4
5
6
7
./Demo2
|
+--- main.cc
|
+--- MathFunctions.cc
|
+--- MathFunctions.h

这个时候,CMakeLists.txt 可以改成如下的形式:

1
2
3
4
5
6
# CMake 最低版本号要求
cmake_minimum_required (VERSION 2.8)
# 项目信息
project (Demo2)
# 指定生成目标
add_executable(Demo main.cc MathFunctions.cc)

唯一的改动只是在 add_executable 命令中增加了一个 MathFunctions.cc 源文件。这样写当然没什么问题,但是如果源文件很多,把所有源文件的名字都加进去将是一件烦人的工作。更省事的方法是使用 aux_source_directory 命令,该命令会查找指定目录下的所有源文件,然后将结果存进指定变量名。其语法如下:

1
aux_source_directory(<dir> <variable>)

因此,可以修改 CMakeLists.txt 如下:

1
2
3
4
5
6
7
8
9
# CMake 最低版本号要求
cmake_minimum_required (VERSION 2.8)
# 项目信息
project (Demo2)
# 查找当前目录下的所有源文件
# 并将名称保存到 DIR_SRCS 变量
aux_source_directory(. DIR_SRCS)
# 指定生成目标
add_executable(Demo ${DIR_SRCS})

这样,CMake 会将当前目录所有源文件的文件名赋值给变量 DIR_SRCS ,再指示变量 DIR_SRCS 中的源文件需要编译成一个名称为 Demo 的可执行文件。

2) 多个目录,多个源文件

现在进一步将 MathFunctions.h 和 MathFunctions.cc 文件移动到 math 目录下。

1
2
3
4
5
6
7
8
9
./Demo3
|
+--- main.cc
|
+--- math/
|
+--- MathFunctions.cc
|
+--- MathFunctions.h

于这种情况,需要分别在项目根目录 Demo3 和 math 目录里各编写一个 CMakeLists.txt 文件。为了方便,我们可以先将 math 目录里的文件编译成静态库再由 main 函数调用。

根目录中的 CMakeLists.txt :

1
2
3
4
5
6
7
8
9
10
11
12
13
# CMake 最低版本号要求
cmake_minimum_required (VERSION 2.8)
# 项目信息
project (Demo3)
# 查找当前目录下的所有源文件
# 并将名称保存到 DIR_SRCS 变量
aux_source_directory(. DIR_SRCS)
# 添加 math 子目录
add_subdirectory(math)
# 指定生成目标
add_executable(Demo main.cc)
# 添加链接库
target_link_libraries(Demo MathFunctions)

该文件添加了下面的内容: 第3行,使用命令 add_subdirectory 指明本项目包含一个子目录 math,这样 math 目录下的 CMakeLists.txt 文件和源代码也会被处理 。第6行,使用命令 target_link_libraries 指明可执行文件 main 需要连接一个名为 MathFunctions 的链接库 。

==子目录中的 CMakeLists.txt==:

1
2
3
4
5
# 查找当前目录下的所有源文件
# 并将名称保存到 DIR_LIB_SRCS 变量
aux_source_directory(. DIR_LIB_SRCS)
# 生成链接库
add_library (MathFunctions ${DIR_LIB_SRCS})

在该文件中使用命令 add_library 将 src 目录中的源文件编译为静态链接库。

3.正规一点的组织结构

正规一点来说,一般会把源文件放到src目录下,把头文件放入到include文件下,生成的对象文件放入到build目录下,最终输出的elf文件会放到bin目录下,这样整个结构更加清晰。让我们把前面的文件再次重新组织下,

我们在最外层目录下新建一个CMakeLists.txt,内容如下,

1
2
3
4
5
cmake_minimum_required (VERSION 2.8)

project (demo)

add_subdirectory (src)

这里出现一个新的命令add_subdirectory(),这个命令==可以向当前工程添加存放源文件的子目录,并可以指定中间二进制和目标二进制的存放位置==,具体用法可以百度。
这里指定src目录下存放了源文件,当执行cmake时,就会进入src目录下去找src目录下的CMakeLists.txt,所以在src目录下也建立一个CMakeLists.txt,内容如下,

1
2
3
4
5
6
7
aux_source_directory (. SRC_LIST)

include_directories (../include)

add_executable (main ${SRC_LIST})

set (EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)

这里又出现一个新的命令set,是用于定义变量的,EXECUTABLE_OUT_PATHPROJECT_SOURCE_DIR是CMake自带的预定义变量,其意义如下,

EXECUTABLE_OUTPUT_PATH :目标二进制可执行文件的存放位置
PROJECT_SOURCE_DIR:工程的根目录

所以,这里set的意思是把存放elf文件的位置设置为工程根目录下的bin目录。

添加好以上这2个CMakeLists.txt后,整体文件结构如下,

下面来运行cmake,不过这次先让我们切到build目录下,然后输入以下命令,
cmake ..
Makefile会在build目录下生成,然后在build目录下运行make,

运行ok,我们再切到bin目录下,发现main已经生成,并运行测试,

测试OK!

这里解释一下为什么在build目录下运行cmake?从前面几个case中可以看到,如果不这样做,cmake运行时生成的附带文件就会跟源码文件混在一起,这样会对程序的目录结构造成污染,而在build目录下运行cmake,生成的附带文件就只会待在build目录下,如果我们不想要这些文件了就可以直接清空build目录,非常方便。

另外一种写法:
前面的工程使用了2个CMakeLists.txt,这种写法是为了处理需要生成多个elf文件的情况,最外层的CMakeLists.txt用于掌控全局,使用add_subdirectory来添加要生成elf文件的源码目录。

如果只生成一个elf文件,那么上面的例子可以只使用一个CMakeLists.txt,可以把最外层的CMakeLists.txt内容改成如下,

1
2
3
4
5
6
7
8
9
10
11
cmake_minimum_required (VERSION 2.8)

project (demo)

aux_source_directory (src SRC_LIST)

include_directories (include)

add_executable (main ${SRC_LIST})

set (EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)

==同时,还要把src目录下的CMakeLists.txt删除。==

4.动态库和静态库的编译控制

有时我们只需要编译出动态库,静态库,然后等着让其它程序去使用。让我们看下这种情况该如何使用cmake。首先按照如下重新组织文件,只留下testFunc.h和TestFunc.c,

我们会在build目录下运行cmake,并把生成的库文件存放到lib目录下。
最外层的CMakeLists.txt内容如下,

1
2
3
4
5
cmake_minimum_required (VERSION 2.8)

project (demo)

add_subdirectory (lib_testFunc)

lib_testFunc目录下的CMakeLists.txt如下,

1
2
3
4
5
6
7
8
9
aux_source_directory (. SRC_LIST)

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)

这里又出现了新的命令和预定义变量,

add_library: 生成动态库或静态库(第1个参数指定库的名字;第2个参数决定是动态还是静态,如果没有就默认静态;第3个参数指定生成库的源文件)
set_target_properties: 设置输出的名称,还有其它功能,如设置库的版本号等等
LIBRARY_OUTPUT_PATH: 库文件的默认输出路径,这里设置为工程目录下的lib目录
好了,让我们进入build目录下运行cmake ..,成功后再运行make,

cd到lib目录下进行查看,发现已经成功生成了动态库和静态库,

ps:可以看出前面使用set_target_properties重新定义了库的输出名字,如果不用set_target_properties也可以,那么库的名字就是add_library里定义的名字,只是我们连续2次使用add_library指定库名字时,这个名字不能相同,而set_target_properties可以把名字设置为相同,只是最终生成的库文件后缀不同,这样相对来说会好看点。

5.对库进行链接

既然我们已经生成了库,那么就进行链接测试下。把build里的文件都删除,然后在在工程目录下新建src目录和bin目录,在src目录下添加一个main.c和一个CMakeLists.txt,整体结构如下,

这里写图片描述

main.c内容如下,

1
2
3
4
5
6
7
8
9
10
#include <stdio.h>
#include "testFunc.h"

int main(void)
{
func(100);

return 0;

}

修改工程目录下的CMakeLists.txt,如下,

1
2
3
4
5
6
7
cmake_minimum_required (VERSION 2.8)

project (demo)

add_subdirectory (lib_testFunc)

add_subdirectory (src)

只是使用add_subdirectorysrc目录添加进来。
src目录下的CMakeLists.txt如下,

1
2
3
4
5
6
7
8
9
10
11
12
aux_source_directory (. SRC_LIST)
# find testFunc.h

include_directories (../lib_testFunc)

link_directories (${PROJECT_SOURCE_DIR}/lib)

add_executable (main ${SRC_LIST})

target_link_libraries (main testFunc)

set (EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)

这里出现2个新的命令,

link_directories: 添加非标准的共享库搜索路径
target_link_libraries: 把目标文件与库文件进行链接

make成功,进入到bin目录下查看,发现main已经生成,并运行,

运行成功!

ps:在lib目录下有testFunc的静态库和动态库,target_link_libraries (main testFunc)默认是使用动态库,如果lib目录下只有静态库,那么这种写法就会去链接静态库。也可以直接指定使用动态库还是静态库,写法是:

1
2
3
target_link_libraries (main libtestFunc.so)
#或
target_link_libraries (main libtestFunc.a)

ps: 查看elf文件使用了哪些库,可以使用readelf -d ./xx来查看

6.添加编译选项

有时编译程序时想添加一些编译选项,如-Wall-std=c++11等,就可以使用add_compile_options来进行操作。
这里以一个简单程序来做演示,main.cpp如下

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

int main(void)
{
auto data = 100;
std::cout << "data: " << data << "\n";
return 0;
}

CMakeLists.txt内容如下,

1
2
3
4
5
6
7
8
9
cmake_minimum_required (VERSION 2.8)

project (demo)

set (EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)

add_compile_options(-std=c++11 -Wall)

add_executable(main main.cpp)

整体目录结构如下,

在这里插入图片描述

然后cd到build目录下,执行cmake .. && make命令,就可以在bin目录下得到main的elf文件

7. 添加控制选项

有时希望在编译代码时只编译一些指定的源码,例如本来要编译生成多个bin或库文件,现在只想生成某些指定的bin或库文件,这时可以使用cmake的option命令。

这里仍然使用例子来解释,假设我们现在的工程会生成2个bin文件,main1和main2,现在整体结构体如下,

在这里插入图片描述

外层的CMakeLists.txt内容如下,

1
2
3
4
5
6
7
8
9
cmake_minimum_required(VERSION 2.8)

project(demo)

option(MYDEBUG "enable debug compilation" OFF)

set (EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)

add_subdirectory(src)

这里使用了option命令,其第一个参数是这个option的名字,第二个参数是字符串,用来描述这个option是来干嘛的,第三个是option的值,ON或OFF,也可以不写,不写就是默认OFF。

然后编写src目录下的CMakeLists.txt,如下

1
2
3
4
5
6
7
8
9
cmake_minimum_required (VERSION 2.8)

add_executable(main1 main1.c)

if (MYDEBUG)
add_executable(main2 main2.c)
else()
message(STATUS "Currently is not in debug mode")
endif()

注意,这里使用了if-else来根据option来决定是否编译main2.c
其中main1.c和main2.c的内容如下,

1
2
3
4
5
6
7
8
9
10
11
// main1.c
#include <stdio.h>

int main(void)
{
printf("hello, this main1\n");


return 0;

}
1
2
3
4
5
6
7
8
9
10
11
// main2.c
#include <stdio.h>

int main(void)
{
printf("hello, this main2\n");


return 0;

}

然后cd到build目录下输入cmake .. && make就可以只编译出main1,如果想编译出main2,就把MYDEBUG设置为ON,再次输入cmake .. && make重新编译。

每次想改变MYDEBUG时都需要去修改CMakeLists.txt,有点麻烦,其实可以通过cmake的命令行去操作,例如我们想把MYDEBUG设置为OFF,先cd到build目录,然后输入cmake .. -DMYDEBUG=ON,这样就可以编译出main1和main2 (在bin目录下)

来源:https://blog.csdn.net/qq_28114615/article/details/90406140

2020.3.29 19:01