CMake使用指南

CMake是一个跨平台的构建工具,可以用简单的语句来描述所有平台的安装(编译过程)。本文将从基础到进阶,详细介绍CMake的使用方法。

一、基础编译

1. 项目与文件结构

项目结构

1
2
3
4
5
6
.
├── build
├── CMakeLists.txt
├── main.c
├── testFunc.c
└── testFunc.h

main.c

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

int main(void)
{
func(100);
return 0;
}

testFunc.c

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

void func(int data)
{
printf("data is %d\n", data);
}

testFunc.h

1
2
3
4
5
6
#ifndef _TEST_FUNC_H_
#define _TEST_FUNC_H_

void func(int data);

#endif

CMakeLists.txt

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

2. 编译与运行

1
2
3
cd build && cmake ../
make -j8
./main

3. 思考与改进

  1. 源文件管理问题:如果在同一目录下有多个源文件,需要在add_executable里把所有源文件都添加进去,这不够优雅。

    • 解决方案:使用aux_source_directory(dir var)把指定目录下所有的源文件存储在一个变量中
    • 参数说明:第一个参数dir是指定目录,第二个参数var是用于存放源文件列表的变量名
  2. 头文件路径问题:当程序文件较多时,我们会进行分类管理,把代码根据功能放在不同的目录下。这时头文件定位需要使用完整路径,如:

    1
    2
    #include "xxxxxx/testFunc.h"
    #include "xxxxxx/testFunc1.h"
    • 解决方案:使用include_directories方法添加多个指定头文件的搜索路径,路径之间用空格分隔
  3. 源代码组织问题:源代码随意放置不够优雅

    • 解决方案:统一放入src文件夹后使用add_subdirectory(source_dir [binary_dir] [EXCLUDE_FROM_ALL])设置源代码目录
    • 参数说明:
      • source_dir:指定包含CMakeLists.txt和代码文件所在的目录
      • binary_dir:二进制代码目录,执行后的输出文件将会存放在此处
      • EXCLUDE_FROM_ALL:可选标记,表示新增加的子目录将会排除在ALL目录之外

二、进阶编译

1. 项目与文件结构

项目结构

1
2
3
4
5
6
7
8
9
10
11
12
.
├── bin
├── build
├── CMakeLists.txt
├── include1
| └── testFunc1.h
├── include2
| └── testFunc2.h
└── src
├── main.c
├── testFunc1.c
└── testFunc2.c

main.c

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

int main(void)
{
func(100);
return 0;
}

testFunc.c

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

void func(int data)
{
printf("data is %d\n", data);
}

testFunc.h

1
2
3
4
5
6
#ifndef _TEST_FUNC_H_
#define _TEST_FUNC_H_

void func(int data);

#endif

CMakeLists.txt

1
2
3
4
cmake_minimum_required (VERSION 2.8)
project (demo)
# 设置源代码目录
add_subdirectory (src)

这里指定src目录下存放了源文件,当执行cmake时,就会进入src目录下去找src目录下的CMakeLists.txt,所以在src目录下也建立一个CMakeLists.txt,内容如下:

src/CMakeLists.txt

1
2
3
4
5
6
7
8
# 将当前src/CMakeLists.txt同文件夹下所有文件添加进SRC_LIST变量
aux_source_directory (. SRC_LIST)
# 添加头文件目录
include_directories (../include1 ../include2)
# 指定生成目标
add_executable (main ${SRC_LIST})
# 设定目标二进制可执行文件的存放位置,其中PROJECT_SOURCE_DIR为工程根目录
set (EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)

2. 编译与运行

1
2
3
4
5
cd build
cmake ..
make -j8
cd ../bin
./main

这样Makefile会在build目录下生成,二进制程序会在bin目录下生成。

3. 思考与改进

可以只使用一个CMakeLists.txt,把最外层的CMakeLists.txt内容改成如下:

1
2
3
4
5
6
cmake_minimum_required (VERSION 2.8)
project (demo)
set (EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
aux_source_directory (src SRC_LIST)
include_directories (include1 include2)
add_executable (main ${SRC_LIST})

此时还需要把src目录下的CMakeLists.txt删除即可。

三、生成库文件

1. 项目结构

1
2
3
4
5
6
7
8
9
10
11
12
13
.
├── bin
├── build
├── CMakeLists.txt
├── include1
| └── testFunc1.h
├── include2
| └── testFunc2.h
├── lib
└── src
├── main.c
├── testFunc1.c
└── testFunc2.c

CMakeLists.txt

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
cmake_minimum_required (VERSION 3.5)
project (demo)
set (SRC_LIST1 ${PROJECT_SOURCE_DIR}/src/testFunc1.c)
set (SRC_LIST2 ${PROJECT_SOURCE_DIR}/src/testFunc2.c)
include_directories (../include1 ../include2)

# add_library: 生成动态库或静态库
# 第1个参数指定库的名字
# 第2个参数决定是动态还是静态,如果没有就默认静态
# 第3个参数指定生成库的源文件
add_library (testFunc_shared1 SHARED ${SRC_LIST1})
add_library (testFunc_shared2 SHARED ${SRC_LIST2})
add_library (testFunc_static1 STATIC ${SRC_LIST1})
add_library (testFunc_static2 STATIC ${SRC_LIST2})

# set_target_properties: 设置最终生成的库的名称
# 还有其它功能,如设置库的版本号等
set_target_properties (testFunc_shared1 PROPERTIES OUTPUT_NAME "testFunc1")
set_target_properties (testFunc_shared2 PROPERTIES OUTPUT_NAME "testFunc2")
set_target_properties (testFunc_static1 PROPERTIES OUTPUT_NAME "testFunc1")
set_target_properties (testFunc_static2 PROPERTIES OUTPUT_NAME "testFunc2")

# LIBRARY_OUTPUT_PATH: 库文件的默认输出路径
# 这里设置为工程目录下的lib目录
set (LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)

2. 运行查看编译好的库文件

1
2
3
4
5
cd build/
cmake ..
make
cd ../lib/
ls

最后将会在lib文件夹下生成libtestFunc1.alibtestFunc1.solibtestFunc2.alibtestFunc2.so

四、链接库文件

1. 项目结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
.
├── bin
├── build
├── CMakeLists.txt
├── include1
| └── testFunc1.h
├── include2
| └── testFunc2.h
├── lib
| ├── libtestFunc1.a
| ├── libtestFunc1.so
| ├── libtestFunc2.a
| └── libtestFunc2.so
└── src
├── main.c
├── testFunc1.c
└── testFunc2.c

main.c

1
2
3
4
5
6
7
#include <stdio.h>
#include "testFunc.h"
int main(void)
{
func(100);
return 0;
}

CMakeLists.txt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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)
include_directories (${PROJECT_SOURCE_DIR}/testFunc/inc)

# find_library: 在指定目录下查找指定库,并把库的绝对路径存放到变量里
# 第一个参数是变量名称
# 第二个参数是库名称
# 第三个参数是HINTS
# 第4个参数是路径
find_library(TESTFUNC_LIB testFunc HINTS ${PROJECT_SOURCE_DIR}/testFunc/lib)
add_executable (main ${SRC_LIST})

# target_link_libraries: 把目标文件与库文件进行链接
target_link_libraries (main ${TESTFUNC_LIB})

2. 编译与运行

1
2
3
4
5
cd build/
cmake ..
make
cd ../bin/
./main

总结

CMake是一个强大的跨平台构建工具,通过本文的学习,我们掌握了:

  1. 基础的CMake项目构建方法
  2. 如何组织和管理多文件项目
  3. 如何生成和使用静态/动态库
  4. 如何链接和使用外部库

通过合理使用CMake,我们可以大大简化C/C++项目的构建过程,提高开发效率。