目录
1 使用 find_package()找CMake包
1.1 find_package()命令参数
1.2 find_package()的两种模式
1.2.1 find_package()的Module模式
1.2.2 find_package()的Config模式
1 找一个ROS包作为依赖
2 如何写一个基于ROS的CMakeList
1 使用 find_package 找CMake包
cmake本身不提供任何搜索库的便捷方法,所有搜索库并给库变量赋值的操作必须由cmake代码(自己写的)完成,比如下面将要提到的FindXXX.cmake和XXXConfig.cmake,只不过,库的作者通常会提供这两个文件,以方便使用者调用。
CMakeList.txt找一个CMake包作为依赖核心是使用find_package()命令,使用find_package()找包会自动查找FindXXX.cmake或XXXConfig.cmake文件。
1.1 find_package()命令参数:
find_package(<package> [version] [EXACT] [QUIET]|[REQUIRED]
[[COMPONENTS] [components...]] [CONFIG]|[MODULE])
<package>
为包的名字,必填;
[version]
为版本号,如3.5.1,可以不填.
[EXACT]
选填, 当使能时, 要求找到的库版本号和要求的完全一致才算找到.
[QUIET]
和[REQUIRED]
填一个就行, 使能[QUIET]
时, 就算找不到库, cmake也不会报错会继续执行; 使能[REQUIRED]
时要求必须找到库, 找不到时会报错退出.
CONIFG
选项使能时, 使用Config模式找包;
MODULE
选项使能时, 使用Module模式找包.
当没有指明具体用哪种模式时, find_package
会先使用Module Mode
找包, 如果找不到再使用Config Mode
寻找
这里只是针对Ubuntu下的应用做了一个简单的整理, 详细的教程可以参考官方find_package教程和Find Module教程
1.2 find_package 的两个工作方式
find_package 有两种模式,Module模式和Config模式
Module模式:搜索CMAKE_MODULE_PATH指定路径下的FindXXX.cmake文件,执行该文件从而找到XXX库。其中,具体查找库并给XXX_INCLUDE_DIRS和XXX_LIBRARIES两个变量赋值的操作由FindXXX.cmake模块完成。
Config模式:搜索XXX_DIR指定路径下的XXXConfig.cmake文件,执行该文件从而找到XXX库。其中具体查找库并给XXX_INCLUDE_DIRS和XXX_LIBRARIES两个变量赋值的操作由XXXConfig.cmake模块完成。
CMake默认采取Module模式,如果Module模式未找到库,才会采取Config模式。如果XXX_DIR路径下找不到XXXConfig.cmake文件,则会找/usr/local/lib/cmake/XXX/中的XXXConfig.cmake文件。总之,Config模式是一个备选策略。通常,库安装时会拷贝一份XXXConfig.cmake到系统目录中,因此在没有显式指定搜索路径时也可以顺利找到。
无论哪种模式, 找到package后一般都会对以下cmake变量赋值:
<package>_FOUND: 找到为true, 否则为false
<package>_INCLUDE_DIRS: 库头文件所在路径
<package>_LIBRARIES/<package_LIBRARY_DIRS>: 库文件所在路径
<package>_VERSION/<package>_VERSION_STRING: 库的版本号(不一定有值,除非文件中提供了版本信息)
若XXX安装时没有安装到系统目录,因此无法自动找到XXXConfig.cmake,可以在CMakeLists.txt
最前面添加XXX的搜索路径。
添加CaffeConfig.cmake的搜索路径
set(Caffe_DIR /home/hzh/projects/Caffe/build)
#添加CaffeConfig.cmake的搜索路径,CMake会自动搜所有变量名为XXX_DIR的路径
或者
list(APPEND CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH}" "${CMAKE_CURRENT_LIST_DIR}/cmake")
通过命令 cmake –help-module-list (输入cmake –help,然后双击Tab会有命令提示)得到你的CMake支持的模块的列表:直接查看模块路径。比如Ubuntu linux上,模块的路径是 /usr/share/cmake/Modules/
1.2.1 find_package()的Module模式
使用Module模式的话, 一般在工程目录下新建一个cmake的目录, 并在cmake目录中存放自己写的Find<package>.cmake
文件,工程结构如下:
project_name/
└── src/
└── include/
└── cmake/
├── FindFoo.cmake
├── FindBoo.cmake
├── CMakeList.txt
还需要在CMakeList.txt
的find_package
前加入FindXXX.cmake
文件的路径:
list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")
CMakeLists.txt 内容:
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake")
find_package(Foo REQUIRED) # FOO_INCLUDE_DIR, FOO_LIBRARIES
find_package(Boo REQUIRED) # BOO_INCLUDE_DIR, BOO_LIBRARIES
include_directories("${FOO_INCLUDE_DIR}")
include_directories("${BOO_INCLUDE_DIR}")
add_executable(Bar Bar.hpp Bar.cpp)
target_link_libraries(Bar ${FOO_LIBRARIES} ${BOO_LIBRARIES})
注意:如果系统目录(一般是 /usr/local/lib/cmake/ )里有一个Find<package>.cmake文件,但你却不想使用默认的,想自己定义一个Find<package>.cmake,即想让它绕过默认库,则你可以指定 CMAKE_MODULE_PATH ,它的优先级比默认路径要高。
1.2.2 find_package()的Config 模式
(a) XXXConfig.cmake文件位置
对Config模式,<package>Config.cmake 一般放在外部目录下,也就是说这个文件一般是库的作者写的,库被安装时,该文件被安装在库的安装目录里,供库的使用者直接使用(如果未安装在系统目录,则使用方法是先设置 XXX_DIR,让find_package能找得到XXXConfig.cmake)。
Config-file Package是cmake支持的标准包, 一般通过源码编译安装的包都在CMakeList.txt
中使用cmake指令生成了相应的配置文件<package>Config.cmake/<package>-config.cmake和版本文件<config-file>Version.cmake/<config-file>-version.cmake, 版本文件中存储了这个库的版本信息用来和指定的版本比较.
(b) 使用Config模式下的find_package()选项:
find_package(<package> [version] [EXACT] [QUIET]
[REQUIRED] [[COMPONENTS] [components...]]
[CONFIG]
[NAMES name1 [name2 ...]]
[CONFIGS config1 [config2 ...]]
[HINTS path1 [path2 ... ]]
[PATHS path1 [path2 ... ]])
[NAMES]不填时, 默认寻找<package>Config.cmake, 否则寻找<name>Config.cmake. <package>或name都是大小写敏感的, 名字写错了, 可能就找不到包了.
[CONFIGS] config1.cmake config2.cmake ..., 这一项可以直接设定需要找的配置文件的名字, 当设置了这一项时, find_package只会去找这一项指定名字的配置文件.
[HINTS]和[PATHS] 选项后面可以填猜测的配置文件所在路径
(c) Config模式下, find_package会在以下路径中去寻找配置文件
<prefix>/(lib/<arch>|lib|share)/cmake/<name>*/ (U)
<prefix>/(lib/<arch>|lib|share)/<name>*/ (U)
<prefix>/(lib/<arch>|lib|share)/<name>*/(cmake|CMake)/ (U)
<arch>
是由变量CMAKE_LIBRARY_ARCHITECTURE
指定的路径(默认为x86_64-linux-gnu
).
<prefix>
为路径前缀, Unix类的系统下, 提供<prefix>
的变量有6个:
- cmake cache变量:
CMAKE_PREFIX_PATH
- cmake cache变量:
CMAKE_SYSTEM_PREFIX_PATH
- 环境变量:
CMAKE_PREFIX_PATH
- 环境变量:
PATH
其中以/bin
,/sbin
结尾的路径会自动退回到上一级路径 - 由指令中
HINTS
指定的路径 - 由指令中
PATHS
指定的路径
(注: 环境变量就是系统shell中的变量; cmake cache变量可以理解成cmake中的全局变量, 是被存储在文件中的, 当执行cmake ..
后, 会生成一个CMakeCache.txt
的文件, 里面记录了cache变量的值. 另外cmake还有普通变量, cache变量和普通变量的区别可以参考cmake 两种变量原理).
(d) 以opencv为例, 看一下opencv是如何被找到的. 在CMakeList.txt
中写入:
find_package(OpenCV 3 REQUIRED)
message("opencv config file path: " ${OpenCV_CONFIG})
message("1. cmake cache var 'CMAKE_PREFIX_PATH' = ${CMAKE_PREFIX_PATH}" )
message("2. env var 'CMAKE_PREFIX_PATH' = $ENV{CMAKE_PREFIX_PATH}" )
message("3. env var 'PATH' = $ENV{PATH}" )
message("4. cache var 'CMAKE_SYSTEM_PREFIX_PATH' = ${CMAKE_SYSTEM_PREFIX_PATH}" )
执行cmake ..
后, 可以看到终端中有输出(每个人可能不一样):
opencv config file path: /opt/ros/kinetic/share/OpenCV-3.3.1-dev/OpenCVConfig.cmake
1. cmake cache var 'CMAKE_PREFIX_PATH' =
2. env var 'CMAKE_PREFIX_PATH' = /home/yangt/workspace/ros_ws/cw_project/ivrc_ws/devel:/opt/ros/kinetic
3. env var 'PATH' = /opt/ros/kinetic/bin:/home/yangt/bin:/home/yangt/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/usr/local/texlive/2018/bin/x86_64-linux:/snap/bin:/usr/local/texlive/2018/bin/x86_64-linux
4. cache var 'CMAKE_SYSTEM_PREFIX_PATH' = /usr/local;/usr;/;/usr;/usr/local
可以看到, 找到的Opencv为/opt/ros/kinetic
下的3.3.1版本. 而能够提供这个路径<prefix>
的变量有: 环境变量CMAKE_PREFIX_PATH
和环境变量PATH
. 如果我们在CMakeList中把这两个环境变量的值设为空, 那么将不会找到符合版本的Opencv. 在find_package
前写:
set(ENV{CMAKE_PREFIX_PATH} "")
set(ENV{PATH} "")
再次执行cmake ..
后输出为:
CMake Error at CMakeLists.txt:9 (find_package):
Could not find a configuration file for package "OpenCV" that is compatible
with requested version "3".
The following configuration files were considered but not accepted:
/usr/local/lib/cmake/opencv4/OpenCVConfig.cmake, version: 4.0.0
/usr/share/OpenCV/OpenCVConfig.cmake, version: 2.4.9.1
可以看到, 找到的opencv为/usr/local
下的4.0.0和/usr
下的2.4.9, 但由于版本不符合REQUIRED
要求, 还是报错退出了. 如果继续把CMAKE_SYSTEM_PREFIX_PATH
设为空, 将找不到任何版本的opencv.
Module Mode
Module Mode 会去变量CMAKE_MODULE_PATH
存放的路径下寻找名为Find<package>.cmake
的文件. Find<package>.cmake
是需要我们自己写的, 其中可以强硬的指定库路径所在位置, 也可以通过cmake提供的函数来自动寻找, 第二种方式更为鲁棒一些. CMEK_MODULE_PATH
默认也是空的, 需要我们手动提供find-module文件的路径. 简单来说Module Mode就是允许让用户用自己的方式来找库, 这样更能确保找到自己想要的库. 像ceres, gtsam等这些库就是使用module模式来找自己的依赖库.
二. 找包的一些小技巧
-
find_package(<package_name>)
中填的包名<package_name>
很重要, 实际就是去找Find<package_name>.cmake
文件或者<package_name>Config.cmake
文件, 这是区分大小写的. 如果不知道包名填什么, 就去/usr
下搜索一下库名, 如果能发现xxxConfig.cmake
, 那么xxx就是你应该填的名子. - 实际上大多数包都是使用Config模式找到的. 如果电脑上一个包存在多个版本, 一定要填需要的版本号.
- 如果找不到想要的库, 先确定电脑是否安装了这个库. 库的位置一般在
/usr
下和/opt
下, 去这两个目录下搜索一下就能知道. 如果电脑确实安装了该库, 那么就需要根据上面讲的原理一步一步排查. 首先确定是使用Config模式还是Module模式, 然后根据模式去检查上面提到的搜索路径, 是否包含库实际存在的路径. 一般可以通过设置cmake变量CMAKE_PREFIX_PATH
来包含实际库的路径.
如何写一个基于ROS的CMakeList
一个ros工程目录的基本结构为:
workspace_name # 工作空间目录
└── devel # 编译后自动生成该目录. 生成的target存放在该目录
└── src # 源码目录
└── package1 # 包目录
└── src # 存放源码目录
├── xxx.cpp
└── include # 存放头文件的目录
├── xxx.hpp
├── CMakeList.txt
├── package.xml
└── package2 # 包目录
...
└── packagen # 包目录
可以看到, 一个ros package就是上面介绍的一个CMake c++工程, 只不过多了一个package.xml
文件. 另外, 一个ros package必须放在workspace目录下的src里才行.
编译ros包的基本指令:
cd <workspace_folder> # 进入工作空间目录
catkin build <package-name>
更多关于ros的基本概念请先参看ros官方教程.
ROS package的CMakeList与普通CMakeList的写法基本是一样的, 普通CMakeList支持的语法, ros CMakeList都支持. 只不过ros对cmake进行了封装, 增加了几条指令. 和普通的CMakeList相比, 这里主要关心2个问题: 1) 如何找到其他的ros package作为库使用(找普通的library方法不变). 2) 如何让自己写的ros package能够被其他ros package找到使用.
如何使用其他的ros包
寻找ros包同样也使用find_package()
指令, 不过有些许不同:
find_package(catkin REQUIRED COMPONENTS
<package1>
<package2>
...
<packagen>)
可以看到, 就算有n个ROS包, 也可以使用1个find_package()
来找. 所有的ROS包都将作为catkin的components, 这些包的头文件存储在变量catkin_INCLUDE_DIRS
中, 库文件都存储在变量catkin_LIBRARIES
中. 找ROS包除了在CMakeList.txt
中使用find_package
, 还需要在package.xml
文件中添加:
<depend>package1<depend>
...
<depend>packagen<depend>
假设工作空间下已经有一个package-A
. 现在我想写一个package-B
, 需要使用package-A
中的函数. 此时需要在package-B
的CMakeList.txt中添加:
find_package(catkin REQUIRED package-A)
include_directories(${catkin_INCLUDE_DIRS})
add_executable(<target_name>
xxx.cpp ...)
target_link_libraries(<target-name>
${catkin_LIBRARIES})
然后在package-B
的package.xml
文件中写入:
<depend>package-A<depend>
这样就能在package-B的代码中包含package-A的头文件并使用其中的函数了.
ROS包一定是位于某个工作空间中的(可以是其他工作空间), 每一个工作空间都有一个setup.bash文件, 要想这个工作空间中的包能被find_package()
找到, 必须先在终端执行 source setup.bash
命令来设定相应的CMAKE PATH变量
如何让自己的ROS包能被其他包调用
想要让自己写的ros包能被其他ros包顺利调用, 需要在生成target的指令之前添加:
catkin_package(
INCLUDE_DIRS <自己包的头文件所在相对路径(相对于CMakeList.txt)>
LIBRARIES <自己包会生成的库的名字>
CATKIN_DEPENDS <自己包所依赖的其他ros包的名字>
DEPENDS <自己包所依赖的其他非ROS库的名字>)
- 如果
INCLUDE_DIRS
不填, 则其他ros包无法找到这个包的头文件; - 如果
LIBRARIES
不填, 则其他包会找不到这个包生成的库文件, 会出现undefined reference error: ...
; -
CATKIN_DEPENDS/DEPENDS
的作用在于: 当其他包调用这个包时, 不需要再用find_package()
再去寻找一遍相同的依赖库. 举个例子, 假如我们自己写的packae_A中依赖了OpenCV, 如果在catkin_package()
中写了DEPENDS OpenCV
, 那么在其他包中使用find_package
找 package_A时, 会自动加入OpenCV库的依赖, 而不需要再使用find_package(OpenCV REQUIRED)
寻找OpenCV.
更详细的关于ROS CMakeList的知识, 参考官网ROS CMakeList.
例程simple_ros_cmake_example中展示了一个基本的ROS 版CMakeList写法.这个包读取一张图片并发布成占据栅格在rviz中显示,同时订阅rviz发布的2D Nav Goal
信息.
转载自:https://github.com/ytiang
参考:
https://blog.csdn.net/bytxl/article/details/50637277