我藏在人群中 然后失去晴空
像我的名字 从你的记忆清空。 —《你头顶的风》
本文仅仅介绍了一些常用函数的参数的简单写法以及个人理解,如果你想了解更多,可以去查找专门介绍该函数的博客。本文只能保证你读完之后对各个函数有基本的认识,以及最基本的应用。
个人认为,想要提高自己写cmakelist
能力的最好方法有两个。其一者,自己编写更加复杂的工程文件。其二者,阅读、修改现有库的cmakelist
。有的库的cmakelist
有上百行(比如PCL
库),但是其实看明白之后就是很简单的几个模块判断、引入。尤其是后者,小学写过的字帖都有三部分:描红,仿影,临帖。先看人家怎么写,学会笔法之后再写自己的,编程学习之法可谓相同。
笔者才疏学浅,知识均由自学得来,故可能多有错漏。本文仅作抛砖引玉之用,欢迎读者斧正。
含义:要求cmake的最小版本
示例:
cmake_minimum_required(VERSION 2.8)
说明:
cmake_minimum_required(VERSION)命令隐式调用cmake_policy(VERSION)命令,以指定为给定范围的cmake版本编写当前项目代码。
个人理解:
cmake各个版本在查找文件的策略等等上会有一些差异。通过这个函数指定最小版本。以保证当前的策略是你想要的策略。
对于初学者,无需管这个,随便指定一个就可以,例如VERSION 2.8
.
含义:指定本工程名称
示例:
project(test)
个人理解:
指定工程名称,随便起一个名字就可以。需要注意的是,这个指定的不是编译出来的exe
或者是可执行文件的名字,那个是通过另一个函数指定的。这个就是工程的名字。
含义:设定变量的值
示例:
set(PCL_ROOT "D:/program-tools/PCL/PCL 1.12.1") //设定PCL_ROOT值为后面的路径
set(_waiting_for_debug 0) //设定值为0
set(DEFAULT_CXX_STANDARD 11) //设定值为11
个人理解:
这个函数基本可以看作赋值用的,需要注意的是,编译器往往有一些预定义的值,例如示例里面的DEFAULT_CXX_STANDARD
,这个是编译器预定义的值,通过将其置为11,可以设定编译规范为C++11
.
这个函数另一个重要作用就是指定库的路径,例如示例中的第一行,指定了PCL
库的ROOT
根目录。这一点我们下面也会谈到。
这个函数非常非常非常重要,你编译的时候找不到包大部分都是因为这个函数使用不当引起的。而且有些问题非常隐秘。这一部分可能有点多,但是请务必看下去,不然到时候你debug一天也找不到问题的时候可不要后悔哦。
cmake本身不提供任何搜索库的便捷方法,所有搜索库并给变量赋值的操作必须由cmake代码完成,比如FindXXX.cmake和XXXConfig.cmake。只不过,库的作者通常会提供这两个文件,以方便使用者调用。
例如,以著名的点云库PCL为例,其部分文件夹结构如下:
---PCL
|---PCL 1.12.1
|---include
|--- ...(略,一些头文件)
|---lib
|--- ...(略,一些库文件)
|---cmake
|---PCLConfig.cmake
|---PCLConfigVersion.cmake
|---Modles
|---FindEigen.cmake
|---FindFLANN.cmake
|---FindQhull.cmake
|---FindOpenMP.cmake
|--- ...(略,其余的依赖cmake文件)
|--- ...(其他文件夹)
观察上面的文件夹结构,作者特意把cmake文件独立一个文件夹,而其库引导文件PCLConfig.cmake
就在cmake目录下,我们不妨称之为根引导文件。在这个根引导文件的同级目录下,有一个Modles
文件夹,里面的文件都是诸如Findxxx.cmake
之类,顾名思义,显然是找到PCL库的其他依赖库的引导文件。我们不用打开根引导文件也能猜到,想必是这个PCLConfig.cmake
调用了文件夹里面的这些小的查找文件,去查找所需的依赖库。所以怎么办,你只要让cmake找到这个PCLConfig.cmake
根引导文件就可以了。这个根文件会自动调用其他查找文件。
find_package(OpenCV REQUIRED)
REQUIRED
:这个包必须要找到,找不到就会报错,下面的都不会执行。DNNConfig.cmake
dnn-config.cmake
”set()
函数,设定库DNN的路径,从而找到其配置文件DNNconfig.cmake
。PCL
库,那么怎么办,你需要找到你PCL库源代码所在位置,找到PCLConfig.cmake
,设定PCL_DIR
值为该文件路径即可。set(PCL_DIR "D:/program-tools/PCL/PCL 1.12.1/cmake")
find_package(PCL REQUIRED)
这样就不会再报错了。
再比如,我要引入Opencv,怎么办:
set(OpenCV_DIR D:/opencv/opencv3.4.6/opencv/build/x64/vc15/lib/)
find_package(OpenCV REQUIRED)
你可能注意到,这两个DIR一个是字符串,一个不是,这个不重要,两个写法都是可以的。
set()
函数配套使用,例如你所需要的库名为ABC,则你需要定义ABC_DIR
到你的库引导文件下,再调用find_package()
set(ABC_DIR "your path")
find_package(ABC REQUIRED)
扩展阅读资料:
描述子不止有REQUIRED
,还有一些其他的。不过对于初学者而言,一般你所需的库肯定是必须的,而不是可选的。所以如果你有需要,请自行查阅其他资料,这里仅介绍REQUIRED
。
find_package
采用两种模式搜索库:
Module
模式:搜索CMAKE_MODULE_PATH
指定路径下的FindXXX.cmake
文件,执行该文件从而找到XXX库。其中,具体查找库并给XXX_INCLUDE_DIRS
和XXX_LIBRARIES
两个变量赋值的操作由FindXXX.cmake
模块完成(先搜索当前项目里面的Module文件夹里面提供的FindXXX.cmake
,然后再搜索系统路径/usr/local/share/cmake-x.y/Modules/FindXXX.cmake
)Config
模式:搜索XXX_DIR
指定路径下的XXXConfig.cmake
文件,执行该文件从而找到XXX库。其中具体查找库并给XXX_INCLUDE_DIRS
和XXX_LIBRARIES
两个变量赋值的操作由XXXConfig.cmake
模块完成。含义:添加头文件搜索路径
顾名思义,你可以通过多个include_directories
函数来添加大量的搜索路径,这些函数之间不是互相覆盖的关系,而是添加到搜索列表的关系。
示例:
include_directories(${OpenCV_INCLUDE_DIRS})
include_directories("D:/program-tools/PCL/PCL 1.12.1/include/pcl-1.12/")
include_directories("D:/program-tools/PCL/PCL 1.12.1/3rdParty/FLANN/include/")
示例讲解:
第一行:${OpenCV_INCLUDE_DIRS}
是一个变量,里面的值显而易见是个路径。通过include_directories
引入搜索路径。
第二、三行:引入指定头文件搜索路径。
个人讲解:
关于${OpenCV_INCLUDE_DIRS}
这类函数怎么来,一般是OpencvConfig.cmake
这种自带的引导文件自带的,你自己读就可以看到,一般都在注释里面注明了。
这种库提供的变量,其实一般已经被库自带的引导文件自动包含了,但是为了vs code的自动补全,你不妨再手动添加一下,反正也没什么坏处。
含义:添加可执行文件
示例:
add_executable(main
${PROJECT_SOURCE_DIR}/DNN/dnn.cpp
${PROJECT_SOURCE_DIR}/main.cpp
)
关于什么是${PROJECT_SOURCE_DIR}
,自己去查,到这都四千字了懒得写了。
个人讲解:
第一个参数就是可执行文件的名字,Windows下面就是main.exe
,后面若干个参数都是说明了,这个可执行文件是由哪几个源代码编译过来的。当你的工程很简单的时候,可能一个cpp就可以编译出来一个exe可执行文件,但是一些稍微复杂一点的就不是这样的了。
需要注意的是,这个函数其实老版cmake是不支持一下添加好几个源文件的,因为其实源文件里面也有依赖关系,例如你在A.cpp
里面定义了类A,在main.cpp
里面使用了类A,那么如果你先编译main.cpp
就会报错。但是我看现在的cmake好像都支持自动排序编译,很实用。不过仍然建议,按照你源文件的依赖顺序去写,对你自己也不无好处。
一个项目可以不止生成一个目标文件。
含义:对该目标链接库
示例:
target_link_libraries(main ${OpenCV_LIBS})
target_link_libraries(test ${Boost_LIBRARIES} ${Qhull_LIBRARIES} ${PCL_LIBRARIES} ${PTHREAD_LIB})
个人理解:
${OpenCV_LIBS}
大家有兴趣可以打印出来看看是什么。含义:输出,打印
示例:
message(root: "111 ${PCL_ROOT} 1111")
效果:
可以看到,${}
括起来的自动替换为变量的值,而root:
即使没有被套在双引号内,仍然是作为字符打印的。这个常用做调试,查看变量的值。这个是vscode终端,没有颜色。如果是ubuntu的terminal,通过改变参数,可以输出红色、黄色等颜色的错误、警告。
这个是我写的一个DNN库的cmakelist,这里没有把它编译成lib。可以作为参考。
cmake_minimum_required(VERSION 2.8)
project(main)
# SET(CMAKE_BUILD_TYPE "Release") #可以设定优化为Release
# set(CMAKE_CXX_FLAGS_RELEASE "-O3") #可以设定优化级别为O3
set(DEFAULT_CXX_STANDARD 11) #C++11规范
set(OpenCV_DIR D:/opencv/opencv3.4.6/opencv/build/x64/vc15/lib/)
find_package(OpenCV REQUIRED)
include_directories(${OpenCV_INCLUDE_DIRS})
include_directories(${PROJECT_SOURCE_DIR}/DNN/)
add_executable(main
${PROJECT_SOURCE_DIR}/main.cpp
${PROJECT_SOURCE_DIR}/DNN/dnn.cpp
)
target_link_libraries(main ${OpenCV_LIBS})
这个是一个叫做wintoast的库,做出来的效果是这样的:
可以以windows系统通知的形式,横幅通知在屏幕右下方,文字、图标都可以换,有点好玩。这里我又自己二次封装了一下,注意看add_library
函数。这个文件写的很丑,不过既然是自己写着玩,我也就没管了。
cmake_minimum_required(VERSION 3.17)
project(wintoasttest)
add_definitions(-DCOMPILEDWITHC11)
include_directories(${PROJECT_SOURCE_DIR}/lib-wintoast/)
add_library(wintoastlib STATIC
${PROJECT_SOURCE_DIR}/lib-wintoast/wintoastlib.cpp
${PROJECT_SOURCE_DIR}/lib-wintoast/wintoastlib.h)
add_library(my_wintoastlib STATIC
${PROJECT_SOURCE_DIR}/sjh/my-wintoast/mywintoast.cpp
${PROJECT_SOURCE_DIR}/sjh/my-wintoast/mywintoast.h)
add_executable(main main.cpp)
add_executable(test test.cpp)
add_executable(test2 test2.cpp)
target_link_libraries(main ${PROJECT_SOURCE_DIR}/lib-wintoast/wintoastlib.lib)
target_link_libraries(test ${PROJECT_SOURCE_DIR}/lib-wintoast/wintoastlib.lib)
target_link_libraries(test2 ${PROJECT_SOURCE_DIR}/lib-wintoast/wintoastlib.lib
${PROJECT_SOURCE_DIR}/sjh/my-wintoast/my_wintoastlib.lib)
后续将会填充其他函数。写了六千七百字今晚不想写了。
我终于平庸 终于化成了
路过你头顶的风
–《你头顶的风》