ROS Package构建过程中的CMakeLists文件分析

转眼间又是一个轮回,2018年6月之后因为一些事情就没有再更新博客,重新写的时候已是2019年的5月,然后2019年6月之后,因为精力投向了其它方面就又停了,现在重新提笔又是5月,只不过是2020年的5月。最近写代码,遇到了一些编译链接上的问题,想写下来。因为这些问题都跟ROS中的构建系统有关,所以首先就找到了官网的教程,以下内容,只是个人对其的翻译和理解。

文章目录

  • 1. 概述
  • 2.文件结构和次序
  • 3.格式说明
    • 3.1 CMake版本
    • 3.2 软件包名称
    • 3.3 搜寻依赖的CMake软件包
    • 3.4 Python模块支持
    • 3.5 消息、服务和动作消息生成器
      • 3.5.1 先决条件
    • 3.6 软件包编译配置文件生成
    • 3.7 指定构建目标
      • 3.7.1 构建可执行目标
      • 3.7.2 构建库目标
      • 3.7.3 构建存在依赖的目标
    • 3.8 代码测试
    • 3.9 软件包部署配置
  • 4. 参考资料

1. 概述

CMakeLists.txt文件作为CMake软件构建系统的切入点,任何使用CMake系统进行构建的软件包都包含一个或多个CMakeLists.txt文件,用以描述如何进行代码构建和部署。而在ROS的catkin工程中使用的CMakeLists.txt文件符合标准的vanilla CMakeLists.txt文件格式,只有些许针对性的限制。

2.文件结构和次序

CMakeLists.txt文件具有固定的格式,甚至有时候自己编写时,其格式所包含的关键指令次序不对,也会引发各种问题。在ROS中,新创建一个包时会附带创建一个CMakeLists.txt,先看一下带有部分自带信息的CMakeLists.txt文件,去掉了暂时用不到指令和说明。

cmake_minimum_required(VERSION 2.8.3)   // 所需要的 CMake 版本

project(yumi_test)                      // 软件包名称

add_compile_options(-std=c++11)         // 使用 C++11 标准进行编译,支持 Kinetic 及之后的版本

find_package(catkin REQUIRED COMPONENTS // 搜索 build 需要的软件包
  geometric_shapes
  moveit_core
  moveit_ros_planning
  moveit_ros_planning_interface
  pcl_conversions
  pcl_ros
  pluginlib
  rosbag
  roscpp
  tf2_eigen
  tf2_geometry_msgs
  tf2_ros
  yumi_hw
  message_generation
)

find_package(Boost REQUIRED system filesystem date_time thread)

# catkin_python_setup()                // python 模块支持

add_message_files(                     // 添加 msg 文件夹中自定义的 msg 消息
   FILES
   Message.msg
 )

 add_service_files(                    // 添加 srv 文件夹中自定义的 srv 服务消息
   FILES
   Service.srv
 )

add_action_files(                      // 添加 action 文件夹中自定义的 action 消息
   FILES
   Action.action
 )

 generate_messages(                    // 消息生成宏命令
   DEPENDENCIES
   tf2_geometry_msgs                   // 消息生成所依赖的其它消息类型
 )

# generate_dynamic_reconfigure_options(// 生成cfg文件夹定义的动态配置参数
#   cfg/DynReconf.cfg
# )

catkin_package(                        // 为软件包生成 CMake 配置文件
  INCLUDE_DIRS include                 // 当前软件包将要导出的头文件存放目录
  LIBRARIES ${PROJECT_NAME}            // 当前软件包将要导出的库
  CATKIN_DEPENDS Boost Eigen geometric_shapes moveit_core moveit_ros_planning 
  moveit_ros_planning_interface pcl_conversions pcl_ros pluginlib rosbag roscpp 
  tf2_eigen tf2_geometry_msgs tf2_ros message_runtime
  DEPENDS system_lib
)

include_directories(                   // 指定额外的头文件位置,当前软件包的头文件位置放在最前面
  include                              // 当前软件的头文件位置
  ${catkin_INCLUDE_DIRS}               // 生成的软件所依赖的头文件的路径变量
)

add_library(${PROJECT_NAME}            // 构建 C++ 库目标
   src/${PROJECT_NAME}/yumi_motion.cpp
 )

add_dependencies(${PROJECT_NAME}       // 为构建的库目标添加依赖
	${${PROJECT_NAME}_EXPORTED_TARGETS} 
	${catkin_EXPORTED_TARGETS}
)

add_executable(${PROJECT_NAME}_node src/yumi_gripper_node.cpp) // 构建可执行目标,加前缀防止命名冲突
add_dependencies(${PROJECT_NAME}_node ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})  //添加依赖
target_link_libraries(${PROJECT_NAME}_node   ${catkin_LIBRARIES} ) // Specify libraries to link a library or executable target against
set_target_properties(${PROJECT_NAME}_node PROPERTIES OUTPUT_NAME node PREFIX "") //修改名称

add_executable(yumi_gripper_test src/yumi_gripper_test.cpp)
target_link_libraries(yumi_gripper_test ${catkin_LIBRARIES} ${Boost_LIBRARIES} )
add_dependencies(yumi_gripper_test ${catkin_EXPORTED_TARGETS})

## Mark executables for installation
# install(TARGETS ${PROJECT_NAME}_node
#   RUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
# )

## Mark libraries for installation
# install(TARGETS ${PROJECT_NAME}
#   ARCHIVE DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
#   LIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
#   RUNTIME DESTINATION ${CATKIN_GLOBAL_BIN_DESTINATION}
# )

## Mark cpp header files for installation
# install(DIRECTORY include/${PROJECT_NAME}/
#   DESTINATION ${CATKIN_PACKAGE_INCLUDE_DESTINATION}
#   FILES_MATCHING PATTERN "*.h"
#   PATTERN ".svn" EXCLUDE
# )

## Mark other files for installation (e.g. launch and bag files, etc.)
# install(FILES
#   # myfile1
#   # myfile2
#   DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}
# )

# catkin_add_gtest(${PROJECT_NAME}-test test/test_yumi_motion.cpp)     // 添加测试用可执行目标
# if(TARGET ${PROJECT_NAME}-test)
#   target_link_libraries(${PROJECT_NAME}-test ${PROJECT_NAME})
# endif()

3.格式说明

3.1 CMake版本

每个CMakeLists.txt文件必须首先声明使用的CMake版本,一般是2.8.3或更高的版本。

cmake_minimum_required(VERSION 2.8.3) 

3.2 软件包名称

指定软件包的名称,在CMake中使用project()功能指定的软件包名称,随后可以在文件中通过${PROJECT_NAME}进行引用。

project(yumi_test)

3.3 搜寻依赖的CMake软件包

搜索构建包所依赖的软件包,首要的依赖包就是catkin,其次所依赖的包可以以组件的形式,写在同一条件指令中。推荐的是在一条指令中添加所有依赖的包,当然也可以分开添加。

find_package(catkin REQUIRED COMPONENTS                        // 搜索 build 需要的软件包
  geometric_shapes
  moveit_core
  moveit_ros_planning
  moveit_ros_planning_interface
  pcl_conversions
  pcl_ros
  pluginlib
  rosbag
  roscpp
  tf2_eigen
  tf2_geometry_msgs
  tf2_ros
  yumi_hw
  message_generation
)

find_package(Boost REQUIRED system filesystem date_time thread) // 分别添加的依赖项

CMake通过find_package搜寻到的依赖包,会生成对应的环境变量供随后的软件包构建使用。这些环境变量描述了软件包所依赖的头文件、源文件和库的路径与位置。生成的环境变量命名上是按_的形式。再详细的就不展开了,掌握到这里不影响使用。

而其中的COMPONENTS组件,设计目的就是用来节省时间的。放到catkinCOMPONENTS里,可以使依赖包的头文件路径、库等信息统一包含到catkin的环境变量中,而不是单独创建环境变量。不过有些库可能需要单独的指令进行搜寻。

3.4 Python模块支持

用来添加python的配置脚本,需要在generate_messages()catkin_package()之前使用。

3.5 消息、服务和动作消息生成器

Messages(.msg),services(.srv) 和 actions(.action) 文件在ROS的软件包使用之前需要特殊的预处理器进行处理。这些处理的宏会将其生成对应于编程所用语言的相关文件,供编程使用。编译系统将使用所有可能的生成器生成对应语言的可用文件。在catkin的构建系统中,定义了三种宏命令,用来分别处理Messages(.msg),services(.srv) 和 actions(.action) 文件。

add_message_files(                     // 添加 msg 文件夹中自定义的 msg 消息
   FILES
   Message.msg
 )

 add_service_files(                    // 添加  srv 文件夹中自定义的 srv 服务消息
   FILES
   Service.srv
 )

add_action_files(                      // 添加  action 文件夹中自定义的 action 消息
   FILES
   Action.action
 )

而要生成对应的可用文件,则必须调用生成命令。如下所示:

 generate_messages(                    // 消息生成宏命令
   DEPENDENCIES
   tf2_geometry_msgs                   // 消息生成所依赖的其它消息类型
 )

3.5.1 先决条件

需要注意的是,以上宏指令的使用,必须满足以下先决条件:

  • 以上宏指令必须在catkin_package()宏指令之前,以确保构建的正常执行
  • catkin_package()宏中的CATKIN_DEPENDS参数必须具有message_runtime这一依赖项
  • find_package()宏指令中必须包含message_generation软件包
  • package.xml文件中包含message_generation的构建依赖和message_runtime的执行依赖
message_generation
message_runtime

3.6 软件包编译配置文件生成

catkin_package()是提供的catkin提供的CMake宏,用来指定编译系统的配置信息,也被用来生成软件包的配置和CMake文件。其必须在add_library()add_ececutable()之前调用。其具有5项可选参数。

  • INCLUDE_DIRS: 软件包将导出的include路径(即头文件放在哪个文件夹之下)
  • LIBRARIES: 软件包将导出的库
  • CATKIN_DEPENDS:依赖的其它catkin projects
  • DEPENDS:依赖的非catkin CMake Projects
  • CFG_EXTRAS:其它的配置信息
    例如:
catkin_package(                        //为软件包生成 CMake 配置文件
 INCLUDE_DIRS include                  //配置软件包中包含的头文件导出路径或位置
 LIBRARIES ${PROJECT_NAME}
 CATKIN_DEPENDS Boost Eigen geometric_shapes moveit_core moveit_ros_planning 
 moveit_ros_planning_interface pcl_conversions pcl_ros pluginlib rosbag roscpp 
 tf2_eigen tf2_geometry_msgs tf2_ros 
 DEPENDS system_lib
)

3.7 指定构建目标

构建目标可以采用多种形式,但通常是以下两种形式之一:

  • 可执行目标
  • 库目标

在设置构建目标之前,需要指定构建目标的代码或库的位置。用以下两条指令:

include_directories(,,……,)       //头文件所在的位置
link_directories(,,……,)          //库文件所在的位置

第一条指令的参数应该是由find_package()所生成的*_INCLUDE_DIRS变量和其它需要包含进来的路径。如果使用catkin和Boost,则第一条指令应该如下所示:

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

第一个参数include表示当前包的include/路径。第二条指令用于包含额外的库路径,并不推荐,因为在find_package()调用时会自动添加。

在构建目标的设置过程中,非常重要的一点是:在CMake的内部,catkin的构建目标名称必须是唯一的,而与构建或安装到的文件夹无关。这也是CMake规定的。 接下来,开始目标构建.

3.7.1 构建可执行目标

这一目标是通过add_executable()功能实现的,例如:

add_executable(yumi_gripper_test src/yumi_gripper_test.cpp)

构建名称为yumi_gripper_test的可执行文件,源文件为当前软件包中的src/yumi_gripper_test.cpp。如果具有多个源文件,可以依次在后面添加。

add_executable(yumi_gripper_test src/yumi_gripper_test.cpp src/first.cpp src/second.cpp)

3.7.2 构建库目标

这一目标是通过add_library()这一CMake功能来实现的,默认构建的为共享库。

add_library(${PROJECT_NAME}
   src/${PROJECT_NAME}/yumi_test.cpp
 )

第一个参数为构建的库名称,第二个参数则是指定构建所用的代码。

3.7.3 构建存在依赖的目标

》》》》如果构建的目标依赖于库目标

则需要通过target_link_libraries()来指定构建的目标所链接到的库。通常这条指令是在目标构建或库构建指令之后添加。

target_link_libraries(yumi_gripper_test ${catkin_LIBRARIES} ${Boost_LIBRARIES})

简单的说就是构建当前目标依赖哪些额外的资源,比如说用C++写了很多个类,想在一个Main文件中使用所有的类来完成一种功能,那每一个类对应的头文件与源文件就可以编译成对应的库,供Main文件生成的执行目标进行链接。

add_library(yumi_control_node src/yumi_control_node.cpp)                                    // 写的类文件
add_dependencies(yumi_control_node ${catkin_EXPORTED_TARGETS})                              // 添加外部依赖,使用了其它包生成的.srv文件
target_link_libraries(yumi_control_node ${catkin_LIBRARIES} ${Boost_LIBRARIES})             // 添加生成链接

add_executable(yumi_test src/yumi_test.cpp)                                                 // 构建使用yumi_control_node类的主文件
add_dependencies(yumi_test ${catkin_EXPORTED_TARGETS})                                      // 添加外部依赖,其中使用了其它包生成的.srv文件
target_link_libraries(yumi_test  yumi_control_node  ${catkin_LIBRARIES} ${Boost_LIBRARIES}) // 添加生成链接,加入由类生成的依赖库

》》》》如果构建的目标依赖于其它目标或软件包所生成的.msg、.srv、.action文件

则需要通过添加明确的依赖关系,以确保正确的构建顺序。这种情况是普遍存在的,除非当前的程序包不使用ROS提供的任何信息格式。

add_dependencies(yumi_gripper_test ${catkin_EXPORTED_TARGETS})

》》》》如果构建的目标依赖于当前软件包所生成的.msg、.srv、.action文件
则需要为当前目标的构建添加明确的依赖关系,以确保需要的.msg、.srv等文件预先进行生成,保证软件包正确的构建顺序。

add_dependencies(yumi_gripper_test ${${PROJECT_NAME}_EXPORTED_TARGETS})

》》》》如果构建的目标以上两种依赖都存在
需要将两种依赖关系,都明确的添加到目标构建的过程中。

add_dependencies(yumi_gripper_test ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})

3.8 代码测试

构建系统中定义了一个专门用于构建测试目标的宏,基于gtest,如下所示:

if(TESTING_ENABLE)
	catkin_add_gtest(gripper_test test/gripper_test.cpp)
endif()

3.9 软件包部署配置

这一部分准备后面再研究,想把自己写的ROS软件包打包成binary的形式,可以在需要使用的电脑上直接部署使用,而不是把整个源代码copy过去。

4. 参考资料

[1] catkin/CMakeLists.txt
[2] 在ROS中功能包中将类的函数定义与声明分开文件写用main.cpp调用如何配置CMakeLists.txt
[3] ROS下的CMakeLists.txt编写

你可能感兴趣的:(ROS)