cmake学习笔记三以笔者的robosub竞赛为例
继笔者认真学习了CMake语法之后,便开始尝试自己用CMake将以前用Qt写的软件框架程序改编为CMake指令生成模式。现已成功,在此奉上一系列CMakeLists.txt的源码。
一. 前言
1. 比赛项目简要介绍
笔者曾经参加过美国海军作为主办方的竞赛,竞赛名称叫IAUVC(International Autonomous Underwater Vehicle Competiton),即国际水下无人航行器竞赛。笔者在2016年作为团队的图像及总控负责人,2017年作为团队的技术顾问。
由于2016年团队的控制系统仍有很大的改进空间,所以笔者就写了新的软件框架,主要思想基于多进程通信。
2. 本文思路
本文并不系统的解释语法,而是从根目录的CMakeLists.txt开始,按照指令执行流程进行讲解。
注:
关于语法的总结,笔者前面的文章《CMake学习笔记(二)——CMake语法》中,也对CMake语法做了较为系统的总结。
3. 文件列表
在该CMake项目下使用Linux的tree指令,得到如下的文件列表:
.
├── CMakeLists.txt
├── CustomizeFunctions
│ ├── CMakeLists.txt
│ ├── CustomizeStructs
│ │ └── imageprocessheader.h
│ ├── GeneralImageProcess
│ │ ├── CMakeLists.txt
│ │ ├── contours_fun.cpp
│ │ ├── contours_fun.h
│ │ ├── imageprocessing_fun.cpp
│ │ └── imageprocessing_fun.h
│ └── SupportFunctions
│ ├── CMakeLists.txt
│ ├── string_fun.cpp
│ ├── string_fun.h
│ ├── sys_fun.cpp
│ └── sys_fun.h
├── IPCClients
│ ├── CMakeLists.txt
│ ├── IPCImageClient
│ │ └── ncclient_image_main.cpp
│ ├── IPCSendClient
│ │ └── ncclient_send_main.cpp
│ └── IPCSurfClient
│ └── NCClient_Surf_Main.cpp
├── IPCServer
│ ├── CMakeLists.txt
│ └── main.cpp
└── NCFunctions
├── CMakeLists.txt
├── NCClient
│ ├── CMakeLists.txt
│ ├── ncclient.cpp
│ ├── ncclient.h
│ ├── ncclient_image.cpp
│ ├── ncclient_image.h
│ ├── ncclient_imagemacros.h
│ ├── ncclient_macros.h
│ ├── ncclient_send.cpp
│ ├── ncclient_send.h
│ ├── ncclient_surface.cpp
│ └── ncclient_surface.h
├── NCServer
│ ├── CMakeLists.txt
│ ├── ncserver_dataproc.cpp
│ ├── ncserver.h
│ ├── ncserver_link.cpp
│ ├── ncserver_macros.h
│ ├── ncserver_record.cpp
│ ├── ncserver_stage.cpp
│ └── ncserver_strategy.cpp
└── NCStage
├── Basic
│ ├── ncstage_basic.cpp
│ ├── ncstage_basic.h
│ ├── ncstage.cpp
│ ├── ncstage.h
│ └── ncstage_macro.h
├── CMakeLists.txt
├── ncstage_test.cpp
└── ncstage_test.h
该文件列表中,下列路径存在CMakeLists.txt:
- IPCSocket,根目录
- CustomizeFunctions,自定义底层函数源码
- NCFunctions,针对笔者一系列类与函数的源码
- NCClient,客户端及其派生类源码
- NCServer,服务器端源码
- NCStage,任务阶段的类实现源码
- IPCClients,不同客户端二进制文件生成源码
- IPCServer,服务器端文件生成源码
笔者编译生成整个项目,是在根目录IPCSocket下创造文件夹build,在build文件夹内执行cmake与make指令而生成的。指令如下:
mkdir build
cd ./build
cmake ../
make
GitHub源码地址:
https://github.com/upcAutoLang/Framework-for-NACIT2017
下面笔者将按照整个cmake的生成流程逐步分析讲解。存在重复的指令时,笔者会偷懒略过。
二. CMakeLists.txt详解
下面笔者讲从根目录向下逐层讲解:
1. 根目录/CMakeLists.txt
此处根目录为IPCSocket。该目录下的CMakeLists.txt如下:
# CMake最低版本要求
CMAKE_MINIMUM_REQUIRED(VERSION 2.8)
# CMake项目名称
PROJECT(IPCSocket)
# 设置二进制目标文件生成路径
SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)
# 设置库文件生成路径
SET(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)
# 添加四个CMake子目录
ADD_SUBDIRECTORY(./CustomizeFunctions)
ADD_SUBDIRECTORY(./NCFunctions)
ADD_SUBDIRECTORY(./IPCClients)
ADD_SUBDIRECTORY(./IPCServer)
此段代码下,定义了项目名称IPCSocket后,CMake便自动生成了两个变量:
// 二进制文件保存路径;
PROJECT_BINARY_DIR = IPCSocket_BINARY_DIR
// 源代码路径;
SOURCE_DIR = IPCSocket_BINARY_DIR
随后又定义了二进制目标文件与库文件的生成路径。此处两行SET代码是指将这两个路径设置为PROJECT_BINARY_DIR(即执行cmake指令的路径)下的bin, lib路径中。
后面的紧接的四个ADD_SUBDIRECTORY指令,是指CMake指令顺序进入四个路径中,顺序执行几个路径中的CMakeLists.txt文件。
这里笔者认为可以将其理解成C++的四个函数。四个函数顺序执行,按先后顺序依次处理./CustomizeFunctions ./NCFunctions ./IPCClients ./IPCServer
中的CMakeLists.txt文件。如果这些CMakeLists.txt文件中也存在ADD_SUBDIRECTORY指令也同理。
下面按照ADD_SUBDIRECTORY的顺序进行说明。
2. /CustomizeFunctions/CMakeLists.txt
/CustomizeFunctions文件夹中存储了笔者的自定义函数,笔者此处想要将其封装为库文件。
/CustomizeFunctions/CMakeLists.txt文件内容如下所示:
# CMake最低版本要求
CMAKE_MINIMUM_REQUIRED(VERSION 2.8)
# 添加两个CMake子目录
ADD_SUBDIRECTORY(./GeneralImageProcess)
ADD_SUBDIRECTORY(./SupportFunctions)
即进入./GeneralImageProcess, ./SupportFunctions继续寻找CMakeLists.txt。
(1) /CustomizeFunctions/GeneralImageProcess/CMakeLists.txt
GeneralImageProcess文件夹中存储了笔者按照自己的使用习惯而对OpenCV做的自定义函数封装,笔者准备将其封装成库文件。
主要被定义为两部分内容:
- contours_fun:笔者自定义的基于轮廓处理的函数;
- imageprocessing_fun:笔者自定义的图像预处理函数;
文件内容如下:
# CMake最低版本要求
CMAKE_MINIMUM_REQUIRED(VERSION 2.8)
# 系统中寻找OpenCV的安装路径
FIND_PACKAGE(OpenCV REQUIRED)
# 添加源文件中的#include路径
INCLUDE_DIRECTORIES(./)
INCLUDE_DIRECTORIES(../)
INCLUDE_DIRECTORIES(../CustomizeStructs)
# 设置路径变量
SET(LIB_CONTOUR_SOURCE
../CustomizeStructs/imageprocessheader.h
./contours_fun.h
./contours_fun.cpp)
SET(LIB_IMAGEPROCESS_SOURCE
../CustomizeStructs/imageprocessheader.h
./imageprocessing_fun.h
./imageprocessing_fun.cpp)
# 生成库文件
ADD_LIBRARY(contours_fun SHARED ${LIB_CONTOUR_SOURCE})
ADD_LIBRARY(imageprocessing_fun SHARED ${LIB_IMAGEPROCESS_SOURCE})
其中,FIND_PACKAGE(OpenCV REQUIRED)在系统中寻找OpenCV的安装路径。
平常的软件,CMake的FIND_PACKAGE路径是/usr/share/cmake-2.8/Modules
。
但对于OpenCV的FIND_PACKAGE指令,寻找路径是/usr/local/share/OpenCV/OpenCVConfig.cmake
。其中, /usr/local/share/OpenCV/
是笔者在使用源码编译安装OpenCV时设置的安装地址OpenCV_INSTALL_DIR。
关于FIND_PACKAGE的工作原理,请参考地址:
http://blog.csdn.net/bytxl/article/details/50637277
两条SET指令设置了两个路径,在ADD_LIBRARY指令中使用。
指令ADD_LIBRARY(contours_fun SHARED ${LIB_CONTOUR_SOURCE})
中,有三个部分如下:
- contours_fun:生成的库文件基准名称
- 此处笔者生成的是共享库,所以加上前缀lib,后缀.so,完整的库文件名称应该为libcontours_fun.so
- SHARED:生成库的属性为共享库;此处若为STATIC则为静态库
- ${LIB_CONTOUR_SOURCE}:此处为生成库文件的源码路径,该变量在前面已经被定义。
所以该指令便依赖变量LIB_CONTOUR_SOURCE中的路径,生成了libcontours_fun.so库文件。
另外一条ADD_LIBRARY指令同理。
(2) /CustomizeFunctions/SupportFunctions/CMakeLists.txt
CustomizeFunctions/SupportFunctions路径中,保存了笔者按照操作习惯而定义的支持函数。主要有两部分:
- string_fun:自定义字符串操作函数;
- sys_fun:自定义系统操作函数;
CMakeLists.txt文件内容如下:
# CMake最低版本要求
CMAKE_MINIMUM_REQUIRED(VERSION 2.8)
# 添加源文件中的#include路径
INCLUDE_DIRECTORIES(./)
INCLUDE_DIRECTORIES(../)
# 设置路径变量
SET(LIB_STRING_SOURCE
./string_fun.h
./string_fun.cpp)
SET(LIB_SYS_SOURCE
./string_fun.h
./string_fun.cpp
./sys_fun.h
./sys_fun.cpp)
# 生成库文件
ADD_LIBRARY(string_fun SHARED ${LIB_STRING_SOURCE})
ADD_LIBRARY(sys_fun SHARED ${LIB_SYS_SOURCE})
原理同2.(1),此处不再赘述。
3. /NCFunctions/CMakeLists.txt
/NCFunctions文件夹中,存放了专为笔者所属团队而写类的源码。CMakeLists.txt文件如下:
CMAKE_MINIMUM_REQUIRED(VERSION 2.8)
ADD_SUBDIRECTORY(./NCStage)
ADD_SUBDIRECTORY(./NCClient)
ADD_SUBDIRECTORY(./NCServer)
进入./NCServer ./NCClient ./NCStage
继续寻找CMakeLists.txt文件。
(1) /NCFunctions/NCStage/CMakeLists.txt
/NCFunctions/NCStage文件夹中存放的是笔者自定义任务阶段类的源码。主要有三部分:
- ncstage:任务阶段类的基类源码;
- ncstage_basic:由ncstage派生出的基础阶段类的源码;
- ncstage_test:用于调试的由ncstage派生出的阶段类的源码;
CMakeLists.txt文件如下:
# CMake最低版本要求
CMAKE_MINIMUM_REQUIRED(VERSION 2.8)
# 添加源文件中的#include路径
INCLUDE_DIRECTORIES(./)
INCLUDE_DIRECTORIES(./Basic)
# 设置路径变量
SET(LIB_STAGE_SOURCE
./Basic/ncstage.cpp)
SET(LIB_STAGEBASIC_SOURCE
${LIB_STAGE_SOURCE}
./Basic/ncstage_basic.cpp)
SET(LIB_STAGETEST_SOURCE
${LIB_STAGE_SOURCE}
./ncstage_test.cpp)
# 生成库文件
ADD_LIBRARY(ncstage SHARED ${LIB_STAGE_SOURCE})
ADD_LIBRARY(ncstage_basic SHARED ${LIB_STAGEBASIC_SOURCE})
ADD_LIBRARY(ncstage_test SHARED ${LIB_STAGETEST_SOURCE})
原理同2.(1),此处不再赘述。
(2) /NCFunctions/NCClient/CMakeLists.txt
笔者整个工程的框架,采用的是由socket实现进程间的通讯。/NCFunctions/NCClient文件夹中存放的是各个客户端的类源码。主要有四部分:
- ncclient:客户端基类源码;
- ncclient_image:图像客户端源码,用于图像信息处理,派生于客户端基类;
- ncclient_send:串口通讯客户端源码,用于与下位机传递信息,派生于客户端基类;
- ncclient_surface:界面客户端源码,用于服务器与其他客户端之间的通讯,派生于客户端基类;
/NCFunctions/NCClient/CMakeLists.txt内容如下:
# CMake最低版本要求
CMAKE_MINIMUM_REQUIRED(VERSION 2.8)
# 系统中寻找OpenCV的安装路径
FIND_PACKAGE(OpenCV REQUIRED)
# 添加源文件中的#include路径
INCLUDE_DIRECTORIES(./)
INCLUDE_DIRECTORIES(../)
INCLUDE_DIRECTORIES(../../CustomizeFunctions/SupportFunctions)
INCLUDE_DIRECTORIES(../../CustomizeFunctions/CustomizeStructs)
INCLUDE_DIRECTORIES(../../CustomizeFunctions/GeneralImageProcess)
INCLUDE_DIRECTORIES(${OpenCV_INCLUDE_DIRS})
# 添加依赖库路径
LINK_DIRECTORIES(${PROJECT_BINARY_DIR}/lib)
# 设置路径变量
SET(LIB_CLIENT_SOURCE
./ncclient.cpp
../../CustomizeFunctions/SupportFunctions/string_fun.cpp)
SET(LIB_IMAGE_SOURCE
${LIB_CLIENT_SOURCE}
./ncclient_image.cpp)
SET(LIB_SEND_SOURCE
${LIB_CLIENT_SOURCE}
./ncclient_send.cpp)
SET(LIB_SURF_SOURCE
${LIB_CLIENT_SOURCE}
./ncclient_surface.cpp)
# 生成库文件
ADD_LIBRARY(ncclient SHARED ${LIB_CLIENT_SOURCE})
ADD_LIBRARY(ncclient_image SHARED ${LIB_IMAGE_SOURCE})
# 连接库文件,以这些库文件为基础生成目标文件
TARGET_LINK_LIBRARIES(ncclient_image
${OpenCV_LIBS}
imageprocessing_fun)
ADD_LIBRARY(ncclient_send SHARED ${LIB_SEND_SOURCE})
ADD_LIBRARY(ncclient_surf SHARED ${LIB_SURF_SOURCE})
这里值得提的是LINK_DIRECTORIES指令与TARGET_LINK_LIBRARIES指令的运用。
生成图像客户端目标文件,是基于之前自定义函数部分中的imageprocessing_fun
的图像处理函数的,而之前这些函数已经被处理为库文件,被存储在工程生成路径下的lib路径中,所以指令LINK_DIRECTORIES(${PROJECT_BINARY_DIR}/lib)
就是将库文件路径包含在工程中,以便后面库文件的链接。
后面TARGET_LINK_LIBRARIES
命令便通过链接先前的库文件来生成目标文件,该条命令主要有几部分组成:
- ncclient_image:生成的目标文件名称
- 由于先前存在指令
ADD_LIBRARY(ncclient_image SHARED ${LIB_IMAGE_SOURCE})
,所以此处生成的目标文件是共享库文件;
- 由于先前存在指令
- ${OpenCV_LIBS}:依赖库文件列表
- 此处的变量${OpenCV_LIBS}是在通过编译安装OpenCV源码后,系统中便已经存在了该变量。该变量是在OpenCV安装地址下的OpenCVConfig.cmake(笔者的路径是
/usr/local/share/OpenCV/OpenCVConfig.cmake
)中通过遍历而得到的。该变量获取的过程大致如下图所示:
- 此处的变量${OpenCV_LIBS}是在通过编译安装OpenCV源码后,系统中便已经存在了该变量。该变量是在OpenCV安装地址下的OpenCVConfig.cmake(笔者的路径是
- imageprocessing_fun:依赖库文件
- 该库文件是由前面2.(1)中
/CustomizeFunctions/GeneralImageProcess/CMakeLists.txt
文件生成的;
- 该库文件是由前面2.(1)中
注:TARGET_LINK_LIBRARIES指令一定要在ADD_LIBRARY指令之后。
其余部分在2.(1)中都有讲解,此处不再赘述。
(3) /NCFunctions/NCServer/CMakeLists.txt
前面是客户端类代码,此处的/NCFunctions/NCServer文件夹中存放的便是服务器端的类源码。服务器类只有一个,但依照完成不同功能的模块,被笔者分为五部分:
- ncserver_link:服务器端socket网络通信部分的类函数源码
- ncserver_dataproc:服务器端解算获得数据部分的类函数源码
- ncserver_stage:服务器端判断当前任务进行状态的类函数源码
- ncserver_strategy:服务器端依据当前各传感器数据与当前任务进行状态,判断下一步策略的类函数源码
- ncserver_record:服务器端记录信息的类函数源码
/NCFunctions/NCServer/CMakeLists.txt内容如下:
# CMake最低版本要求
CMAKE_MINIMUM_REQUIRED(VERSION 2.8)
# 添加源文件中的#include路径
INCLUDE_DIRECTORIES(./)
INCLUDE_DIRECTORIES(../)
INCLUDE_DIRECTORIES(../NCClient)
INCLUDE_DIRECTORIES(../NCStage)
INCLUDE_DIRECTORIES(../NCStage/Basic)
INCLUDE_DIRECTORIES(../../CustomizeFunctions/SupportFunctions)
INCLUDE_DIRECTORIES(../../CustomizeFunctions/CustomizeStructs)
INCLUDE_DIRECTORIES(../../CustomizeFunctions/GeneralImageProcess)
# 查找某个路径下的所有源文件,并将源文件列表存储到某个变量中
AUX_SOURCE_DIRECTORY(./ LIB_SERVER_SOURCE)
SET(LIB_SERVER_SOURCE
${LIB_SERVER_SOURCE}
../NCClient/ncclient_macros.h
../NCStage/Basic/ncstage.cpp
../../CustomizeFunctions/SupportFunctions/string_fun.cpp
../../CustomizeFunctions/SupportFunctions/sys_fun.cpp)
# 生成库文件
ADD_LIBRARY(ncserver SHARED ${LIB_SERVER_SOURCE})
该部分源码中存在AUX_SOURCE_DIRECTORY指令,有两个参数:
- ./:查找的路径
- LIB_SERVER_SOURCE:将上述路径中的源文件列表存入该变量
经过这条指令,可以查找当前CMakeLists文件所在路径下的所有源文件,并将整个源文件列表存入变量LIB_SERVER_SOURCE中。
其余部分在前文已经叙述,此处不再赘述。
经过前面的处理,${PROJECT_BINARY_DIR}/lib下的库文件已经生成完毕。生成文件如图所示:
4. /IPCClients/CMakeLists.txt
/IPCClients文件夹中,存放了所有客户端的实现源码。CMakeLists.txt文件如下:
# CMake最低版本要求
CMAKE_MINIMUM_REQUIRED(VERSION 2.8)
# 系统中寻找OpenCV的安装路径
FIND_PACKAGE(OpenCV REQUIRED)
# 添加依赖库路径
LINK_DIRECTORIES(${PROJECT_BINARY_DIR}/lib)
# 添加源文件中的#include路径
INCLUDE_DIRECTORIES(./)
INCLUDE_DIRECTORIES(./IPCImageClient)
INCLUDE_DIRECTORIES(./IPCSendClient)
INCLUDE_DIRECTORIES(./IPCSurfClient)
INCLUDE_DIRECTORIES(../NCFunctions)
INCLUDE_DIRECTORIES(../NCFunctions/NCClient)
INCLUDE_DIRECTORIES(../CustomizeFunctions)
INCLUDE_DIRECTORIES(../CustomizeFunctions/CustomizeStructs)
INCLUDE_DIRECTORIES(../CustomizeFunctions/GeneralImageProcess)
INCLUDE_DIRECTORIES(../CustomizeFunctions/SupportFunctions)
# 设置路径变量
SET(LIBS_CLIENT
ncclient)
SET(LIBS_IMAGE
${LIBS_CLIENT}
ncclient_image)
SET(LIBS_SEND
${LIBS_CLIENT}
ncclient_send)
SET(LIBS_SURF
${LIBS_CLIENT}
ncclient_surf)
# 生成二进制可执行文件
ADD_EXECUTABLE(IPCImageClient
./IPCImageClient/ncclient_image_main.cpp)
# 链接库文件,以这些库文件为基础生成目标文件
TARGET_LINK_LIBRARIES(IPCImageClient
${LIBS_IMAGE})
ADD_EXECUTABLE(IPCSendClient
./IPCSendClient/ncclient_send_main.cpp)
TARGET_LINK_LIBRARIES(IPCSendClient
${LIBS_SEND})
ADD_EXECUTABLE(IPCSurfClient
./IPCSurfClient/NCClient_Surf_Main.cpp)
TARGET_LINK_LIBRARIES(IPCSurfClient
${LIBS_SURF})
其中命令ADD_EXECUTABLE用于生成目标二进制可执行文件,有两个参数如下:
- IPCImageClient:生成目标二进制可执行文件名称
- ./IPCImageClient/ncclient_image_main.cpp:生成该可执行文件所依赖的源码
随后的TARGET_LINK_LIBRARIES指令,以指定的库文件为基础,生成目标文件。有两个参数如下:
- IPCImageClient:先前在ADD_EXECUTABLE中指定的目标二进制可执行文件名称
- ${LIBS_IMAGE}:依赖库文件列表
综上,指令ADD_EXECUTABLE(IPCImageClient ./IPCImageClient/ncclient_image_main.cpp)
便以源码文件ncclient_image_main.cpp,以及变量LIB_IMAGE中包含的库文件为基础,生成了目标可执行文件IPCImageClient。
其余的两条ADD_EXECUTABLE, TARGET_LINK_LIBRARIES指令同理,不再赘述。
该部分CMakeLists.txt在${PROJECT_BINARY_DIR}/bin路径中,生成了所有客户端的可执行文件,如下图所示:
4. /IPCServer/CMakeLists.txt
/IPCServer文件夹中,存放了服务器端的实现源码。CMakeLists.txt文件如下:
# CMake最低版本要求
CMAKE_MINIMUM_REQUIRED(VERSION 2.8)
# 添加依赖库路径
LINK_DIRECTORIES(${PROJECT_BINARY_DIR}/lib)
# 添加源文件中的#include路径
INCLUDE_DIRECTORIES(./)
INCLUDE_DIRECTORIES(../NCFunctions)
INCLUDE_DIRECTORIES(../NCFunctions/NCServer)
INCLUDE_DIRECTORIES(../NCFunctions/NCClient)
INCLUDE_DIRECTORIES(../NCFunctions/NCStage)
INCLUDE_DIRECTORIES(../NCFunctions/NCStage/Basic)
INCLUDE_DIRECTORIES(../CustomizeFunctions)
INCLUDE_DIRECTORIES(../CustomizeFunctions/CustomizeStructs)
INCLUDE_DIRECTORIES(../CustomizeFunctions/GeneralImageProcess)
INCLUDE_DIRECTORIES(../CustomizeFunctions/SupportFunctions)
# 设置路径变量
SET(LIBS_SERVER
ncserver
ncstage
ncstage_basic
ncstage_test
)
# 生成二进制可执行文件
ADD_EXECUTABLE(IPCServer
./main.cpp)
# 链接库文件,以这些库文件为基础生成目标文件
TARGET_LINK_LIBRARIES(IPCServer
${LIBS_SERVER})
该部分源码原理在2.(1), 4中都有讲解,此处不再赘述。
该部分CMakeLists.txt在${PROJECT_BINARY_DIR}/bin路径中,生成了服务器端的可执行文件,如下图所示:
至此,整个cmake与make流程全部结束。
后记:
该工程项目框架的源码,笔者已经将源码和文档整理并放在GitHub上,链接见前文。届时望各位大神指教~