转眼间又是一个轮回,2018年6月之后因为一些事情就没有再更新博客,重新写的时候已是2019年的5月,然后2019年6月之后,因为精力投向了其它方面就又停了,现在重新提笔又是5月,只不过是2020年的5月。最近写代码,遇到了一些编译链接上的问题,想写下来。因为这些问题都跟ROS中的构建系统有关,所以首先就找到了官网的教程,以下内容,只是个人对其的翻译和理解。
CMakeLists.txt文件作为CMake软件构建系统的切入点,任何使用CMake系统进行构建的软件包都包含一个或多个CMakeLists.txt文件,用以描述如何进行代码构建和部署。而在ROS的catkin工程中使用的CMakeLists.txt文件符合标准的vanilla CMakeLists.txt文件格式,只有些许针对性的限制。
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()
每个CMakeLists.txt文件必须首先声明使用的CMake版本,一般是2.8.3或更高的版本。
cmake_minimum_required(VERSION 2.8.3)
指定软件包的名称,在CMake中使用project()
功能指定的软件包名称,随后可以在文件中通过${PROJECT_NAME}
进行引用。
project(yumi_test)
搜索构建包所依赖的软件包,首要的依赖包就是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
组件,设计目的就是用来节省时间的。放到catkin
的COMPONENTS
里,可以使依赖包的头文件路径、库等信息统一包含到catkin
的环境变量中,而不是单独创建环境变量。不过有些库可能需要单独的指令进行搜寻。
用来添加python的配置脚本,需要在generate_messages()
和catkin_package()
之前使用。
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 // 消息生成所依赖的其它消息类型
)
需要注意的是,以上宏指令的使用,必须满足以下先决条件:
catkin_package()
宏指令之前,以确保构建的正常执行catkin_package()
宏中的CATKIN_DEPENDS
参数必须具有message_runtime
这一依赖项find_package()
宏指令中必须包含message_generation
软件包package.xml
文件中包含message_generation
的构建依赖和message_runtime
的执行依赖message_generation
message_runtime
catkin_package()
是提供的catkin提供的CMake宏,用来指定编译系统的配置信息,也被用来生成软件包的配置和CMake文件。其必须在add_library()
和add_ececutable()
之前调用。其具有5项可选参数。
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
)
构建目标可以采用多种形式,但通常是以下两种形式之一:
在设置构建目标之前,需要指定构建目标的代码或库的位置。用以下两条指令:
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规定的。 接下来,开始目标构建.
这一目标是通过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)
这一目标是通过add_library()
这一CMake功能来实现的,默认构建的为共享库。
add_library(${PROJECT_NAME}
src/${PROJECT_NAME}/yumi_test.cpp
)
第一个参数为构建的库名称,第二个参数则是指定构建所用的代码。
》》》》如果构建的目标依赖于库目标
则需要通过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})
构建系统中定义了一个专门用于构建测试目标的宏,基于gtest,如下所示:
if(TESTING_ENABLE)
catkin_add_gtest(gripper_test test/gripper_test.cpp)
endif()
这一部分准备后面再研究,想把自己写的ROS软件包打包成binary的形式,可以在需要使用的电脑上直接部署使用,而不是把整个源代码copy过去。
[1] catkin/CMakeLists.txt
[2] 在ROS中功能包中将类的函数定义与声明分开文件写用main.cpp调用如何配置CMakeLists.txt
[3] ROS下的CMakeLists.txt编写