简介
本文以常用点云匹配算法库ndt_omp为例,探究在一个cmake ROS工程中使用第三方ROS库的方法与背后的机制。
如何引入第三方算法库
首先ndt_omp 是一个基于ROS组织的算法库(ROS包),这可以通过工程中的package.xml看出来。要使用该库,首先我们将该工程移动到主工程的src文件中,和其他的ROS包处与平行的关系。若需要在另一个ROS工程中使用ndt_omp库,则在该ROS工程的CMakeLists.txt中添加如下:
find_package(catkin REQUIRED COMPONENTS
...
ndt_omp
...
)
对于find_package(),我们知道它其实是去查找.cmake配置文件并加载配置文件中关于库的信息,优先进入MODULE模式去查找FindXXX.cmake文件,如果找不到则会进入config模式去指定路径下查找XXXConfig.cmake文件。
那么,在当前这个实例中,执行cmake后,find_package(catkin)会找到/opt/ros/melodic/share/catkin/cmake/catkinConfig.cmake文件进行执行,在该文件中,对catkin_INCLUDE_DIRS
、catkin_LIBRARIES
,catkin_LIBRARY_DIRS
等环境变量进行了构造,而将ndt_omp添加到find_package(catkin REQUIRED COMPONENTS …) 中,那么ndt_omp的头文件、库文件等信息就会被加入到catkin_INCLUDE_DIRS、catkin_LIBRARIES等环境变量中,然后将这些信息通过target_link_libraries、include_directories等方式进行设置就可以成功找到包了。
那么ndt_omp包通过find_package(catkin REQUIRED COMPONENTS …) 加入到catkin_INCLUDE_DIRS、catkin_LIBRARIES等环境变量的信息又是如何被设置的呢?这就是靠catkin_package()
命令,在ndt_omp包的CMakeLists.txt中就可以看到:
catkin_package(
INCLUDE_DIRS include
LIBRARIES ndt_omp
)
catkin_package() 的参数如下:
INCLUDE_DIRS:声明给其他 package 的 include 路径。
LIBRARIES:声明给其他 package 的库。
CATKIN_DEPENDS:本包依赖的 catkin package。
DEPENDS:本包依赖的非 catkin package。
CFG_EXTRAS:其它配置参数。
如果其他功能包采用find_package(catkin REQUIRED COMPONENTS XXX)使用当前功能包XXX, XXX包中使用catkin_package()设置的 include 路径和库以及DEPENDS 依赖将会包含在catkin_INCLUDE_DIRS 和 catkin_LIBRARIES等变量中 。
catkin_package() 的另一个作用,就是将该包编译生成的文件按照ROS工程的模板进行放置,例如会将可执行文件生成到devel/lib/XXX中。
那么还有一个疑问,我们知道catkin_package()设置的信息会被设置到catkin_INCLUDE_DIRS 和 catkin_LIBRARIES等变量中,那么这背后是如何实现的呢? 请看下面的内容。
find_package(catkin REQUIRED COMPONENTS)细节
find_package(catkin REQUIRED COMPONENTS)将找到catkinConfig.cmake并将REQUIRED COMPONENTS传入进行执行,下面对catkinConfig.cmake的内容进行解析.
1、初始化环境变量
# XXXX don't overwrite catkin_* variables when being called recursively
if(NOT _CATKIN_FIND_ OR _CATKIN_FIND_ EQUAL 0)
set(_CATKIN_FIND_ 0)
if(catkin_FIND_COMPONENTS)
set(catkin_INCLUDE_DIRS "")
set(catkin_LIBRARIES "")
set(catkin_LIBRARY_DIRS "")
set(catkin_EXPORTED_TARGETS "")
endif()
endif()
# increment recursion counter
math(EXPR _CATKIN_FIND_ "${_CATKIN_FIND_} + 1")
本ros工程第一次find_package(catkin)时,进入XX/catkinConfig.cmake后_CATKIN_FIND_没有定义,如上代码所示,将创建catkin_INCLUDE_DIRS
、catkin_LIBRARIES
,catkin_LIBRARY_DIRS
等环境变量 并初始化为空。
而find_package()在后面处理依赖时也可能递归的触发XX/catkinConfig.cmake,例如tf依赖
find_package(catkin REQUIRED COMPONENTS tf)
就会导致后续递归的触发, 为了保证递归触发XX/catkinConfig.cmake时环境变量不会执行初始化清0,设立了_CATKIN_FIND_变量,记录XX/catkinConfig.cmake的递归次数,并通过IF判断使得仅在第一次执行XX/catkinConfig.cmake时执行初始化了。
2、处理依赖
# find all components
if(catkin_FIND_COMPONENTS)
message("catkin_FIND_COMPONENTS!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")
foreach(component ${catkin_FIND_COMPONENTS})
string(TOLOWER "${component}" component_lower)
# skip catkin since it does not make sense as a component
if(NOT ${component_lower} STREQUAL "catkin")
# get search paths from CMAKE_PREFIX_PATH (which includes devel space)
set(paths "")
foreach(path ${CMAKE_PREFIX_PATH})
if(IS_DIRECTORY ${path}/share/${component}/cmake)
list(APPEND paths ${path}/share/${component}/cmake)
endif()
endforeach()
# find package component
if(catkin_FIND_REQUIRED)
# try without REQUIRED first
find_package(${component} NO_MODULE PATHS ${paths}
NO_DEFAULT_PATH NO_CMAKE_FIND_ROOT_PATH)
if(NOT ${component}_FOUND)
# show better message to help users with the CMake error message coming up
message(STATUS "Could not find the required component '${component}'. "
"The following CMake error indicates that you either need to install the package "
"with the same name or change your environment so that it can be found.")
find_package(${component} REQUIRED NO_MODULE PATHS ${paths}
NO_DEFAULT_PATH NO_CMAKE_FIND_ROOT_PATH)
endif()
elseif(catkin_FIND_QUIETLY)
find_package(${component} QUIET NO_MODULE PATHS ${paths}
NO_DEFAULT_PATH NO_CMAKE_FIND_ROOT_PATH)
else()
find_package(${component} NO_MODULE PATHS ${paths}
NO_DEFAULT_PATH NO_CMAKE_FIND_ROOT_PATH)
endif()
# append component-specific variables to catkin_* variables
list_append_unique(catkin_INCLUDE_DIRS ${${component}_INCLUDE_DIRS})
# merge build configuration keywords with library names to correctly deduplicate
catkin_pack_libraries_with_build_configuration(catkin_LIBRARIES ${catkin_LIBRARIES})
catkin_pack_libraries_with_build_configuration(_libraries ${${component}_LIBRARIES})
list_append_deduplicate(catkin_LIBRARIES ${_libraries})
# undo build configuration keyword merging after deduplication
catkin_unpack_libraries_with_build_configuration(catkin_LIBRARIES ${catkin_LIBRARIES})
list_append_unique(catkin_LIBRARY_DIRS ${${component}_LIBRARY_DIRS})
list(APPEND catkin_EXPORTED_TARGETS ${${component}_EXPORTED_TARGETS})
endif()
endforeach()
list_insert_in_workspace_order(catkin_INCLUDE_DIRS ${catkin_INCLUDE_DIRS})
list_insert_in_workspace_order(catkin_LIBRARY_DIRS ${catkin_LIBRARY_DIRS})
endif()
当catkin有依赖的组件时,catkin_FIND_COMPONENTS 不为空, 当对组件的依赖是REQUIRED时,catkin_FIND_REQUIRED为true。遍历所有的组件并对属于catkin的组件执行find_package(),此时find_package会去catkin工作空间的devel/share文件夹中找对应组件的XXXConfig.cmake获取组件信息,例如对于ndt_omp,相应的文件是:
之后将获取的组件信息加入到catkin_INCLUDE_DIRS
、catkin_LIBRARIES
,catkin_LIBRARY_DIRS
环境变量中:
# append component-specific variables to catkin_* variables
list_append_unique(catkin_INCLUDE_DIRS ${${component}_INCLUDE_DIRS})
# merge build configuration keywords with library names to correctly deduplicate
catkin_pack_libraries_with_build_configuration(catkin_LIBRARIES ${catkin_LIBRARIES})
catkin_pack_libraries_with_build_configuration(_libraries ${${component}_LIBRARIES})
list_append_deduplicate(catkin_LIBRARIES ${_libraries})
# undo build configuration keyword merging after deduplication
catkin_unpack_libraries_with_build_configuration(catkin_LIBRARIES ${catkin_LIBRARIES})
list_append_unique(catkin_LIBRARY_DIRS ${${component}_LIBRARY_DIRS})
list(APPEND catkin_EXPORTED_TARGETS ${${component}_EXPORTED_TARGETS})
那么,catkin组件的XXXConfig.cmake文件又是如何生成的呢?依然是通过 catkin_package()
生成的!
拿ndt_omp来说,catkin_package()会使得编译时在catkin工作空间中的devel/share/ndt_omp/cmake/中生成ndt_ompConfig-version.cmake和ndt_ompConfig.cmake。
总结
通过find_package(catkin REQUIRED COMPONENTS XXX) 会找到catkinConfig.cmake文件并执行,在catkinConfig.cmake中又会对REQUIRED COMPONENTS执行find_package(),比如find_package(XXX),这又将
找到XXXConfig.cmake,并从中获取信息放置到catkin_INCLUDE_DIRS、catkin_LIBRARIES,catkin_LIBRARY_DIRS 等环境变量,而XXXConfig.cmake将通过catkin_package()进行生成,catkin_package()中通过参数设置的信息将传入到XXXConfig.cmake。