ROS Catkin 教程之 CMakeLists.txt

1. 概览

CMakeLists.txt 是用 CMake 构建系统构建 ROS 程序包的输入文件。任何兼容 CMake 的包都包含一个或多个 CMakeLists.txt 文件,用以描述怎样构建和安装代码。catkin 项目采用标准的 vanilla CMakeLists.txt 文件,并带有一些额外的约束。

2. 总体结构和顺序

你的 CMakeLists.txt 文件必须遵从以下格式,否则你的程序包将无法正确编译。这些配置命令是有顺序的。

  1. Required CMake Version (cmake_minimum_required)
  2. Package Name (project())
  3. Find other CMake/Catkin packages needed for build (find_package())
  4. Enable Python module support (catkin_python_setup())
  5. Message/Service/Action Generators (add_message_files(), add_service_files(), add_action_files())
  6. Invoke message/service/action generation (generate_messages())
  7. Specify package build info export (catkin_package())
  8. Libraries/Executables to build (add_library()/add_executable()/target_link_libraries())
  9. Tests to build (catkin_add_gtest())
  10. Install rules (install())

3. CMake Version

每个 catkin CMakeLists.txt 文件必须以所需 CMake 最低版本开头。 Catkin 需要 2.8.3 或更高版本。

cmake_minimum_required(VERSION 2.8.3)

    
    
    
    
  • 1

4. Package name

接下来一项是程序包名,由 CMake 的 project 函数指定。比如说构建一个 robot_brain 的程序包:

project(robot_brain)

    
    
    
    
  • 1

注意,在 CMake 中,如果有需要,你可以使用变量 ${PROJECT_NAME} 在脚本的任意地方引用项目名。

5. 查找依赖的 CMake 程序包

然后我们需要使用 find_package 函数指出在构建我们的项目前需要查找哪些 CMake 程序包。我们总是至少有一个依赖项,那就是 catkin:

find_package(catkin REQUIRED)

    
    
    
    
  • 1

如果你的项目还依赖于其他的包,他们会自动变成 catkin 的组件(就 CMake 而言)。将那些包指定为组件,而不是对其使用 find_package,会使过程变得更简单。例如,如果你使用了 nodelet 程序包:

find_package(catkin REQUIRED COMPONENTS nodelet)

    
    
    
    
  • 1

注意:你只需要找出在构建目标时依赖的组件,而不需要找出运行时依赖。

你也可以使用下面的方式:

find_package(catkin REQUIRED)
find_package(nodelet REQUIRED)

    
    
    
    
  • 1
  • 2

但是,你会发现这样颇有不便。

5.1 find_package() 做了什么?

如果通过 find_package 查找到某个程序包,其结果就是创建一些 CMake 环境变量,这些变量给出了有关找到的程序包的信息。接下来可以在 CMake 脚本中使用这些环境变量。这些环境变量描述了程序包的外部头文件的位置,程序包所依赖于哪些库,以及库所在的路径。变量的命名规则通常为 _

  • _FOUND - 如果找到库则设置为真
  • _INCLUDE_DIRS 或 _INCLUDES - 程序包导出的包含路径
  • _LIBRARIES 或 _LIBS - 程序包导出的库路径
  • _DEFINITIONS - ?

5.2 为什么将程序包指定为组件?

Catkin 程序包并不真的是 catkin 的组件,相反,使用 CMake 组件功能这种设计简化了你的输入过程。

对于 catkin 程序包,将他们作为 catkin 的组件是有好处的,因为可以使用 catkin_ 前缀统一创建一组环境变量。假如你在代码中用到了程序包 nodelet,查找依赖包的建议方法如下:

find_package(catkin REQUIRED COMPONENTS nodelet)

    
    
    
    
  • 1

这意味着 nodelet 导出的包含路径、库路径等也会附加到 catkin_ 变量。例如,catkin_INCLUDE_DIRS 变量不仅包含了 catkin 的相关路径,也包含了 nodelet 的相关路径。这将在之后派上用场。

我们也可以单独对 nodelet 应用 find_package:

find_package(nodelet)

    
    
    
    
  • 1

5.3 Boost

如果使用 C++ 和 Boost,则需要在 Boost 上调用 find_package(),并指出你正在使用 Boost 哪些方面的组件。例如你想使用 Boost threads,你需要这样写:

find_package(Boost REQUIRED COMPONENTS thread)

    
    
    
    
  • 1

6. catkin_package()

catkin_package() 是一个由 catkin 提供的宏。这是用来指定与编译系统有关的 catkin 专用信息的,这些信息被用来生成 pkg-config 和 CMake 文件。

此函数必须在用 add_library() 或 add_executable() 生成任何目标前调用。该函数具有 5 个可选参数:

  • INCLUDE_DIRS - 包的导出包含路径
  • LIBRARIES - 从包导出的库
  • CATKIN_DEPENDS - 此项目依赖的其他 catkin 项目
  • DEPENDS - 此项目依赖的非 catkin 的 CMake 项目。若要详细理解,请查看这篇解释
  • CFG_EXTRAS - 其他选项

可到此处查看关于全部宏的文档。

举个例子:

catkin_package(
   INCLUDE_DIRS include
   LIBRARIES ${PROJECT_NAME}
   CATKIN_DEPENDS roscpp nodelet
   DEPENDS eigen opencv)

    
    
    
    
  • 1
  • 2
  • 3
  • 4
  • 5

这表示头文件将导出到程序包文件夹中的“include”文件夹。其中 CMake 环境变量 ${PROJECT_NAME} 的值为你先前传递给 project() 函数的值。“roscpp” + “nodelet” 是构建/运行此程序包时需要存在的包,“eigen” + “opencv” 是构建/运行此程序包时需存在的系统依赖项。

7. 指定构建目标

构建目标有多种形式,但是通常是以下两种可能中的一个:

  • 可执行目标 - 可以运行的程序
  • 库目标 - 可执行目标在构建和/或运行时可以使用的库

7.1 目标命名

要强调的是,catkin 中的构建目标的名称必须是唯一的,无论它们是构建/安装到哪个文件夹。这是 CMake 的一项要求。然而目标名称的唯一性只是在 CMake 内部需要,你可以使用 set_target_properties() 来重命名目标,例如:

set_target_properties(rviz_image_view
                      PROPERTIES OUTPUT_NAME image_view
                      PREFIX "")

    
    
    
    
  • 1
  • 2
  • 3

这将在构建和安装输出中把目标名称从 rviz_image_view 改为 image_view

7.2 自定义输出目录

虽然可执行目标和库目标的默认输出目录通常被设定为合适的值,但在特定情况下可能需要重新设定。例如一个包含 Python 绑定的库必须放在不同的文件夹中,以便可以在 Python 中导入。例如:

set_target_properties(python_module_library
  PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CATKIN_DEVEL_PREFIX}/${CATKIN_PACKAGE_PYTHON_DESTINATION})

    
    
    
    
  • 1
  • 2

7.3 包含路径和库路径

在指定目标前,你需要为目标指出可找到的资源的位置,特别是头文件和库:

  • Include Paths - 要构建的代码(通常为 C/C++)的头文件的位置
  • Library Paths - 构建可执行目标所需的库的位置
  • include_directories(, , …, )
  • link_directories(, , …, )

7.3.1 include_directories()

include_directories 的参数应该是由调用 find_package 产生的 *_INCLUDE_DIRS 变量,以及其他需要包含的目录。如果你使用了 catkin 和 Boost,你的 include_directories() 调用看起来如下所示:

include_directories(include ${Boost_INCLUDE_DIRS} ${catkin_INCLUDE_DIRS})

    
    
    
    
  • 1

第一个参数“include”表示程序包的 include/ 目录也是包含路径的一部分。

7.3.2 link_directories()

CMake link_directories() 函数可以被用来添加额外的库路径,然而并不建议这样做。所有的 catkin 和 CMake 程序包的连接信息已经在使用 find_package 时添加。只需要在 target_link_libraries() 中连接这些库就行了。

例如:

link_directories(~/my_libs)

    
    
    
    
  • 1

7.4 可执行目标

要指定必须构建的可执行目标,我们必须使用 add_executable() CMake 函数。

add_executable(myProgram src/main.cpp src/some_file.cpp src/another_file.cpp)

    
    
    
    
  • 1

这将构建一个名为 myProgram 的目标可执行文件,它由 3 个源文件构建而来:src/main.cpp,src/some_file.cpp 和 src/another_file.cpp。

7.5 库目标

add_library() 函数用于指定要构建的库目标。默认情况下 catkin 生成共享库。

add_library(${PROJECT_NAME} ${${PROJECT_NAME}_SRCS})

    
    
    
    
  • 1

7.6 target_link_libraries

使用 target_link_libraries() 函数指定可执行目标要链接的库。 这通常在 add_executable() 调用之后使用。 如果找不到ros,请添加$ {catkin_LIBRARIES}。

语法:

target_link_libraries(, , , ... )

    
    
    
    
  • 1

示例:

add_executable(foo src/foo.cpp)
add_library(moo src/moo.cpp)
target_link_libraries(foo moo)  -- This links foo against libmoo.so

    
    
    
    
  • 1
  • 2
  • 3

注意,在大多数用例中是不需要使用 link_directories(),因为相关信息已经通过 find_package() 自动添加。

8. Messages,Services,和 Action 目标

在被 ROS 程序包构建和使用之前,ROS 中的 Messages (.msg),services (.srv) 和 actions (.action) 文件需要特殊的预处理构建步骤。这些宏的关键作用是生成由特定编程语言编写的文件,以便可以在所选编程语言中使用这些 Messages,services 和 actions。构建系统将使用所有可用的生成器(例如 gencpp,genpy,genlisp 等)生成绑定。

有三个宏来分别处理 messages,services 和 actions:

  • add_message_files
  • add_service_files
  • add_action_files

在调用上述宏之后必须调用下边的宏:

generate_messages()

    
    
    
    
  • 1

8.1 重要的先决条件/限制

  • 这些宏必须位于 catkin_package() 宏之前,以便生成过程正常工作。
 find_package(catkin REQUIRED COMPONENTS ...)
 add_message_files(...)
 add_service_files(...)
 add_action_files(...)
 generate_messages(...)
 catkin_package(...)
 ...

    
    
    
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 你的 catkin_package() 宏参数必须有一个 CATKIN_DEPENDS 依赖项 message_runtime 。
catkin_package(
 ...
 CATKIN_DEPENDS message_runtime ...
 ...)

    
    
    
    
  • 1
  • 2
  • 3
  • 4
  • 你必须为 messagese_generation 使用 find_package() ,可以单独使用,也可以作为 catkin 的一个组件使用:
find_package(catkin REQUIRED COMPONENTS message_generation)

    
    
    
    
  • 1
  • package.xml 文件必须包含对 message_generation 的 build 依赖以及对 message_runtime 的 runtime 依赖。 如果依赖关系是从其他包传递的,那么这不是必需的。

  • 如果你有一个目标依赖于(甚至传递地依赖于)需要构建 messages/services/actions 文件的其他目标,则需要对目标 catkin_EXPORTED_TARGETS 添加显式依赖,以便它们以正确的顺序构建。 除非你的软件包确实不使用 ROS 的任何部分,否则这种情况几乎总是适用。不幸的是,这种依赖关系不能自动传播。 示例如下(some_target 是 add_executable() 设置的目标的名称):

add_dependencies(some_target ${catkin_EXPORTED_TARGETS})

    
    
    
    
  • 1
  • 如果你的程序包即要构建 messages 和/或 services,又要构建使用它们的可执行文件,则需要在自动生成的消息目标上创建显式依赖项,以便以正确的顺序构建它们。 示例如下(some_target 是 add_executable() 设置的目标的名称):
add_dependencies(some_target ${${PROJECT_NAME}_EXPORTED_TARGETS})

    
    
    
    
  • 1
  • 如果你的包装满足上述两个条件,则需要添加两个依赖关系,即:
add_dependencies(some_target ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})

    
    
    
    
  • 1

8.2 示例

如果你的程序包在 “msg” 目录中有两条 messages,分别名为 “MyMessage1.msg” 和 “MyMessage2.msg”,并且这些消息依赖于 std_msgs 和 sensor_msgs,在 “srv” 的目录中的有一个 service 名为 “MyService.srv”, 定义使用这些 messages 和 service 的可执行文件 message_program,以及可执行文件 do_not_use_local_messages_program,它使用 ROS 的某些部分,但不使用此程序包中定义的messages/service,那么你需要在 CMakeLists.txt 中使用以下内容:

  # Get the information about this package's buildtime dependencies
  find_package(catkin REQUIRED
    COMPONENTS message_generation std_msgs sensor_msgs)

Declare the message files to be built

add_message_files(FILES
MyMessage1.msg
MyMessage2.msg
)

Declare the service files to be built

add_service_files(FILES
MyService.srv
)

Actually generate the language-specific message and service files

generate_messages(DEPENDENCIES std_msgs sensor_msgs)

Declare that this catkin package’s runtime dependencies

catkin_package(
CATKIN_DEPENDS message_runtime std_msgs sensor_msgs
)

define executable using MyMessage1 etc.

add_executable(message_program src/main.cpp)
add_dependencies(message_program KaTeX parse error: Expected '}', got 'EOF' at end of input: {{PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})

define executable not using any messages/services provided by this package

add_executable(does_not_use_local_messages_program src/main.cpp)
add_dependencies(does_not_use_local_messages_program ${catkin_EXPORTED_TARGETS})

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

另外,如果要构建 actionlib 动作,并在 “action” 目录中有一个名为 “MyAction.action” 的 action 规范文件,则必须将 actionlib_msgs 添加到使用 catkin find_packaged 的组件列表中,并在调用 generate_messages() 前添加以下调用:

add_action_files(FILES
  MyAction.action
)

   
   
   
   
  • 1
  • 2
  • 3

此外,package.xml 中必须具有对 actionlib_msgs 的 build 依赖。

9. 使能 Python 支持

如果你的 ROS 程序包提供一些 Python 模块,你必须创建 setup.py 文件,并调用

catkin_python_setup()

   
   
   
   
  • 1

10. 单元测试

有一个 catkin 专用的宏用于处理基于 gtest 的单元测试,称为 catkin_add_gtest()。

if(CATKIN_ENABLE_TESTING)
  catkin_add_gtest(myUnitTest test/utest.cpp)
endif()

   
   
   
   
  • 1
  • 2
  • 3

11. 可选步骤:指定可安装目标

构建之后,目标文件被放在 catkin 工作空间的 devel 空间中。然而,我们通常希望将目标文件安装到系统中以使其他人可以使用,或者安装到一个本地文件夹中以测试系统级的安装。换句话说,如果你想对你的代码进行 “make install” 操作,你需要指定目标文件应当被安装到哪个位置。

这通过使用 CMake install() 函数完成,带有以下参数:

  • TARGET - 目标被安装到哪里
  • ARCHIVE DESTINATION - 静态库和 DLL(Windows).lib 存根
  • LIBRARY DESTINATION - 非 DLL 共享库和模块
  • RUNTIME DESTINATION - 可执行目标文件和 DLL(Windows)格式共享库

示例如下:

install(TARGETS ${PROJECT_NAME}
  ARCHIVE DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
  LIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
  RUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
)

   
   
   
   
  • 1
  • 2
  • 3
  • 4
  • 5

除了这些标准目标目录,一些文件需要被安装到特殊的文件夹。例如包含 Python 绑定的库必须安装到一个不同的文件夹才能在 Python 中导入:

install(TARGETS python_module_library
  ARCHIVE DESTINATION ${CATKIN_PACKAGE_PYTHON_DESTINATION}
  LIBRARY DESTINATION ${CATKIN_PACKAGE_PYTHON_DESTINATION}
)

   
   
   
   
  • 1
  • 2
  • 3
  • 4

11.1 安装 Python 可执行脚本

对于 Python 代码,由于没有使用 add_library() 和 add_executable() 函数,CMake 无法确定哪些是目标文件以及目标文件类型,所以安装规则有所不同。这种情况下,在 CMakeLists.txt 文件中使用以下内容:

catkin_install_python(PROGRAMS scripts/myscript
  DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION})

   
   
   
   
  • 1
  • 2

有关 python 脚本安装的详细信息,以及更好地实践文件系统分层,请参考 catkin 手册。

如果你只是安装 Python 脚本而不提供任何模块,则既不需要创建上面提到的 setup.py 文件,也不需要调用 catkin_python_setup()。

11.2 安装头文件

头文件也应该被安装到“include”文件夹中,这通常是通过安装整个文件夹的文件(可按文件名过滤并排除 SVN 子文件夹)来完成的。可以通过如下所示的安装规则来完成:

install(DIRECTORY include/${PROJECT_NAME}/
  DESTINATION ${CATKIN_PACKAGE_INCLUDE_DESTINATION}
  PATTERN ".svn" EXCLUDE
)

   
   
   
   
  • 1
  • 2
  • 3
  • 4

或者如果 include 文件夹下的子文件夹和程序包名不匹配的话:

install(DIRECTORY include/
  DESTINATION ${CATKIN_GLOBAL_INCLUDE_DESTINATION}
  PATTERN ".svn" EXCLUDE
)

   
   
   
   
  • 1
  • 2
  • 3
  • 4

11.3 安装 roslaunch 文件和其他资源

其他资源,例如启动文件可以被安装到 ${CATKIN_PACKAGE_SHARE_DESTINATION}:

install(DIRECTORY launch/
  DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}/launch
  PATTERN ".svn" EXCLUDE)

   
   
   
   
  • 1
  • 2
  • 3
                                

你可能感兴趣的:(ROS)