目录
(1)find_package(catkin REQUIRED COMPONENTS ...)
(3)add_executable( src1 src2 ...) (简易写法)
(4)target_link_libraries( lib1 lib2 ...) (简易写法,lib是依赖库的路径)
首先要知道,可以把CMakeLists.txt当成是一串源代码,CMakeLists.txt有自带的一些“系统变量”,里面的每个命令都是一个个“函数”,函数也需要按照要求填写正确格式的“变量”,我们可以通过这些“函数”对Cmake提出一些要求,或配置,进而对makefile进行一系列的自动的“书写”,最终使用make指令利用makefile对源代码进行编译。
其中这些“系统变量”有些是不显式写在CMakeLists.txt中的,比如程序包A的头文件路径用变量A_INCLUDE_DIRS表示,它就是一个“系统变量”,可以通过find_package这个“函数(命令)”去命令cmake工具去自动寻找程序包A的头文件路径,并当成“函数返回值”写到A_INCLUDE_DIRS这个变量中,这样就可以在后面的CMakeLists.txt中显式使用A_INCLUDE_DIRS这个变量了,这种“系统变量”通常使用${A_INCLUDE_DIRS}的形式来引用。
(1)find_package(catkin REQUIRED COMPONENTS ...)
(摘抄[1])find_package告诉cmake去按照优先级顺序在指定的搜索路径进行查找Findxxx.cmake文件和xxxConfig.cmake文件(其中xxx代表库的名字,特别注意的是有大小写之分),这两个文件大体上是没有区别的,cmake能够找到这两个文件中的任何一个,我们都能成功使用该库,也就是我们可以用库的内置好了Cmake变量。包含了库的头文件和库文件的路径信息,虽然库的作者一般会提供这两个文件,但是也会遇到安装完毕后找不到的情况。当我们在cmake..命令之后,Cmake 会读取执行CMakeLists.txt中的代码,当执行find_package()这条命令后,Cmake 就会从某些路径中找这Findxxx.cmake文件或者xxxConfig.cmake文件,Cmake找到任意一个之后就会执行这个文件,然后这个文件执行后就会设置好一些Cmake变量。Cmake比如下面的变量(NAME表示库的名字 比如可以用Opencv 代表Opencv库):
<NAME>_FOUND //是否找到该库的标志位 <NAME>_INCLUDE_DIRS or <NAME>_INCLUDES //该库的头文件地址 <NAME>_LIBRARIES or <NAME>_LIBS //该库的静态或者动态链接文件地址 <NAME>_DEFINITIONS //不懂这个
一般常用的就是xxx_FOUND 、xxx_INCLUDE_DIRS、xxx_LIBS,分别代表是否找到库的标志、库的头文件路径、库文件路径。find_package()有两种模式:Module模式和Config模式,分别对应上面的Findxxx.cmake 和xxxConfig.cmake两个文件。cmake默认优先Module模式,而Config模式是备选项。
Module模式(仅仅查找Findxxx.cmake文件):
Cmake会优先搜索CMAKE_MODULE_PATH指定的路径,如果在CMakeLists.txt中没有设置CMAKE_MODULE_PATH为存储Findxxx.cmake的路径,也就是说没有下面的指令:
set(CMAKE_MODULE_PATH "Findxxx.cmake文件所在的路径")
那么Cmake不会搜索CMAKE_MODULE_PATH指定的路径,此时Cmake会搜索第二优先级的路径,也就是<CMAKE_ROOT>/share/cmake-x.y/Mdodules (注意:x.y表示版本号。我的是3.10)。其中CMAKE_ROOT是你在安装Cmake的时候的系统路径,因为我并没有指定安装路径,所以是系统默认的路径,在我的系统中(ubuntu16.04)系统的默认路径是/usr/loacl,如果你在安装的过程中使用了
cmake -DCMAKE_INSTALL_PREFIX=自己dir路径
那么此时CMAKE_ROOT就代表那个你写入的路径 。刚刚说道第一优先级的路径搜索没有找到Findxxx.cmake文件,就会到第二优先级的路径下搜索。如果Cmake在两个路径下都没有找到Findxxx.cmake文件。那么Cmake就会进入Config模式。
Config模式(仅仅查找xxxConfig.cmake文件):
Cmake会优先搜索xxx_DIR 指定的路径。如果在CMakeLists.txt中没有设置这个cmake变量。也就是说没有下面的指令:
set(xxx_DIR "xxxConfig.cmkae文件所在的路径")
那么Cmake就不会搜索xxx_DIR指定的路径,此时Cmake 就会自动到第二优先级的路径下搜索,也就是/usr/local/lib/cmake/xxx/中的xxxConfig.cmake文件。
上面主要讲了Cmake的搜索模式。如果Cmake在两种模式提供的路径中没有找到对应的Findxxx.cmake和xxxConfig.cmake文件,此时系统就会提示最上面的那些错误信息。
总结:
- 首先简单来讲:find_package用来查找一些包的路径、头文件路径等信息,例如在A的CMakeList.txt中,设定find_package(B),该命令不会直接使得cmake知道工程A需要依赖B,而只是告诉cmake去查找B,然后返回一些相关变量而已。
- 可以通过set命令手动设置所需要的库路径(其实是对应的Findxxx.cmake和xxxConfig.cmake文件路径),此功能很重要,第一:当系统处于某种原因找不到需要的库文件的时候,就需要手动指定。第二:当系统包含多个版本的某库时候,比如PCL1.7与PCL1.8共存的时候,可以通过set命令手动选择需要哪个库。
在ROS中的使用:
find_package的标准命令格式是:
find_package(<PackageName> [version] [EXACT] [QUIET] [MODULE]
[REQUIRED] [[COMPONENTS] [components...]]
[OPTIONAL_COMPONENTS components...]
[NO_POLICY_SCOPE])
在ROS中,在写CMakeList.txt的时候,至少需要指明去寻找“catkin”这个包,如果自己需要依赖于其他自己写的catkin_package,最好是把他们当成catkin这个包的一个COMPONENTS去寻找,比如:
![](https://i-blog.csdnimg.cn/blog_migrate/d6c7374c8aed34482cd58a0a2f384e68.png)
把自己写的catkin_package当成catkin的COMPONENTS的好处是,这些自己的或者其他系统写好的catkin_package包,它们的include路径、libraries路径等自动添加到 catkin_ variables中,也就是说, catkin_INCLUDE_DIRS不仅包含catkin包自己的include路径,还包含了它的以上所有COMPONENTS(组件)的路径。同理catkin_LIBRARIES也包含了这些组件的库路径,这样就很方便了!
比如,catkin程序包A在调用catkin程序包B中的头文件 b.h ,只需要在包A的CMakeList.txt中,在find_package中,写上:
find_package(catkin REQUIRED COMPONENTS B)
并声明依赖关系,确保B先于A构建出来
add_dependencies(<A的target> <依赖的B的target>)
这样就可正常使用B的头文件了。通常情况下,因为b.h以及对应的b.cpp参与了A中某可执行文件的构建,那么在A的CMakeList.txt文件中使用include_directories(B_INCLUDE_DIRS)指明对b.h的引用,以及使用target_link_libraries(A_target B_LIBRARIES)指明对B库的链接。
但是当把B当做catkin的组件时,仅仅使用默认的命令即可,如下,而不用再手动添加
include_directories( ${catkin_INCLUDE_DIRS})
target_link_libraries(<A的target> ${catkin_LIBRARIES})
Note:所以,虽然上面两句不用再指明包B,但一定要在find_package 中指明,只有这样才能把B的变量添加到catkin_variables中,一起用!
这点更详细的解释建议参看官方解释http://wiki.ros.org/catkin/CMakeLists.txt
(2)include_directories()
这个命令主要解决的问题是:如果在包A中的某 *.cpp 文件中调用了一个头文件,比如#include <opencv/cv.h>,gcc去哪里找这个头文件的问题;
我们可以使用#include “/usr/local/include/opencv/cv.h”来直接显式的给出头文件地址,但是显然不规范,我们可以通过在A的CMakeList.txt中给出关于它的一个根目录,即命令:
include_directories(/usr/local/include)
使得系统知道以/usr/local/include为基础去寻找头文件opencv/cv.h。
此外,结合find_package()命令,可以很方便的自动填写关于需要的头文件的路径,比如对于opencv,使用以下命令就可以自动查找头文件路径
find_package(opencv REQUIRED)
include_directories(${OPENCV_INCLUDE_DIRS})
注意点:
- 包A自身的头文件夹include也需要被填写进来(如果有的话),并且要提前于其他包的路径。
- 对于catkin类型自带的程序包,在find_package中指明之后,在include_directories只需要对应的写上${catkin_INCLUDE_DIRS}就可以指代所有的:
find_package(catkin REQUIRED COMPONENTS
roscpp
rospy
std_msgs
message_generation
)
include_directories(
#下面的include指的就是自身的头文件文件夹
include
#下面的是find_package指令返回的头文件路径
${catkin_INCLUDE_DIRS}
)
(3)add_executable(<name> src1 src2 ...) (简易写法)
使用add_executable指令告诉gcc,要生成名为<name>的可执行文件,这个可执行文件由src1、src2...来编译生成,注意,所有牵涉到<name>编译的文件都要列出来。
比如,对于包A中src文件夹里面有文件test.cpp math.cpp matrix.cpp三个,include文件夹里面有math.h matrix.h 两个头文件,在test.cpp (有 main函数)中分别用
#include"math.h"
#include"matrix.h"
调用了math.cpp matrix.cpp,那么在生成<name>为test的可执行文件的时候,要填写
add_executable(test src/test.cpp src/math.cpp src/matrix.cpp )
因为,在生成test可执行文件的过程中,需要把src/test.cpp src/math.cpp src/matrix.cpp三个文件的obj文件进行相互链接。
(4)target_link_libraries(<name> lib1 lib2 ...) (简易写法,lib是依赖库的路径)
该命令要放在add_executable命令之后,当add_executable执行完之后,会生成名称为name的二进制文件,此时还没有完全生成可执行文件,比如name的源代码在头文件调用了boost库,那么在生成name的最终可执行文件时候,还需要去和boost的库文件进行链接,最终才可以生成最终的可执行文件。
add_executable的作用就是告诉cmake,把刚刚生成的name,与该包依赖的其他外部库文件( lib1 lib2 ...)等进行链接,生成最终的可执行文件。截个图看一下输出
重点的命令总结:
这几个命令按照顺序填写,可以完成基本的配置,可以看出,CMakeList.txt将一个编译过程,分为了几个阶段,基本上就是按照编译的步骤来的。
步骤1.查找依赖包(库和头文件)的路径
| find_package() |
步骤2.为自己源代码的头文件提供路径
| include_directories() |
步骤3.编译自己的源代码为目标文件
| add_executable() |
步骤4.把自己的目标文件与其他库文件进行链接
| target_link_libraries() |
因此,在cmake报错的时候,可按照错误分类,判断是找不到包(find_package可能有问题),还是没有找到头文件(include_directories),还是链接失败啥的,一一分析去找,有可能问题是比较综合的。
下一篇是其他相关的配置命令,有些不是必要的,但是却十分常用,但是也是十分重要的。传送门:https://blog.csdn.net/u012057432/article/details/103353547
参考链接:
[1] https://blog.csdn.net/chengde6896383/article/details/86497016 find_package原理