2.2 Makefile 与 cmake
# 2.2.1 Makefile 与 cmake
在 Windows 上编译和调试 C/C++ 程序我们可以使用 Visual Studio,在 Linux 机器上编译 C/C++ 程序最终使用的是 gcc/g++,当然调试使用 gdb,我们使用 makefile 文件组织大型 C/C++ 或者含有多个 C/C++ 文件的项目,本节不会介绍 makefile 的语法,因为网络上已经有太多的这方面的资料了。有人认为 makefile 仍然不太方便,于是发明了 cmake 工具,cmake 工具将含有 cmake 指令的文件生成 makefile 文件,含有 cmake 指令的文件一般文件名是 CMakeLists.txt。这是实际开发中大多数 C/C++ 项目的组织方式,也适用于大多数开源 C/C++ 的项目。
对于大多数 Linux 下的 C/C++ 开源项目,一般执行 configure 命令后会生成 CMakeLists.txt 文件,接着执行 cmake 指令会生成 makefile 文件,之后执行 make 命令利用 gcc/g++ 对项目进行编译。
对于 Windows 系统可以直接从 cmake 官网 https://cmake.org/ 下载相应的安装包进行安装即可,对于 Linux 系统,以 CentOS 为例,执行如下命令即可安装 cmake:
yum install cmake
安装好 cmake 工具之后,我们需要编写 CMakeList.txt 文件, CMakeLists.txt 样例如下:
样例一
cmake_minimum_required(VERSION 2.6)
project (FLAMGINGO_SERVER)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x -g -Wall -O0 -Wno-unused-variable -pthread")
link_directories(
${PROJECT_SOURCE_DIR}/lib
/usr/lib64/mysql/
)
set(net_srcs
base/AsyncLog.cpp
base/ConfigFileReader.cpp
base/Platform.cpp
base/Timestamp.cpp
net/Acceptor.cpp
net/ByteBuffer.cpp
net/Channel.cpp
net/Connector.cpp
net/EpollPoller.cpp
net/EventLoop.cpp
net/EventLoopThread.cpp
net/EventLoopThreadPool.cpp
net/InetAddress.cpp
net/Poller.cpp
net/PollPoller.cpp
net/ProtocolStream.cpp
net/SelectPoller.cpp
net/Sockets.cpp
net/TcpClient.cpp
net/TcpConnection.cpp
net/TcpServer.cpp
net/Timer.cpp
net/TimerQueue.cpp
)
set(mysqlapi_srcs
mysqlapi/DatabaseMysql.cpp
mysqlapi/Field.cpp
mysqlapi/QueryResult.cpp
)
set(mysqlmgr_srcs
mysqlmgr/MysqlManager.cpp
mysqlmgr/MysqlThrd.cpp
mysqlmgr/MysqlThrdMgr.cpp
mysqlmgr/TaskList.cpp
)
set(json_srcs
jsoncpp1.9.0/json_reader.cpp
jsoncpp1.9.0/json_value.cpp
jsoncpp1.9.0/json_writer.cpp
)
set(zlib_srcs
zlib1.2.11/zutil.c
zlib1.2.11/uncompr.c
zlib1.2.11/trees.c
zlib1.2.11/inftrees.c
zlib1.2.11/inflate.c
zlib1.2.11/inffast.c
zlib1.2.11/infback.c
zlib1.2.11/gzwrite.c
zlib1.2.11/gzread.c
zlib1.2.11/gzlib.c
zlib1.2.11/gzclose.c
zlib1.2.11/deflate.c
zlib1.2.11/crc32.c
zlib1.2.11/compress.c
zlib1.2.11/adler32.c
zlib1.2.11/ZlibUtil.cpp
)
set(utils_srcs
utils/StringUtil.cpp
utils/URLEncodeUtil.cpp
utils/MD5.cpp
utils/DaemonRun.cpp
)
set(chatserver_srcs
chatserversrc/main.cpp
chatserversrc/ChatServer.cpp
chatserversrc/ChatSession.cpp
chatserversrc/UserManager.cpp
chatserversrc/MsgCacheManager.cpp
chatserversrc/TcpSession.cpp
chatserversrc/MonitorSession.cpp
chatserversrc/MonitorServer.cpp
chatserversrc/HttpSession.cpp
chatserversrc/HttpServer.cpp
chatserversrc/BussinessLogic.cpp)
set(fileserver_srcs
fileserversrc/main.cpp
fileserversrc/FileServer.cpp
fileserversrc/FileSession.cpp
fileserversrc/FileManager.cpp
fileserversrc/TcpSession.cpp)
set(imgserver_srcs
imgserversrc/main.cpp
fileserversrc/FileServer.cpp
fileserversrc/FileSession.cpp
fileserversrc/FileManager.cpp
fileserversrc/TcpSession.cpp)
add_executable(chatserver ${net_srcs} ${json_srcs} ${chatserver_srcs} ${mysqlapi_srcs} ${mysqlmgr_srcs} ${zlib_srcs} ${utils_srcs})
#光包含库目录是没用的,还必须使用TARGET_LINK_LIBRARIES链接该库
TARGET_LINK_LIBRARIES(chatserver mysqlclient)
add_executable(fileserver ${net_srcs} ${fileserver_srcs} ${utils_srcs})
TARGET_LINK_LIBRARIES(fileserver)
add_executable(imgserver ${net_srcs} ${imgserver_srcs} ${utils_srcs})
TARGET_LINK_LIBRARIES(imgserver)
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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
样例二
PROJECT(TRADE)
AUX_SOURCE_DIRECTORY(./ SRC_LIST)
SET(EXECUTABLE_OUTPUT_PATH ../bin)
ADD_DEFINITIONS(-g -O0 -W -Wall -D_REENTRANT -D_FILE_OFFSET_BITS=64 -DAC_HAS_INFO -DAC_HAS_WARNING -DAC_HAS_ERROR -DAC_HAS_CRITICAL -DTIXML_USE_STL -DHAVE_CXX_STDHEADERS -Wno-deprecated ${CMAKE_CXX_FLAGS})
INCLUDE_DIRECTORIES(
./
/usr/local/include/commonlib/dbapi
/usr/local/include/commonlib/json2
/usr/local/include/commonlib/mysql
)
LINK_DIRECTORIES(
./
/usr/local/lib/commonlib
)
ADD_EXECUTABLE(trade ${SRC_LIST})
TARGET_LINK_LIBRARIES(trade pthread netutil json2 mysqlclient dbapi )
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
首行 cmake_minimum_required 指令指定支持该 CMakeLists.txt 文件的 cmake 最低版本号;
project 指令指定该项目的名称,注意项目名称不是最终生成的二进制程序名,一个项目下面可以生成多个二进制程序名;
set 定义和设置各种变量,set 括号后第一个名称是定义的变量名称,其后是变量的值,如上述文件定义了 CMAKE_CXX_FLAGS、net_srcs、mysqlapi_src、mysqlmgr_srcs 、json_srcs、zlib_srcs、utils_srcs、chatserver_srcs、fileserver_srcs、imgserver_srcs 一共 10 个变量,之后引用这些变量可以使用 ${变量名} 来引用这些变量,这些变量可以是内置变量,如 CMAKE_CXX_FLAGS 指定 g++ 编译选项,EXECUTABLE_OUTPUT_PATH 指定输出的二进制文件路径,也可以是自定义变量,如 chatserver_srcs、fileserver_srcs 等;
cmake 使用 aux_source_directory 指令指定源码目录,使用 include_directories 指令指定包含目录,使用
link_directories 指定 lib 目录;
cmake 使用 指令指定生成的动态或静态库的名称,其格式如下:
add_library(libname [SHARED|STATIC|MODULE] [EXCLUDE_FROM_ALL] source1 source2 … sourceN)
1例如:
add_library(hello hello1.cpp hello2.cpp)
1你不需要写全 libhello.so 或 libhello.a,只需要填写 hello 即可,cmake 系统会自动为你生成 libhello.X。类型有三种:
- SHARED,动态库(扩展名为 .so)
- STATIC,静态库(扩展名为 .a)
- MODULE,在使用 dyld 的系统有效,如果不支持dyld,则被当作 SHARED 对待。
EXCLUDE_FROM_ALL 参数的意思是这个库不会被默认构建,除非有其他的组件依赖或者手工构建。 下面命令会生成一个叫 libkafkawrapper.so 的文件,且 libkafkawrapper.so 的生成依赖 librdkafka.so、librdkafka++.so、libcrypto.so、libssl.so 四个库。
add_library(kafkawrapper SHARED ${kafka_wrapper_srcs}) TARGET_LINK_LIBRARIES(kafkawrapper rdkafka rdkafka++ crypto ssl)
1
2TARGET_LINK_LIBRARIES 指定生成的二进制文件以来的其他库,上文已有介绍;
编写完 CMakeLists.txt 文件后,进入 CMakeLists.txt 文件依次执行如下命令即可生成最终的二进制文件:
# 利用cmake生成makefile
cmake .
# 执行make命令,利用gcc/g++编译生成最终的二进制文件
make
2
3
4
CMakeLists.txt 也支持递归编译,父目录的 CMakeLists 先执行再接着执行子目录的 CMakeLists.txt 文件。
cmake 更多的信息可以参考 cmake 官网。
# 2.2.2 利用 cmake 工具生成 Visual Studio 工程文件
对于习惯了 Visual Studio 强大的管理项目、编码和调试功能的读者来说,在 Linux 下使用 gcc/g++ 编译、使用 gdb 调试是一件何其痛苦的事情,对于大多数的开源 C/C++ 项目,如果我们不在意 Windows 和 Linux 在一些底层 API 接口上的使用差别,想熟悉该项目的执行脉络和原理,在 Windows 上使用 Visual Studio 调试该项目也未尝不可。凡是可以使用 CMake 工具编译的 Linux 程序(即提供了 CMakeLists.txt 文件),我们同样也可以利用 CMake 工具生成 Windows 上的 Visual Studio 工程文件。
这里我们以著名的开源网络库 libuv 为例。
从 libuv 的官方地址提供的下载链接:https://dist.libuv.org/dist/ 下载最新的 libuv 的源码得到文件 libuv-v1.31.0.tar.gz(笔者写作此书时,libuv 最新版本是 1.31.0),解压该文件。作者的机器上我将代码解压至 **F:\mycode\libuv-v1.31.0\ **,解压后的目录中确实存在一个 CMakeLists.txt 文件,如下图所示:
启动 Windows 上的 CMake 图形化工具(cmake-gui),按下图进行设置:
设置完成之后,点击界面上的Configure 按钮,会提示 vsprojects 目录不存在,提示是否创建,我们点击 Yes 进行创建。
如果您的机器上安装了多个版本的Visual Studio,接下来会弹窗对话框让我们选择要生成的工程文件对应的 Visual Studio 版本号。读者可以根据自己的实际情况按需选择。我这里选择 Visual Studio 2019。
点击 Finish 按钮后开始启动 CMake 的检测和配置工作。等待一会儿,CMake 底部的输出框中提示 “Configuring Done” 表示配置工作已经完成。
接下来点击 Generate 按钮即可生成所选版本的 Visual Studio 工程文件,生成的文件位于 vsprojects 目录。
我们可以在界面上点击按钮 Open Project 按钮直接打开工程文件,也可以找到对应目录下的 libuv.sln 打开。
打开后如下图所示:
接下来,我们就可以使用 Visual Studio 愉快地进行编译和调试了。
让我们再深入聊一下上述过程:在点击 Configure 按钮之后,和在 Linux 下执行 cmake 命令一样,CMake 工具也是在检测所在的系统环境是否匹配 CMakeLists.txt 中定义的各种环境,本质上是生成了一份可以在 Windows 上编译和运行的代码(也就是说该源码支持在 Windows 上运行) 。因此,对于很多虽然提供了 CMakeLists.txt 文件但并不支持在 Windows 上运行的的 Linux 工程,虽然利用上述方法也能最终生成 Visual Studio 工程文件,但是这些文件并不能在 Windows 上直接无错编译和调试。
由于不同的 CMake 版本支持的 CMakeLists.txt 中的语法可能略有细微差别,有些 CMakeLists.txt 文件在使用上述方法 configure 时可能会产生一些错误,需要读者做些修改才能通过。