1. 从项目文件夹说起工作空间到底解决什么问题带过不少刚入门ROS的新同学之后我发现大多数人第一个真正的坎并不是安装环境而是面对工作空间和功能包这两个概念时的茫然。教程里让敲三行命令就完事可一旦脱离了教程自己动手马上就会遇到找不到包头文件路径不对之类的问题。所以这篇文章我想先从底层逻辑讲清楚再给你一套可以直接照抄的实操流程。所谓ROS工作空间catkin workspace说白了就是一个有约定的项目根目录。用过Maven的知道Java项目要按src/main/java这样的结构组织用过npm的也知道node_modules这些目录都有规矩ROS里的工作空间也一样——它规定了你的代码放在哪、编译中间产物放在哪、最终生成的可执行文件和脚本放在哪。这套约定不是无聊而是为了支撑catkin构建系统的正常工作。一个标准工作空间下通常有三个子目录src源代码区你创建的功能包都放这里包括自己写的包以及clone下来的第三方包。build构建区catkin会把编译过程中的临时文件、CMake缓存文件扔到这里。devel开发环境区编译完成后的可执行文件、生成的头文件、动态库和环境脚本都在这里相当于虚拟环境导出目录。我见过有人图省事直接在src里写代码然后用g手动编译这当然也能跑通一个独立的节点但一旦涉及多个节点之间的消息通信、自定义消息类型、依赖库查找手写编译就会变成灾难。catkin把你需要的大部分东西都自动化了找到依赖包的头文件路径、库路径、生成消息相关的C代码——这些全靠着工作空间的目录结构才能成立。所以我的结论很直接在ROS 1的日常开发里工作空间就是你的地盘你的全部工程都在这一个根目录下运转。顺便说一句到ROS 2时代这个概念依然存在只是名字和构建系统变成了colcon和相应的子目录约定。如果你现在学的是ROS 1 Noetic先吃透catkin_make这套玩法之后切ROS 2时很多思路是相通的。2. 手把手创建ROS工作空间从零到catkin_make通过这一节估计是你们最想看的直接上命令。我默认你用的Ubuntu 20.04 ROS NoeticROS 1其他版本包括Melodic命令完全一致。2.1 创建src目录打开终端输入mkdir -p ~/catkin_ws/src cd ~/catkin_ws这里-p参数表示多级目录一次性创建如果你之前已经创建过~/catkin_ws也不会报错。工作空间叫什么名字本身没有强制要求catkin_ws只是社区里最通用的习惯你用ros_ws、workspace都行。但我建议统一用catkin_ws因为网上绝大多数教程、例程都默认这个路径照着抄的时候能少踩很多坑。2.2 执行catkin_make回到工作空间根目录后直接运行catkin_make第一次执行时你会发现终端刷出来大段日志告诉你Base path: /home/你的用户名/catkin_ws、Source space: /home/你的用户名/catkin_ws/src、Build space: /home/你的用户名/catkin_ws/build等等。这些信息其实就是在告诉你catkin已经认领了你的项目结构。执行完之后你ls看下当前目录会发现多了build和devel两个文件夹。这一步只要没有红色报错基本就成功了。2.3 先弄清楚devel里发生了什么很多人到这里就急着去创建功能包但我建议你先花两分钟看看devel目录。进去之后你会发现里面有个setup.bash文件还有lib、include等目录。理解一个关键点catkin_make不只是编译它还生成了完整的环境脚本。每当你编译完一个功能包对应生成的可执行文件会被放到devel/lib下面动态链接库放到devel/lib消息和头文件放到devel/includepython模块放到devel/lib/python3/dist-packages。而setup.bash的作用就是把这些路径全部注入到当前终端的环境变量里好让ROS相关的命令能找到它们。所以那行经典命令source devel/setup.bash不仅在当前终端生效而且每次工作空间内容更新后都建议重新source一下。如果发现自己明明编译成功了但rosrun就是找不到节点十次里有九次是没有重新source。2.4 为什么我不急着推荐catkin_toolsROS社区里还有一个catkin_tools工具集命令变成catkin build支持并行编译、布局更清晰。我个人的建议是新手先把catkin_make用熟因为它是最简单、最不容易出问题的路径等你在工程里积累了多个功能包、觉得编译太慢时再切换到catkin build也不迟。切换的方式也不复杂sudo apt install python3-catkin-tools catkin build注意catkin build和catkin_make的编译产物目录会有差异不要在同一工作空间里混用两种方式否则build目录里缓存会互相干扰偶尔会报一些莫名其妙的错误。真要用catkin_tools干净利落地建一个新的工作空间从开始就用它。3. 创建功能包catkin_create_pkg的正确姿势工作空间有了接下来要解决的是代码以什么单位组织、怎么跟ROS的依赖系统挂钩。这就是功能包package的职责。3.1 功能包是ROS代码组织的最小单元ROS里所有可复用的东西都以功能包为边界一组节点、消息定义、库文件、配置文件整整齐齐地装进一个包。包和包之间有依赖关系比如你的包要发布一个std_msgs/String消息就得声明依赖std_msgs。这个声明不是填空题而是编译系统和运行系统查找路径的依据。不声明依赖编译时就可能找不到ros/ros.h运行时topic消息类型也可能对不上。一个包最基本的物理身份标识是目录下同时存在package.xml和CMakeLists.txt两个文件。别小看这一点——判断一个目录是不是功能包ROS社区的标准就是你有没有这两个文件。3.2 catkin_create_pkg命令实战创建功能包有现成的命令先进入工作空间的src目录cd ~/catkin_ws/src catkin_create_pkg hello_ros std_msgs rospy roscpp这条命令的意思是创建一个名为hello_ros的功能包并声明依赖std_msgs、rospy、roscpp。命令执行完后你会在src/hello_ros下看到package.xml、CMakeLists.txt、include/hello_ros、src/等目录结构。关于包名这里有几个硬性规范我踩过坑也帮人救过场包名只能用字母、数字和下划线且不能以数字开头。包名不允许包含大写字母。ROS社区约定包名贯彻小写风格你起HelloROS编译时系统可能睁一只眼闭一只眼但后续很多工具会出问题。包名要尽可能简短、见名知意beginner_tutorials这种没问题但别有空格和中文。3.3 常见依赖怎么选创建功能包时依赖是跟着命令直接写进去的但如果漏了后面也可以补。先整体看一下最常见的几类依赖依赖名典型作用什么时候必须加roscppC版ROS客户端库写C节点、#include ros/ros.h时必须加rospyPython版ROS客户端库写Python节点、import rospy时必须加std_msgs标准消息类型String、Int32、Float64等几乎任何涉及topic消息的包都要加message_generation编译自定义消息时生成代码你定义了.msg文件时加message_runtime运行时解析自定义消息你的包依赖其他包里的自定义消息时加sensor_msgs图像、点云、IMU等传感器消息处理摄像头、雷达数据时加geometry_msgs位置、姿态、向量等几何消息做导航、机械臂控制时加tf2/tf2_ros坐标变换相关功能涉及多个坐标系转换时加我见过不少同学创建包时依赖只写了个std_msgs后面写C节点直接#include ros/ros.h却编译不过原因就是漏了roscpp。不必担心加多了——依赖声明本身不会让你的代码变慢只要能解析到对应的包就没事所以刚开始拿不准时宁可多声明几个常用的也不要缺。3.4 创建后目录里多了什么创建完后再catkin_make一次然后去devel/lib/hello_ros目录看看你会发现里面什么都没有——对因为你的包还没编译出任何可执行文件。这是初级玩家容易疑惑的点为什么创建了包却没有可执行程序因为catkin_create_pkg只搭了骨架真正的源码、编译目标都还没配置。骨架的价值在于ROS已经认识这个包了你可以在其他包里引用它的资源和依赖关系。4. package.xml和CMakeLists.txt功能包的身份证与操作手册前面说过判断是不是功能包就看这两个文件。这节我拆开讲清楚它们各自管什么以及日常开发中你至少要会改哪些地方。4.1 package.xml包的名片和依赖清单用文本编辑器打开package.xml你会看到一堆XML标签。核心的就几个namehello_ros/name version0.0.0/version descriptionhello_ros package description/description maintainer emailyouexample.comuser/maintainer licenseMIT/license然后就是依赖区包含build_depend、build_export_depend、exec_depend等标签。不同的标签告诉编译系统和运行系统分别在什么阶段需要这个依赖。简单记忆build_depend编译这个包时需要。exec_depend运行这个包时需要。如果你不太确定也可以把它们统一写成depend标签catkin支持这种简写大多数常见依赖用它就够了。新手最容易犯的错误是改了代码、加了新的依赖包却忘了回package.xml里同步声明结果编译报错找不到头文件。定位到报错后第一反应不应该是去怀疑CMake而是先检查package.xml里对应依赖有没有写进去。4.2 CMakeLists.txt真正要动手改的重点CMakeLists.txt比package.xml复杂得多但好消息是catkin_create_pkg生成模板后大多数项目只需要改三四处就够了。你先要理解几个CMake函数在catkin里的作用cmake_minimum_required(VERSION 3.0.2) project(hello_ros)project()声明的项目名必须和包名一致我在排错时见过有人把这里改了结果catkin_make报出各种诡异的target名称错误。这里定了就别动。然后依赖查找是以find_package的形式出现的find_package(catkin REQUIRED COMPONENTS roscpp rospy std_msgs )这一块对应了你在catkin_create_pkg里声明的依赖手动追加新依赖时这里也要同步加一行。4.3 实操中改动最频繁的三个位置第一add_executable()。每写一个C节点源文件就要加一个编译目标。比如你有个src/hello_node.cpp那就写add_executable(hello_node src/hello_node.cpp) target_link_libraries(hello_node ${catkin_LIBRARIES})注意target_link_libraries是配套的漏掉它程序能编译但链接时会报一堆未定义的ros符号。第二自定义消息时的add_message_files()。如果你在msg/目录下放了MyMsg.msg需要在这里声明add_message_files( FILES MyMsg.msg ) generate_messages( DEPENDENCIES std_msgs )这两段的顺序不能乱generate_messages依赖std_msgs的声明如果没写std_msgs在依赖里编译会报找不到消息生成的错误。第三Python节点的处理。如果你的包里有Python写的节点那你甚至不需要改CMakeLists里的编译目标只要让Python文件有可执行权限并在文件开头写上#!/usr/bin/env python3。ROS的rosrun会直接在src目录下找到它。很多人被教程里写C要改CMake写Python不用改这句话搞晕原理就在这里Python没有编译环节。5. 环境变量与路径为什么写完代码总是找不到包如果说功能包是ROS的单位那环境变量就是让ROS知道去哪儿找这个单位的路牌。这一节专讲最常见的找不到问题。5.1 setup.bash到底做了什么你执行source devel/setup.bash时脚本会修改几个关键环境变量最重要的一个是ROS_PACKAGE_PATH。你可以这样查看echo $ROS_PACKAGE_PATH正常情况下输出里会包含你当前工作空间的src路径比如/home/你的用户名/catkin_ws/src:/opt/ros/noetic/share。冒号分隔多个路径ROS会从前到后逐个搜索功能包。如果你没source当前工作空间这个变量里就永远只有系统自带路径/opt/ros/noetic/share那rosrun hello_ros xxx自然找不到。理解这一点之后很多问题都能自己定位了。记住一句话终端的环境变量只属于当前终端。新开一个终端后之前source的配置全都没了。所以:source ~/catkin_ws/devel/setup.bash这个操作每次新开终端、只要你想用这个工作空间里的东西就都得做。嫌麻烦的可以把这句话追加到~/.bashrc末尾echo source ~/catkin_ws/devel/setup.bash ~/.bashrc source ~/.bashrc这样每次开终端都会自动加载。但注意如果你同时维护多个工作空间建议只在.bashrc里source最常用的那一个其他的按需手动source否则同名功能包会造成路径覆盖编译和运行时都可能串包。5.2 rosrun vs roslaunch各自怎么找包rosrun和roslaunch都依赖环境变量找包但roslaunch还会读取包里的launch目录。如果你的launch文件放在包目录下但找不到先确认两件事一是环境变量是否source了二是launch文件名有没有拼写错误。后者我犯过不止一次。5.3 一套标准的找不到包排查流程遇到[rospack] Error: package hello_ros not found这类报错时按顺序做以下操作# 1. 检查包是否存在 ls ~/catkin_ws/src/hello_ros # 2. 检查是否已经编译过 cd ~/catkin_ws catkin_make # 3. 检查环境变量里有没有当前工作空间 echo $ROS_PACKAGE_PATH # 4. 重新source source ~/catkin_ws/devel/setup.bash第五步如果还不行用rospack find hello_ros直接查询ROS自己的包索引。这个命令查不到说明包没有注册进环境能查到但rosrun不行那就是命令拼写或包内文件路径的问题。6. 我这些年带新人踩过的高频坑和排查套路最后这节我按问题现象 - 根因 - 解决的方式整理几个高频坑算是看着一个又一个同学踩完之后沉淀下来的经验。6.1 常见报错与处理对照报错根因处理方式Command catkin_make not foundROS环境没加载先执行source /opt/ros/noetic/setup.bash并确认ROS正确安装No rule to make targetCMakeLists.txt里add_executable写错源文件路径检查src/下文件名拼写和路径fatal error: ros/ros.h: No such file or directory缺少roscpp依赖或没find_package在package.xml加roscppCMakeLists.txt的find_package同步加上package xxx not found要么没source要么该包没编译按5.3节流程排查Another process has locked the package用了rospack的手动编辑或其他进程占用检查是否有另一个终端还开着旧环境关闭后重试Could not find a package configuration fileCMakeLists里依赖了并未安装的第三方包先apt install ros-noetic-对应包名或者确认包的依赖名称没写错6.2 工作空间嵌套工作空间我见过一个让人挠头的案例某同学在src目录里又创建了一个catkin_ws里面又套了src、build、devel。结果每次编译rosrun都优先找到了内层那个半成品包。我说句难听的这属于在文件夹里建文件夹、还指望两个root都能用的做法。工作空间不要嵌套一个项目一个工作空间根目录所有相关包平级放在src下。6.3 同名功能包的覆写问题当你.bashrc里同时source了两个工作空间而两个工作空间都有名为hello_ros的包时先被source的那一个会被后者覆盖。rospack find hello_ros查到的路径是后source的那个工作空间。这不是bug是ROS_PACKAGE_PATH的路径顺序决定的。所以我的建议是不同项目、不同功能包尽量用不同的包名。如果一定要同名那就只source当前项目对应的工作空间。6.4 如何验证你的工作空间和功能包彻底建立好了实操里我习惯用一套三步自测法比教程跑通了就完事可靠得多。做完创建工作空间和功能包后按顺序验证# 1. 验证ROS能找到这个包 rospack find hello_ros # 2. 验证构建系统认这个包 catkin_make catkin_make -DCMAKE_INSTALL_PREFIX$PWD/devel install # 3. 在全新终端里source并尝试查看包信息 source ~/catkin_ws/devel/setup.bash rosrun hello_ros # 没有节点时会提示没有可执行文件这是正常的第三步如果你看到usage: rosrun package_name executable_name这种提示说明包已经被环境正确识别。此时你的工作空间和功能包就算真正建立起来了可以开始往里面写节点了。最后给你一个实用的小习惯每次修改完CMakeLists.txt或者package.xmlcatkin_make后如果出现灵异问题可以先把build目录删掉再重新编译。我见过太多次明明改对了还报错的情况最后都是靠这一招解决。反正编译产物可以重建无所谓的大不了多等几秒。这个习惯我保留到现在已经帮我省下很多无意义排错时间了。
阅读完成 · 觉得有帮助?