3.如何找到一个CMake包和ROS包作为依赖

目录
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.cmakeXXXConfig.cmake,只不过,库的作者通常会提供这两个文件,以方便使用者调用。

CMakeList.txt找一个CMake包作为依赖核心是使用find_package()命令,使用find_package()找包会自动查找FindXXX.cmake或XXXConfig.cmake文件。

1.1 find_package()命令参数:

find_package( [version] [EXACT] [QUIET]|[REQUIRED]
              [[COMPONENTS] [components...]] [CONFIG]|[MODULE])

为包的名字,必填;
[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_DIRSXXX_LIBRARIES两个变量赋值的操作由FindXXX.cmake模块完成。

  • Config模式:搜索XXX_DIR指定路径下的XXXConfig.cmake文件,执行该文件从而找到XXX库。其中具体查找库并给XXX_INCLUDE_DIRSXXX_LIBRARIES两个变量赋值的操作由XXXConfig.cmake模块完成。

CMake默认采取Module模式,如果Module模式未找到库,才会采取Config模式。如果XXX_DIR路径下找不到XXXConfig.cmake文件,则会找/usr/local/lib/cmake/XXX/中的XXXConfig.cmake文件。总之,Config模式是一个备选策略。通常,库安装时会拷贝一份XXXConfig.cmake到系统目录中,因此在没有显式指定搜索路径时也可以顺利找到。

无论哪种模式, 找到package后一般都会对以下cmake变量赋值:

_FOUND: 找到为true, 否则为false
_INCLUDE_DIRS: 库头文件所在路径
_LIBRARIES/: 库文件所在路径
_VERSION/_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.cmake文件,工程结构如下:

project_name/        
└── src/            
└── include/     
└── cmake/     
    ├── FindFoo.cmake
    ├── FindBoo.cmake
├── CMakeList.txt  

还需要在CMakeList.txtfind_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.cmake文件,但你却不想使用默认的,想自己定义一个Find.cmake,即想让它绕过默认库,则你可以指定 CMAKE_MODULE_PATH ,它的优先级比默认路径要高。

1.2.2 find_package()的Config 模式

(a) XXXConfig.cmake文件位置

对Config模式,Config.cmake 一般放在外部目录下,也就是说这个文件一般是库的作者写的,库被安装时,该文件被安装在库的安装目录里,供库的使用者直接使用(如果未安装在系统目录,则使用方法是先设置 XXX_DIR,让find_package能找得到XXXConfig.cmake)。

Config-file Package是cmake支持的标准包, 一般通过源码编译安装的包都在CMakeList.txt中使用cmake指令生成了相应的配置文件Config.cmake/-config.cmake和版本文件Version.cmake/-version.cmake, 版本文件中存储了这个库的版本信息用来和指定的版本比较.

(b) 使用Config模式下的find_package()选项:

find_package( [version] [EXACT] [QUIET]
             [REQUIRED] [[COMPONENTS] [components...]]
             [CONFIG]   
             [NAMES name1 [name2 ...]]
             [CONFIGS config1 [config2 ...]]
             [HINTS path1 [path2 ... ]]
             [PATHS path1 [path2 ... ]])

[NAMES]不填时, 默认寻找Config.cmake, 否则寻找Config.cmake. name都是大小写敏感的, 名字写错了, 可能就找不到包了.
[CONFIGS] config1.cmake config2.cmake ..., 这一项可以直接设定需要找的配置文件的名字, 当设置了这一项时, find_package只会去找这一项指定名字的配置文件.
[HINTS][PATHS] 选项后面可以填猜测的配置文件所在路径

(c) Config模式下, find_package会在以下路径中去寻找配置文件

/(lib/|lib|share)/cmake/*/          (U)
/(lib/|lib|share)/*/                (U)
/(lib/|lib|share)/*/(cmake|CMake)/  (U)

是由变量CMAKE_LIBRARY_ARCHITECTURE指定的路径(默认为x86_64-linux-gnu).
为路径前缀, Unix类的系统下, 提供的变量有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版本. 而能够提供这个路径的变量有: 环境变量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.cmake的文件. Find.cmake是需要我们自己写的, 其中可以强硬的指定库路径所在位置, 也可以通过cmake提供的函数来自动寻找, 第二种方式更为鲁棒一些. CMEK_MODULE_PATH默认也是空的, 需要我们手动提供find-module文件的路径. 简单来说Module Mode就是允许让用户用自己的方式来找库, 这样更能确保找到自己想要的库. 像ceres, gtsam等这些库就是使用module模式来找自己的依赖库.

二. 找包的一些小技巧

  • find_package()中填的包名很重要, 实际就是去找Find.cmake文件或者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  # 进入工作空间目录
catkin build 

更多关于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
  
  
  ...
  )

可以看到, 就算有n个ROS包, 也可以使用1个find_package()来找. 所有的ROS包都将作为catkin的components, 这些包的头文件存储在变量catkin_INCLUDE_DIRS中, 库文件都存储在变量catkin_LIBRARIES中. 找ROS包除了在CMakeList.txt中使用find_package, 还需要在package.xml文件中添加:

package1
...
packagen

假设工作空间下已经有一个package-A. 现在我想写一个package-B, 需要使用package-A中的函数. 此时需要在package-B的CMakeList.txt中添加:

find_package(catkin REQUIRED package-A)

include_directories(${catkin_INCLUDE_DIRS})

add_executable(
  xxx.cpp ...)
target_link_libraries(
  ${catkin_LIBRARIES})

然后在package-Bpackage.xml文件中写入:

package-A

这样就能在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

你可能感兴趣的:(3.如何找到一个CMake包和ROS包作为依赖)