学习cmake从成功编译一个小程序开始

前言

在 Windows上开发我使用最多的IDE还是 Visual Studio,编写、编译一条龙服务,导致了不少编译流程知识的缺失,这种大型的IDE确实好用,诸多配置通过在界面上勾选一下就可以了,但是在编译细节的掌握上还是漏掉了一些知识。

在 linux 开发环境下通常会使用 gcc 或者 g++ 进行编译,可是编译选项有点多,当工程非常大的时候需要写的编译参数太多了,这时可以使用make命令来帮助我们编译 C++ 程序,编译时依赖一些规则,这些规则就写在一个叫 Makefile 的文件中。

后来发现写 Makefile 还是太麻烦了,这个文件也相当大。于是“懒惰”的程序员们又开发出了各种各样的工具用来生成 Makefile 文件,我使用过的目前就只有 automakecmake

生成Makefile

之前使用的生成 Makefile 文件的工具是 automake,被称为是“八股文”一样的操作,每次操作都是固定的几个步骤,比如每次都要运行 autoscanaclocalautoconfautomake./confiugre等命令,需要个人发挥的地方并不多,之前使用的时候也不是完全从0开始一点点写的,往往是写一个项目模板之后,对照着在Makefile.am文件中修改几个参数就好了。

现在新的工作内容中使用 cmake 来生成 Makefile,这个 cmake 之前还确实接触过一些,大概是2012年的时候,那时在编译 OpenCV 库还有增强现实插件的时候用过几次,当时感觉安装起来太麻烦了,对那个红绿蓝的图标记忆犹新,感觉和当时的新闻联播的图标有些亲戚关系。

其实当时根本分不清什么是编译器,什么是 Makefile,对于各种库文件的编译完全是按照文档来操作,现在回过头来看看 cmake 生成 Makefile 还是比较简单的,最起码要比 automake 省了很多步骤,只要编写一个 CMakeLists.txt 文件就好了。

编写CMakeLists.txt生成Makefile

为了练习使用编写CMakeLists.txt生成Makefile,进而编译C++项目,我们可以从头来实现一个小例子,目标是编写一个计算加法的静态库和一个计算减法静态库,然后实现一个测试工程来使用这两个函数库,整个工程使用 cmake 来生成 Makefile,然后使用 make 命令完成编译。

实现简单的代码文件

加法和减法都是常用的简单计算,用来举例子很容易理解,接下来展示要用到的几个文件内容,每个文件只有几行,只为了说明问题,文件内容如下:

1
2
3
//myadd.h

int add(int a, int b);
1
2
3
4
5
6
7
//myadd.cpp

#include "myadd.h"

int add(int a, int b) {
return a + b;
}
1
2
3
//mysub.h

int sub(int a, int b);
1
2
3
4
5
6
7
//mysub.cpp

#include "mysub.h"

int sub(int a, int b) {
return a - b;
}
1
2
3
4
5
6
7
8
9
10
11
12
//test.cpp

#include "myadd.h"
#include "mysub.h"
#include <iostream>

int main() {
std::cout << "happy birthday!" << std::endl;
std::cout << "519 + 1 = " << add(519, 1) << std::endl;
std::cout << "1320 - 6 = " << sub(1320, 6) << std::endl;
return 0;
}

使用常规方法编译

首先使用最简单 g++ 命令来编译这个样例程序:

  1. 查看目录下文件
1
2
albert@home-pc:testcmake$ ls
myadd.cpp myadd.h mysub.cpp mysub.h test.cpp
  1. myadd.hmyadd.cpp 编译成静态库 libmyadd.a
1
2
3
4
5
albert@home-pc:testcmake$ g++ -c myadd.cpp
albert@home-pc:testcmake$ ar crv libmyadd.a myadd.o
a - myadd.o
albert@home-pc:testcmake$ ls
libmyadd.a myadd.cpp myadd.h myadd.o mysub.cpp mysub.h test.cpp
  1. mysub.hmysub.cpp 编译成静态库 libmysub.so
1
2
3
4
albert@home-pc:testcmake$ g++ -c mysub.cpp
albert@home-pc:testcmake$ g++ -shared -fPIC -o libmysub.so mysub.o
albert@home-pc:testcmake$ ls
libmyadd.a libmysub.so myadd.cpp myadd.h myadd.o mysub.cpp mysub.h mysub.o test.cpp
  1. 编译链接静态库 libmyadd.a、动态库 libmysub.so 和测试文件生成可执行程序 test
1
2
3
albert@home-pc:testcmake$ g++ test.cpp libmyadd.a -L. -lmysub -o test -Wl,-rpath=.
albert@home-pc:testcmake$ ls
libmyadd.a libmysub.so myadd.cpp myadd.h myadd.o mysub.cpp mysub.h mysub.o test test.cpp
  1. 运行查看结果,成功计算表达式的值
1
2
3
4
albert@home-pc:testcmake$ ./test
happy birthday!
519 + 1 = 520
1320 - 6 = 1314

使用cmake方式

上面展示了最原始的编译方法,每次都要敲这些命令,接下来编写一个 CMakeLists 文件,使用 cmake 生成Makefile,以后只要运行 make 命令就可以完成编译了。

调整一下目录结构如下:

1
2
3
4
5
6
7
8
9
albert@home-pc:testcmake$ tree
.
|-- myadd
| |-- myadd.cpp
| `-- myadd.h
|-- mysub
| |-- mysub.cpp
| `-- mysub.h
`-- test.cpp
  1. 进入 myadd 目录新建 CMakeLists.txt 编写内容如下:
1
2
3
4
aux_source_directory(. SRC_LIST)        #将此目录的源文件集合设置为变量SRC_LIST
add_library(myadd STATIC ${SRC_LIST}) #库的名称,库的类型,静态库的源文件列表

set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib) #库的输出路径为根目录下的lib文件夹
  1. 进入 mysub 目录新建 CMakeLists.txt 编写内容如下:
1
2
3
4
aux_source_directory(. SRC_LIST)        #将此目录的源文件集合设置为变量SRC_LIST
add_library(mysub SHARED ${SRC_LIST}) #库的名称,库的类型,动态库的源文件列表

set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib) #库的输出路径为根目录下的lib文件夹
  1. 在工程主目录下新建 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
26
27
28
29
30
31
32
33
34
# 指定cmake版本
cmake_minimum_required(VERSION 3.5)

# 指定项目的名称,一般和项目的文件夹名称对应
project(testcmake)

# 指定子目录
add_subdirectory(myadd)
add_subdirectory(mysub)

# 添加c++ 11标准支持
set(CMAKE_CXX_FLAGS "-std=c++11" )

# 特殊宏,之前编译mysqlcppconn8用到过
add_definitions(-DGLIBCXX_USE_CXX11_ABI)

# 头文件目录
include_directories(myadd mysub)

# 源文件目录
aux_source_directory(. DIR_SRCS)

# 设置环境变量,编译用到的源文件全部都要放到这
set(TEST_MATH ${DIR_SRCS})

# 库文件目录
link_directories(lib)

# 添加要编译的可执行文件
add_executable(${PROJECT_NAME} ${TEST_MATH})

# 添加可执行文件所需要的库
target_link_libraries(${PROJECT_NAME} myadd)
target_link_libraries(${PROJECT_NAME} mysub)
  1. 新建build目录和lib目录,整个工程目录关系如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
albert@home-pc:testcmake$ tree
.
|-- CMakeLists.txt
|-- build
|-- lib
|-- myadd
| |-- CMakeLists.txt
| |-- myadd.cpp
| `-- myadd.h
|-- mysub
| |-- CMakeLists.txt
| |-- mysub.cpp
| `-- mysub.h
`-- test.cpp

4 directories, 8 files
  1. 进入 build 目录下依次运行 cmake ..make 命令
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
26
27
28
29
30
31
32
33
34
35
36
albert@home-pc:testcmake/build$ cmake ..
-- The C compiler identification is GNU 5.4.0
-- The CXX compiler identification is GNU 5.4.0
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: testcmake/build
albert@home-pc:testcmake/build$ make
Scanning dependencies of target mysub
[ 16%] Building CXX object mysub/CMakeFiles/mysub.dir/mysub.cpp.o
[ 33%] Linking CXX shared library ../../lib/libmysub.so
[ 33%] Built target mysub
Scanning dependencies of target myadd
[ 50%] Building CXX object myadd/CMakeFiles/myadd.dir/myadd.cpp.o
[ 66%] Linking CXX static library ../../lib/libmyadd.a
[ 66%] Built target myadd
Scanning dependencies of target testcmake
[ 83%] Building CXX object CMakeFiles/testcmake.dir/test.cpp.o
[100%] Linking CXX executable testcmake
[100%] Built target testcmake
albert@home-pc:testcmake/build$ ./testcmake
happy birthday!
519 + 1 = 520
1320 - 6 = 1314
albert@home-pc:testcmake/build$

至此,使用cmake方式编译工程的例子就写完了。

总结

  1. cmakeautomake 本身不提供编译功能,只是可以按照编写的 CMakeLists.txt 文件生成 Makefile
  2. make 可以根据 Makefile 文件调用 gcc/g++ 命令对源代码进行编译工作
  3. -Wl,-rpath=. 这个选项可以指定可执行文件查找动态库的路径,感觉比 export LD_LIBRARY_PATH 要方便一点
  4. -DGLIBCXX_USE_CXX11_ABI 这个宏可坑了我不少时间,编译使用libmysqlcppconn8的时候,如果不禁用会报编译错误

==>> 反爬链接,请勿点击,原地爆炸,概不负责!<<==

有你,真好~

2020-11-15 23:55:35

Albert Shi wechat
欢迎您扫一扫上面的微信公众号,订阅我的博客