catkin_package到底干了什么

Posted 奇妙之二进制

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了catkin_package到底干了什么相关的知识,希望对你有一定的参考价值。

参考:
https://answers.ros.org/question/58498/what-is-the-purpose-of-catkin_depends/
https://docs.ros.org/en/api/catkin/html/dev_guide/generated_cmake_api.html#catkin-package

我们在一些ros功能包的CMakeLists文件中经常可以看见catkin_package

catkin_package(
  CATKIN_DEPENDS
    diagnostic_updater
    dynamic_reconfigure
    geometry_msgs
    nav_msgs
    rosbag
    roscpp
    sensor_msgs
    std_srvs
    tf2
    tf2_msgs
    tf2_ros
  INCLUDE_DIRS include
  LIBRARIES amcl_sensors amcl_map amcl_pf
)

catkin_package是给下游(使用该包的包)使用的,它用于向其他包导出依赖,这些依赖可能是本包给其他包提供的公共头文件、库,或者是本包依赖的其他包。

它有5个选项:

## The catkin_package macro generates cmake config files for your package
## Declare things to be passed to dependent projects
## INCLUDE_DIRS: uncomment this if you package contains header files
## LIBRARIES: libraries you create in this project that dependent projects also need
## CATKIN_DEPENDS: catkin_packages dependent projects also need
## DEPENDS: system dependencies of this project that dependent projects also need
## CFG_EXTRAS: Additional configuration options
catkin_package(...

DEPENDSCATKIN_DEPENDS 用来告诉 catkin 需要将你程序包的哪些依赖项,传递给使用 find_package(...) 查找你的程序包的程序包。

例如,假设你调用 find_package(Boost REQUIRED),并在你的安装的头文件中 #include <boost/function.hpp>。为使一个依赖于你的程序包的程序包能构建和连接你的头文件,它们需要在自己的 include 路径中包含 Boost 的 include 目录,还需要连接 Boost 的库。

由于你已经在头文件中导出了该依赖,它们应该能从你那里获得依赖。也就是它们不再需要 find_package(Boost REQUIRED) ,因为它们是使用你的包构建的,而不是直接使用 Boost。

你的程序包依赖于 Boost 这一事实是一个实现细节,因此当一些包通过 find_package(...) 查找你的包时,它们能间接获得对 Boost 的依赖。让这种机制起作用方法是在你的 catkin_package(...) 调用中加入 DEPENDS Boost。在内部,catkin 将 find_package(Boost),并向 $your_pkg_LIBRARIES 添加 $Boost_LIBRARIES ,向 $your_pkg_INCLUDE_DIRS 添加 $Boost_INCLUDE_DIRS。因此其他包,只需要链接$your_pkg_INCLUDE_DIRS$your_pkg_LIBRARIES即可获取传递的依赖。

我们应该注意,catkin 将你告诉它的确切的程序包名传递给find_package() 来查找该包, 然后尝试使用该包的 _LIBRARIES_INCLUDE_DIRS 变量。但是 find_package(...) 得到的结果变量的形式并不总是这样,因为 CMake 没有强制执行此操作。例如当 find_package(...) Python 时,find_package(PythonLibs REQUIRED) 的结果变量的形式为 PYTHON_INCLUDE_PATHfind_package(OpenGL REQUIRED) 的结果变量为 OPENGL_INCLUDE_DIR。除了变量前缀变得不一样(PythonLibs -> PYTHON),后缀也变得不标准(PYTHON_INCLUDE_PATH and OPENGL_INCLUDE_DIR vs *_INCLUDE_DIRS)。在这种情况下你就需要使用 INCLUDE_DIRS 选项和 LIBRARIES 选项了。另外,自己对外暴露的头文件、库也要通过选项INCLUDE_DIRS、LIBRARIES 传递出去(见下面的例子foo)。

CATKIN_DEPENDS 选项和 DEPENDS 选项十分相似,但是你只能在其列表中放置 catkin 程序包。将 catkin 依赖设置为一个单独的选项的好处是可以让 catkin 执行一些额外的检查,然后警告你有什么不妥的做法。

cmake_minimum_required(VERSION 2.8.3)
project(foo)

find_package(Boost REQUIRED COMPONENTS
  system
  thread
)

find_package(PythonLibs REQUIRED)
find_package(OpenGL REQUIRED)

find_package(catkin REQUIRED COMPONENTS
  rosconsole
  roscpp
)

include_directories(
  include
  $catkin_INCLUDE_DIRS
  $OPENGL_INCLUDE_DIR
  $PYTHON_INCLUDE_PATH
)

catkin_package(
  INCLUDE_DIRS include $OPENGL_INCLUDE_DIR
  LIBRARIES foo $OPENGL_LIBRARIES
  CATKIN_DEPENDS roscpp
  DEPENDS Boost
)

...

此例中你可以看到我 find_package(Boost...) 并将它传递给 catkin_package()DEPENDS 部分,因为它生成的是兼容 CMake 的变量。我 find_package(PythonLibs...) 并在内部使用它,但是不用将它传递给 catkin_package(),因为我没有在我任何导出的头文件中包含它。我 find_package(OpenGL...) ,由于它不是兼容 CMake 的变量,所以我将其显示地传递给 catkin_package()NCLUDE_DIRSLIBRARIES 部分。最后,我 find_package(catkin ... rosconsole roscpp,并在内部使用,但是我可能只在 .c* 文件中使用了 rosconsole,因此我不用传递它,所以 catkin_package()CATKIN_DEPENDS 部分我只需放入 roscpp。

最后要说明但是,如果一个程序包有直接使用像 Boost 这样的依赖项,那么它们应该确保用 find_package(...) 显示地查找它,而不是通过其他包隐式地依赖于它。举个这样的例子,如果程序包 foo 将 Boost 作为依赖项导出,又有程序包 bar 依赖于 foo,但也在内部使用 Boost,那么 bar 即使在没有显示依赖 Boost 的情况下也能编译正常。 但后来 foo 可能决定重构并删除了它对 Boost 的依赖,那么现在 bar 将无法编译,因为它不再具有通过 foo 传递来的对 Boost 的隐式依赖。

我们上面只是讨论了catkin_package的用途,并没有详细讲述它是如何起作用的。catkin_package调用之后会在安装目录/share/<project_name>/cmake目录下生成cmake config模式的查找文件:<project_name>Config.cmake, <project_name>-version.cmake。这样子其他包调用find_package的时候就会加载这些配置文件,从而得到该有的依赖。

生成的xxConfig.cmake文件主要是处理上面的几个选项,库、头文件,依赖,将搜索结果追加到<project_name>_LIBRARY_DIRS,<project_name>__INCLUDE_DIRS变量。

附录一个xxConfig.cmake文件的例子,它是根据catkin/cmake/template/pkgConfig.cmake.in模板生成的。我的包名叫test。

# generated from catkin/cmake/template/pkgConfig.cmake.in

# append elements to a list and remove existing duplicates from the list
# copied from catkin/cmake/list_append_deduplicate.cmake to keep pkgConfig
# self contained
macro(_list_append_deduplicate listname)
  if(NOT "$ARGN" STREQUAL "")
    if($listname)
      list(REMOVE_ITEM $listname $ARGN)
    endif()
    list(APPEND $listname $ARGN)
  endif()
endmacro()

# append elements to a list if they are not already in the list
# copied from catkin/cmake/list_append_unique.cmake to keep pkgConfig
# self contained
macro(_list_append_unique listname)
  foreach(_item $ARGN)
    list(FIND $listname $_item _index)
    if(_index EQUAL -1)
      list(APPEND $listname $_item)
    endif()
  endforeach()
endmacro()


# pack a list of libraries with optional build configuration keywords
# copied from catkin/cmake/catkin_libraries.cmake to keep pkgConfig
# self contained
macro(_pack_libraries_with_build_configuration VAR)
  set($VAR "")
  set(_argn $ARGN)
  list(LENGTH _argn _count)
  set(_index 0)
  while($_index LESS $_count)
    list(GET _argn $_index lib)
    if("$lib" MATCHES "^(debug|optimized|general)$")
      math(EXPR _index "$_index + 1")
      if($_index EQUAL $_count)
        message(FATAL_ERROR "_pack_libraries_with_build_configuration() the list of libraries '$ARGN' ends with '$lib' which is a build configuration keyword and must be followed by a library")
      endif()
      list(GET _argn $_index library)
      list(APPEND $VAR "$lib$CATKIN_BUILD_CONFIGURATION_KEYWORD_SEPARATOR$library")
    else()
      list(APPEND $VAR "$lib")
    endif()
    math(EXPR _index "$_index + 1")
  endwhile()
endmacro()

# unpack a list of libraries with optional build configuration keyword prefixes
# copied from catkin/cmake/catkin_libraries.cmake to keep pkgConfig
# self contained
macro(_unpack_libraries_with_build_configuration VAR)
  set($VAR "")
  foreach(lib $ARGN)
    string(REGEX REPLACE "^(debug|optimized|general)$CATKIN_BUILD_CONFIGURATION_KEYWORD_SEPARATOR(.+)$" "\\\\1;\\\\2" lib "$lib")
    list(APPEND $VAR "$lib")
  endforeach()
endmacro()


if(test_CONFIG_INCLUDED)
  return()
endif()
set(test_CONFIG_INCLUDED TRUE)

# set variables for source/devel/install prefixes
if("FALSE" STREQUAL "TRUE")
  set(test_SOURCE_PREFIX /data/src/utils/test)
  set(test_DEVEL_PREFIX /data/devel_buzzard_isolated/test)
  set(test_INSTALL_PREFIX "")
  set(test_PREFIX $test_DEVEL_PREFIX)
else()
  set(test_SOURCE_PREFIX "")
  set(test_DEVEL_PREFIX "")
  set(test_INSTALL_PREFIX /data/install_buzzard_isolated)
  set(test_PREFIX $test_INSTALL_PREFIX)
endif()

# warn when using a deprecated package
if(NOT "" STREQUAL "")
  set(_msg "WARNING: package 'test' is deprecated")
  # append custom deprecation text if available
  if(NOT "" STREQUAL "TRUE")
    set(_msg "$_msg ()")
  endif()
  message("$_msg")
endif()

# flag project as catkin-based to distinguish if a find_package()-ed project is a catkin project
set(test_FOUND_CATKIN_PROJECT TRUE)

if(NOT "include " STREQUAL " ")
  set(test_INCLUDE_DIRS "")
  set(_include_dirs "include")
  if(NOT " " STREQUAL " ")
    set(_report "Check the issue tracker '' and consider creating a ticket if the problem has not been reported yet.")
  elseif(NOT " " STREQUAL " ")
    set(_report "Check the website '' for information and consider reporting the problem.")
  else()
    set(_report "Report the problem to the maintainer 'Jiaheng Hong <hongjiaheng@syriusrobotics.com>' and request to fix the problem.")
  endif()
  foreach(idir $_include_dirs)
    if(IS_ABSOLUTE $idir AND IS_DIRECTORY $idir)
      set(include $idir)
    elseif("$idir " STREQUAL "include ")
      get_filename_component(include "$test_DIR/../../../include" ABSOLUTE)
      if(NOT IS_DIRECTORY $include)
        message(FATAL_ERROR "Project 'test' specifies '$idir' as an include dir, which is not found.  It does not exist in '$include'.  $_report")
      endif()
    else()
      message(FATAL_ERROR "Project 'test' specifies '$idir' as an include dir, which is not found.  It does neither exist as an absolute directory nor in '/data/install_buzzard_isolated/$idir'.  $_report")
    endif()
    _list_append_unique(test_INCLUDE_DIRS $include)
  endforeach()
endif()

set(libraries "")
foreach(library $libraries)
  # keep build configuration keywords, target names and absolute libraries as-is
  if("$library" MATCHES "^(debug|optimized|general)$")
    list(APPEND test_LIBRARIES $library)
  elseif(TARGET $library)
    list(APPEND test_LIBRARIES $library)
  elseif(IS_ABSOLUTE $library)
    list(APPEND test_LIBRARIES $library)
  else()
    set(lib_path "")
    set(lib "$library-NOTFOUND")
    # since the path where the library is found is returned we have to iterate over the paths manually
    foreach(path /data/install_buzzard_isolated/lib;/data/install_buzzard_isolated/lib)
      find_library(lib $library
        PATHS $path
        NO_DEFAULT_PATH NO_CMAKE_FIND_ROOT_PATH)
      if(lib)
        set(lib_path $path)
        break()
      endif()
    endforeach()
    if(lib)
      _list_append_unique(test_LIBRARY_DIRS $lib_path)
      list(APPEND test_LIBRARIES $lib)
    else()
      # as a fall back for non-catkin libraries try to search globally
      find_library(lib $library)
      if(NOT lib)
        message(FATAL_ERROR "Project '$PROJECT_NAME' tried to find library '$library'.  The library is neither a target nor built/installed properly.  Did you compile project 'test'?  Did you find_package() it before the subdirectory containing its code is included?")
      endif()
      list(APPEND test_LIBRARIES $lib)
    endif()
  endif()
endforeach()

set(test_EXPORTED_TARGETS "")
# create dummy targets for exported code generation targets to make life of users easier
foreach(t $test_EXPORTED_TARGETS)
  if(NOT TARGET $t)
    add_custom_target($t)
  endif()
endforeach()

set(depends "glog_vendor;grpc_vendor")
foreach(depend $depends)
  string(REPLACE " " ";" depend_list $depend)
  # the package name of the dependency must be kept in a unique variable so that it is not overwritten in recursive calls
  list(GET depend_list 0 test_dep)
  list(LENGTH depend_list count)
  if($count EQUAL 1)
    # simple dependencies must only be find_package()-ed once
    if(NOT $test_dep_FOUND)
      find_package($test_dep REQUIRED NO_MODULE)
    endif()
  else()
    # dependencies with components must be find_package()-ed again
    list(REMOVE_AT depend_list 0)
    find_package($test_dep REQUIRED NO_MODULE $depend_list)
  endif()
  _list_append_unique(test_INCLUDE_DIRS $$test_dep_INCLUDE_DIRS)

  # merge build configuration keywords with library names to correctly deduplicate
  _pack_libraries_with_build_configuration(test_LIBRARIES $test_LIBRARIES)
  _pack_libraries_with_build_configuration(_libraries $$test_dep_LIBRARIES)
  _list_append_deduplicate(test_LIBRARIES $_libraries)
  # undo build configuration keyword merging after deduplication
  _unpack_libraries_with_build_configuration(test_LIBRARIES $test_LIBRARIES)

  _list_append_unique(test_LIBRARY_DIRS $$test_dep_LIBRARY_DIRS)
  list(APPEND test_EXPORTED_TARGETS $$test_dep_EXPORTED_TARGETS)
endforeach()

set(pkg_cfg_extras "")
foreach(extra $pkg_cfg_extras)
  if(NOT IS_ABSOLUTE $extra)
    set(extra $test_DIR/$extra)
  endif()
  include($extra)
endforeach()

xxConfig-version.cmake文件:

# generated from catkin/cmake/template/pkgConfig-version.cmake.in
set(PACKAGE_VERSION "1.0.0")

set(PACKAGE_VERSION_EXACT False)
set(PACKAGE_VERSION_COMPATIBLE False)

if("$PACKAGE_FIND_VERSION" VERSION_EQUAL "$PACKAGE_VERSION")
  set(PACKAGE_VERSION_EXACT True)
  set(PACKAGE_VERSION_COMPATIBLE True)
endif()

if("$PACKAGE_FIND_VERSION" VERSION_LESS "$PACKAGE_VERSION")
  set(PACKAGE_VERSION_COMPATIBLE True)
endif()

注意:This function must be called before declaring any targets with add_library() or add_executable()

上面遗漏了一个选项没有说明,CFG-EXTRAS,这里补充说明。
待写。

以上是关于catkin_package到底干了什么的主要内容,如果未能解决你的问题,请参考以下文章

vue到底干了些什么,别大意,超详细解读

Netty进阶篇3:Channel到底干了些什么?

Java动态代理Proxy.newProxyInstance源码到底干了什么?2021-8-31

揭秘:NFT智能合约到底都干了什么?

官方新出的 Kotlin 扩展库 KTX,到底帮你干了什么?

一次触摸,Android到底干了啥