前言
在 Windows上开发我使用最多的IDE还是 Visual Studio,编写、编译一条龙服务,导致了不少编译流程知识的缺失,这种大型的IDE确实好用,诸多配置通过在界面上勾选一下就可以了,但是在编译细节的掌握上还是漏掉了一些知识。
在 linux 开发环境下通常会使用 gcc 或者 g++ 进行编译,可是编译选项有点多,当工程非常大的时候需要写的编译参数太多了,这时可以使用make命令来帮助我们编译 C++ 程序,编译时依赖一些规则,这些规则就写在一个叫 Makefile 的文件中。
后来发现写 Makefile 还是太麻烦了,这个文件也相当大。于是“懒惰”的程序员们又开发出了各种各样的工具用来生成 Makefile 文件,我使用过的目前就只有 automake
和 cmake
。
生成Makefile
之前使用的生成 Makefile 文件的工具是 automake
,被称为是“八股文”一样的操作,每次操作都是固定的几个步骤,比如每次都要运行 autoscan
、aclocal
、autoconf
、automake
、./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 命令完成编译。
实现简单的代码文件
加法和减法都是常用的简单计算,用来举例子很容易理解,接下来展示要用到的几个文件内容,每个文件只有几行,只为了说明问题,文件内容如下:
//myadd.h
int add(int a, int b);
//myadd.cpp
#include "myadd.h"
int add(int a, int b) {
return a + b;
}
//mysub.h
int sub(int a, int b);
//mysub.cpp
#include "mysub.h"
int sub(int a, int b) {
return a - b;
}
//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++ 命令来编译这个样例程序:
- 查看目录下文件
albert@home-pc:testcmake$ ls
myadd.cpp myadd.h mysub.cpp mysub.h test.cpp
- 将
myadd.h
和myadd.cpp
编译成静态库libmyadd.a
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
- 将
mysub.h
和mysub.cpp
编译成静态库libmysub.so
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
- 编译链接静态库
libmyadd.a
、动态库libmysub.so
和测试文件生成可执行程序test
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
- 运行查看结果,成功计算表达式的值
albert@home-pc:testcmake$ ./test
happy birthday!
519 + 1 = 520
1320 - 6 = 1314
使用cmake方式
上面展示了最原始的编译方法,每次都要敲这些命令,接下来编写一个 CMakeLists 文件,使用 cmake 生成Makefile,以后只要运行 make 命令就可以完成编译了。
调整一下目录结构如下:
albert@home-pc:testcmake$ tree
.
|-- myadd
| |-- myadd.cpp
| `-- myadd.h
|-- mysub
| |-- mysub.cpp
| `-- mysub.h
`-- test.cpp
- 进入 myadd 目录新建 CMakeLists.txt 编写内容如下:
aux_source_directory(. SRC_LIST) #将此目录的源文件集合设置为变量SRC_LIST
add_library(myadd STATIC ${SRC_LIST}) #库的名称,库的类型,静态库的源文件列表
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib) #库的输出路径为根目录下的lib文件夹
- 进入 mysub 目录新建 CMakeLists.txt 编写内容如下:
aux_source_directory(. SRC_LIST) #将此目录的源文件集合设置为变量SRC_LIST
add_library(mysub SHARED ${SRC_LIST}) #库的名称,库的类型,动态库的源文件列表
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib) #库的输出路径为根目录下的lib文件夹
- 在工程主目录下新建 CMakeLists.txt 编写内容如下:
# 指定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)
- 新建build目录和lib目录,整个工程目录关系如下:
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
- 进入 build 目录下依次运行
cmake ..
和make
命令
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方式编译工程的例子就写完了。
总结
cmake
和automake
本身不提供编译功能,只是可以按照编写的 CMakeLists.txt 文件生成 Makefilemake
可以根据 Makefile 文件调用 gcc/g++ 命令对源代码进行编译工作-Wl,-rpath=.
这个选项可以指定可执行文件查找动态库的路径,感觉比export LD_LIBRARY_PATH
要方便一点-DGLIBCXX_USE_CXX11_ABI
这个宏可坑了我不少时间,编译使用libmysqlcppconn8的时候,如果不禁用会报编译错误
有你,真好~