ROS2概念系列(八):文件系统深度解析


目录

一、工作空间workspace

1.1 初始化工作空间

mkdir -p ~/dev_ws/src

1.2 文件结构介绍

ROS2工作空间的文件结构由四个目录组成,分别是build/install/src/log/其中src/目录下存放最小ROS2构建部件成为功能包package。

ROS2工作空间的文件结构

  • src:代码空间,未来编写的代码、脚本,都需要人为的放置到这里;
  • build:编译空间,保存编译过程中产生的中间文件;
  • install:安装空间,放置编译得到的可执行文件和脚本;
  • log:日志空间,编译和运行过程中,保存各种警告、错误、信息等日志。

总体来讲,这四个空间的文件夹,我们绝大部分操作都是在src中进行的,编译成功后,就会执行install里边的结果,build和log两个文件夹用的很少。

二、软件包package

任何ROS2的代码无论是C++还是Python都要放到package中,这样才能正常的编译和运行。

一个package可以编译出来多个目标文件(可执行程序、动态静态库、头文件等等)。

2.1 package结构

一个package下常见的文件路径有:


  ├── CMakeLists.txt    #package的编译规则(必须)
  ├── package.xml       #package的描述信息(必须)
  ├── src/              #源代码文件
  ├── include/          #C++头文件
  ├── scripts/          #可执行脚本
  ├── msg/              #自定义消息
  ├── srv/              #自定义服务
  ├── models/           #3D模型文件
  ├── urdf/             #urdf文件
  ├── launch/           #launch文件

其中定义package的是CMakeLists.txt和package.xml,这两个文件是package中必不可少的。colcon编译系统在编译前,首先就要解析这两个文件。这两个文件就定义了一个package。

  • CMakeLists.txt:定义package的包名、依赖、源文件、目标文件等编译规则,是package不可少的成分
  • package.xml:描述package的包名、版本号、作者、依赖等信息,是package不可少的成分
  • src/:存放ROS的源代码,包括C++的源码和(.cpp)以及Python的module(.py)
  • include/:存放C++源码对应的头文件
  • scripts/:存放可执行脚本,例如shell脚本(.sh)、Python脚本(.py)
  • msg/:存放自定义格式的消息(.msg)
  • srv/:存放自定义格式的服务(.srv)
  • models/:存放机器人或仿真场景的3D模型(.sda, .stl, .dae等)
  • urdf/:存放机器人的模型描述(.urdf或.xacro)
  • launch/:存放launch文件(.launch或.xml)

通常ROS文件组织都是按照以上的形式,这是约定俗成的命名习惯,建议遵守。以上路径中,只有CMakeLists.txt和package.xml是必须的,其余路径根据软件包是否需要来决定。

2.2 package的创建

创建一个package需要在catkin_ws/src下,用到ros2 pkg命令,用法是:

ros2 pkg create <package-name> --build-type {cmake,ament_cmake,ament_python} --dependencies <dependencies -name>

其中<package-name>是包名,--build-type 用来指定该包的编译类型,一共有三个可选项ament_python、ament_cmake、cmake,--dependencies 指的是这个功能包的依赖,可以依赖多个软件包。

创建ROS2的的C++包

ros2 pkg create --build-type ament_cmake <package_name>

创建ROS2的的python包

ros2 pkg create --build-type ament_python <package_name>

例如,新建一个package叫做test_pkg,依赖rclccpp(常用依赖)。

ros2 pkg create example_cpp --build-type ament_cmake --dependencies rclcpp

这样就会在当前路径下新建test_pkg软件包,包括:


  ├── CMakeLists.txt
  ├── include
  │   └── test_pkg
  ├── package.xml
  └── src

ros2 pkg完成了软件包的初始化,填充好了CMakeLists.txt和package.xml,并且将依赖项填进了这两个文件中。

三、CMakeLists.txt文件

3.1 CMakeLists.txt作用

CMakeLists.txt原本是Cmake编译系统的规则文件,而colcon编译系统基本沿用了CMake的编译风格,只是针对ROS2工程添加了一些宏定义。所以在写法上,colcon的CMakeLists.txt与CMake的基本一致。

这个文件直接规定了这个package要依赖哪些package,要编译生成哪些目标,如何编译等等流程。所以CMakeLists.txt非常重要,它指定了由源码到目标文件的规则,colcon编译系统在工作时首先会找到每个package下的CMakeLists.txt,然后按照规则来编译构建。

3.2 CMakeLists.txt写法

CMakeLists.txt的基本语法都还是按照CMake,而colcon在其中加入了少量的宏,总体的结构如下:


cmake_minimum_required() #CMake的版本号 
project()                #项目名称 
find_package()           #找到编译需要的其他CMake/Catkin package
add_message_files()      #新加宏,添加自定义Message/Service/Action文件
add_service_files()
add_action_files()
generate_message()       #新加宏,生成不同语言版本的msg/srv/action接口
add_library()            #生成库
add_executable()         #生成可执行二进制文件
add_dependencies()       #定义目标文件依赖于其他目标文件,确保其他目标已被构建
ament_target_dependencies()       #新加宏,它将使依赖项的库、头文件和自身的依赖项被正常找到
target_link_libraries()  #链接
install()                #安装至本机
ament_package    # 新加宏,安装项目,在CMakeLists.txt文件中的最后一个调用。

3.3 CMakeLists例子

第一条语句指定了cmake的最低版本,第二条语句设定了构建的功能包名称。注意,这个名称必须和package.xml中的名称保持一致。


cmake_minimum_required(VERSION 3.5)
project(nav2_costmap_2d)

接下来,查找系统中的依赖项。另外在构建库或执行文件时需要添加这些依赖项。


find_package(ament_cmake REQUIRED)
find_package(geometry_msgs REQUIRED)
find_package(laser_geometry REQUIRED)
find_package(map_msgs REQUIRED)
find_package(message_filters REQUIRED)
find_package(nav2_common REQUIRED)
find_package(nav2_msgs REQUIRED)
find_package(nav2_util REQUIRED)
find_package(nav2_voxel_grid REQUIRED)
find_package(nav_msgs REQUIRED)
find_package(pluginlib REQUIRED)
find_package(rclcpp REQUIRED)
find_package(rclcpp_lifecycle REQUIRED)
find_package(rmw REQUIRED)
find_package(sensor_msgs REQUIRED)
find_package(std_msgs REQUIRED)
find_package(tf2_geometry_msgs REQUIRED)
find_package(tf2 REQUIRED)
find_package(tf2_ros REQUIRED)
find_package(tf2_sensor_msgs REQUIRED)
find_package(visualization_msgs REQUIRED)
find_package(angles REQUIRED)

添加非ROS2功能包的依赖项时,需要将其对于的头文件路径在include_directories中写明。而对于依赖项为ROS2功能包时,则无需此操作。


find_package(Eigen3 REQUIRED)
include_directories(
  include
  ${EIGEN3_INCLUDE_DIRS}
)

add_library语句用于构建库。可以看到,下面使用了两个不同的语句来添加依赖。ament_target_dependencies是官方推荐的方式去添加依赖项。它将使依赖项的库、头文件和自身的依赖项被正常找到。

通常来说,若依赖项为ROS2功能包时,则使用ament_target_dependencies。若功能包有多个库,它也将一并包含。

target_link_libraries添加依赖项目时需写明具体库的名称。也就是说,添加的每一条都是一个库。比如下面的nav2_costmap_2d_core就是添加了libnav2_costmap_2d_core.so库。

add_executable用于构建执行文件。


add_library(nav2_costmap_2d_core SHARED
  src/array_parser.cpp
  src/costmap_2d.cpp
  src/layer.cpp
  src/layered_costmap.cpp
  src/costmap_2d_ros.cpp
  src/costmap_2d_publisher.cpp
  src/costmap_math.cpp
  src/footprint.cpp
  src/costmap_layer.cpp
  src/observation_buffer.cpp
  src/clear_costmap_service.cpp
  src/footprint_collision_checker.cpp
  src/costmap_collision_checker.cpp
  src/costmap_collision_checker_ros.cpp
  plugins/costmap_filters/costmap_filter.cpp
)

set(dependencies
  geometry_msgs
  laser_geometry
  map_msgs
  message_filters
  nav2_msgs
  nav2_util
  nav2_voxel_grid
  nav_msgs
  pluginlib
  rclcpp
  rclcpp_lifecycle
  sensor_msgs
  std_msgs
  tf2
  tf2_geometry_msgs
  tf2_ros
  tf2_sensor_msgs
  visualization_msgs
  angles
)

ament_target_dependencies(nav2_costmap_2d_core
  ${dependencies}
)

add_library(layers SHARED
  plugins/inflation_layer.cpp
  plugins/static_layer.cpp
  plugins/obstacle_layer.cpp
  src/observation_buffer.cpp
  plugins/voxel_layer.cpp
  plugins/range_sensor_layer.cpp
)
ament_target_dependencies(layers
  ${dependencies}
)
target_link_libraries(layers
  nav2_costmap_2d_core
)

add_library(filters SHARED
  plugins/costmap_filters/keepout_filter.cpp
  plugins/costmap_filters/speed_filter.cpp
)
ament_target_dependencies(filters
  ${dependencies}
)
target_link_libraries(filters
  nav2_costmap_2d_core
)

add_library(nav2_costmap_2d_client SHARED
  src/footprint_subscriber.cpp
  src/costmap_subscriber.cpp
  src/costmap_topic_collision_checker.cpp
)

ament_target_dependencies(nav2_costmap_2d_client
  ${dependencies}
)

target_link_libraries(nav2_costmap_2d_client
  nav2_costmap_2d_core
)

add_executable(nav2_costmap_2d_markers src/costmap_2d_markers.cpp)
target_link_libraries(nav2_costmap_2d_markers
  nav2_costmap_2d_core
)

ament_target_dependencies(nav2_costmap_2d_markers
  ${dependencies}
)

add_executable(nav2_costmap_2d_cloud src/costmap_2d_cloud.cpp)
target_link_libraries(nav2_costmap_2d_cloud
  nav2_costmap_2d_core
)

add_executable(nav2_costmap_2d src/costmap_2d_node.cpp)
ament_target_dependencies(nav2_costmap_2d
  ${dependencies}
)

target_link_libraries(nav2_costmap_2d
  nav2_costmap_2d_core
  layers
  filters
)

最后是安装库、可执行文件和其它文件的语句,它将在install/nav2_costmap_2d/lib安装库文件,install/nav2_costmap_2d/lib/nav2_costmap_2d安装可执行文件,把头文件、launch文件和参数文件安装在/share/${PROJECT_NAME}下。


install(TARGETS
  nav2_costmap_2d_core
  layers
  filters
  nav2_costmap_2d_client
  ARCHIVE DESTINATION lib
  LIBRARY DESTINATION lib
  RUNTIME DESTINATION bin
)

install(TARGETS
  nav2_costmap_2d
  nav2_costmap_2d_markers
  nav2_costmap_2d_cloud
  RUNTIME DESTINATION lib/${PROJECT_NAME}
)

install(FILES costmap_plugins.xml
  DESTINATION share/${PROJECT_NAME}
)

install(DIRECTORY include/
  DESTINATION include/
)

install(
  DIRECTORY include launch params
  DESTINATION share/${PROJECT_NAME}
)

其效果如下:

库文件

可执行文件

如果希望其它的功能包能链接到这些库,必须使用下面语句声明这些功能包的头文件和这些库位置,以便其他功能包要依赖这些功能包时能顺利找到对应的头文件和库文件。同时要使用ament_export_dependencies命令,该命令会将依赖项导出到下游软件包。这样该库使用者也就不必为那些依赖项调用find_package了。


ament_export_include_directories(include)
ament_export_libraries(layers filters nav2_costmap_2d_core nav2_costmap_2d_client)

ament_export_dependencies(${dependencies})

声明好后,其他功能包要链接这些库时只需使用下面两条命令即可!


find_package(nav2_costmap_2d REQUIRED)
ament_target_dependencies(example_node
  nav2_costmap_2d
)

若要该库可以被其它工作空间(不在同一个工作空间下)的功能包使用,也可以将这些库作为插件,可以导出插件的描述文件,以便其它工作空间的包可以调用pluginlib::ClassLoader类可以找到这些包的插件。


pluginlib_export_plugin_description_file(nav2_costmap_2d costmap_plugins.xml)

最后,项目安装是通过ament_package()完成的,并且每个软件包必须恰好执行一次这个调用。ament_package()会安装package.xml文件,用ament索引注册该软件包,并安装CMake的配置(和可能的目标)文件,以便其他软件包可以用find_package找到该软件包。由于ament_package()会从CMakeLists.txt文件中收集大量信息,因此它应该是CMakeLists.txt文件中的最后一个调用。

ament_package()

四、package.xml文件

package.xml也是一个colcon的package必备文件,它是这个软件包的描述文件,用于描述pacakge的基本信息。

4.1 package.xml作用

pacakge.xml包含了package的名称、版本号、内容描述、维护人员、软件许可、编译构建工具、编译依赖、运行依赖等信息。

实际上ros2 pkg findrosdep等命令之所以能快速定位和分析出package的依赖项信息,就是直接读取了每一个pacakge中的package.xml文件。它为用户提供了快速了解一个pacakge的渠道。

4.2 package.xml写法

pacakge.xml遵循xml标签文本的写法,由于版本更迭原因,现在有两种格式并存(format1与format2),不过区别不大。老版本(format1)的pacakge.xml通常包含以下标签:


<pacakge>           根标记文件  
<name>              包名  
<version>           版本号  
<description>       内容描述  
<maintainer>        维护者 
<license>           软件许可证  
<buildtool_depend>  编译构建工具,通常为catkin  
<build_depend>      编译依赖项,与Catkin中的  
<run_depend>        运行依赖项

其中1-6为必备标签,1是根标签,嵌套了其余的所有标签,2-6为包的各种属性,7-9为编译相关信息。

在新版本(format2)中,包含的标签为:


<pacakge>               根标记文件  

<name>                  包名  

<version>               版本号  

<description>           内容描述  

<maintainer>            维护者

<license>               软件许可证  

<buildtool_depend>      编译构建工具,通常为catkin    

<depend>                指定依赖项为编译、导出、运行需要的依赖,最常用

<build_depend>          编译依赖项  

<build_export_depend>   导出依赖项

<exec_depend>           运行依赖项

<test_depend>           测试用例依赖项  

<doc_depend>            文档依赖项


由此看见新版本的pacakge.xml格式上增加了 <depend> 、<build_export_depend> 、<exec_depend> 、<test_depend>和 <doc_depend>,相当于将之前的build和run依赖项描述进行了细分。

目前ROS和ROS2都同时支持两种版本的package.xml,所以无论选哪种格式都可以。

4.3 pacakge.xml例子

以turtlesim软件包为例,其pacakge.xml文件内容如下


QR Code
微信扫一扫,欢迎咨询~

联系我们
武汉格发信息技术有限公司
湖北省武汉市经开区科技园西路6号103孵化器
电话:155-2731-8020 座机:027-59821821
邮件:tanzw@gofarlic.com
Copyright © 2023 Gofarsoft Co.,Ltd. 保留所有权利
遇到许可问题?该如何解决!?
评估许可证实际采购量? 
不清楚软件许可证使用数据? 
收到软件厂商律师函!?  
想要少购买点许可证,节省费用? 
收到软件厂商侵权通告!?  
有正版license,但许可证不够用,需要新购? 
联系方式 155-2731-8020
预留信息,一起解决您的问题
* 姓名:
* 手机:

* 公司名称:

姓名不为空

手机不正确

公司不为空