04:使用 VS Code Remote-SSH 与 Dev Container 建立 ROS1 开发工作流
摘要:这一章不把 VS Code 当成“装几个插件就结束”。我们会先确定 IDE 到底运行在哪一层,再建立两套互补工作流:Remote-SSH Host 负责日常编辑、Git、Docker 和 Host→Container GDB;Dev Container 负责最准确的 ROS1 C++ IntelliSense、
/opt/ros/noetic浏览和 Container 内调试。
@[toc]
本章目标
完成本章后,你应该能够:
- 解释 Windows VS Code、Remote-SSH Host、Docker Container 三层关系;
- 知道 VS Code Extension Host 在不同模式下运行在哪里;
- 会使用
ROS1 Container BashTerminal Profile; - 会使用 Container Tools 查看 Container 文件;
- 会
Reopen in Container,并理解它与普通docker exec的区别; - 理解为什么 VS Code 根目录保持
/workspace; - 理解
.vscode/c_cpp_properties.json中各类 include path 的作用; - 会使用
C/C++: Log Diagnostics判断 IntelliSense 实际加载了什么。
为什么先确定“VS Code 在哪一层”
当前环境不是一个单层 Linux 桌面,而是:
1 | Windows |
如果不先分清这三层,就很容易产生下面的误解:
1 | “Container 里明明有 /opt/ros/noetic,为什么 F12 找不到?” |
答案都取决于:
1 | 当前 VS Code Extension Host 在哪里运行? |
Step 1:把整个仓库作为 Remote-SSH Workspace 打开
推荐主工作方式:
1 | Windows VS Code |
不要只打开:
1 | /home/wdfk/share/ros1-docker/ros_ws |
因为整个仓库里还包括:
1 | Dockerfile |
它们都是完整开发环境的一部分。
检查点
VS Code Explorer 顶层应该能同时看到:
1 | .devcontainer |
Step 2:安装 Host 侧基础扩展
仓库的:
1 | .vscode/extensions.json |
推荐:
1 | ms-vscode.cpptools |
它们分别用于:
1 | C/C++ |
ROS1 专用扩展放在 Dev Container 配置中:
1 | Ranch-Hand-Robotics.rde-ros-1 |
这样 ROS 插件真正运行在有 Noetic 环境的 Container Extension Host 中,而不是强迫 Ubuntu24 Host 模拟完整 Noetic 安装环境。
Step 3:普通 Host Terminal 与 ROS Container Terminal 不要混
Remote-SSH Host 窗口里新建普通终端:
1 | pwd |
通常得到:
1 | /home/wdfk/share/ros1-docker |
这是 Host shell。
仓库在 .vscode/settings.json 中定义了另一个 Terminal Profile:
1 | ROS1 Container Bash |
在 VS Code:
1 | Terminal |
再:
1 | pwd |
应该是:
1 | /workspace |
再检查:
1 | echo "$ROS_DISTRO" |
应该是:
1 | noetic |
这个 Profile 本质上是什么
配置核心等价于:
1 | docker compose \ |
它不是 ROS 插件,也不是新的 shell 类型,只是 VS Code 帮你固定了一条 docker compose exec 命令。
为什么使用 --project-directory
这样 Compose 始终从整个仓库寻找:
1 | compose.yaml |
不依赖终端当前 cwd。
因此你即使在:
1 | ros_ws/src/ros1_hello |
打开这个 Profile,也不会因为当前目录变化而找不到 Compose 项目。
Step 4:用 Container Tools 看“Container 里真实存在的文件”
Container Tools 适合回答:
1 | ros1-dev 是否运行? |
例如 Host Explorer 并不能直接把:
1 | /opt/ros/noetic |
当作 Host 目录浏览,因为它属于 Container 文件系统。
此时可以用 Container Tools 的 Files 功能查看:
1 | /opt/ros/noetic/include |
这和 bind mount 的 /workspace 不一样。
1 | /workspace |
这个区别对后面 F12 非常重要。
Step 5:理解 Reopen in Container
点击 VS Code 左下角 Remote 指示器:
1 | Reopen in Container |
Dev Containers 会读取:
1 | .devcontainer/devcontainer.json |
核心配置:
1 | "dockerComposeFile": "../compose.yaml", |
这不是重新复制一份代码。
因为 /workspace 已经是 Host 仓库 bind mount 进来的同一份文件。
变化的是:
1 | VS Code UI 仍在 Windows |
因此此时 IntelliSense 可以直接访问:
1 | /opt/ros/noetic/include |
这就是 Dev Container 模式比 Host 模式更接近真实编译环境的原因。
Step 6:为什么 Dev Container 根目录仍然是 /workspace
devcontainer.json 中:
1 | "workspaceFolder": "/workspace" |
不要改成:
1 | /workspace/ros_ws |
因为你需要同时看到:
1 | /workspace/ros_ws |
后面阅读 roscpp 源码时,业务代码和上游源码会同时参与导航和调试。
代价是:某些 ROS 插件只会把“VS Code 根目录”当作 catkin workspace 检测目标。
当前根目录是:
1 | /workspace |
而真正的 catkin workspace 是:
1 | /workspace/ros_ws |
所以插件可能显示:
1 | Build tool NOT detected |
这不等于 ROS 环境坏了。
本工程对构建使用显式 Task:
1 | ROS1 [DEV CONTAINER]: Build ros1_hello |
让任务明确 cd /workspace/ros_ws 再执行:
1 | catkin build ros1_hello |
Step 7:理解 .devcontainer/ros_env.bash
文件内容很短:
1 | source /opt/ros/noetic/setup.bash |
顺序非常重要:
1 | Noetic underlay |
最终业务 workspace 在最上层。
为什么不直接把 ROS 插件配置成:
1 | /workspace/ros_ws/devel/setup.bash |
因为第一次打开工程时 ros_ws 可能还没构建,devel/setup.bash 还不存在。
而 ros_env.bash 可以:
1 | 有 overlay 就 source |
所以它是稳定入口。
Step 8:先理解 Host IntelliSense 的边界
回到 Remote-SSH Host 窗口。
此时 ms-vscode.cpptools 运行在:
1 | Ubuntu 24.04 Host |
而不是 Container。
因此:
1 | Host 能直接访问: |
这就是为什么 Host IntelliSense 需要:
1 | ros_debug_ws |
把需要阅读的 ROS 上游源码放到 Host 可见的 bind-mount 目录中。
Step 9:阅读 c_cpp_properties.json
打开:
1 | .vscode/c_cpp_properties.json |
先看编译器:
1 | "compilerPath": "/usr/bin/g++" |
在 Remote-SSH Host 中,这个路径指的是:
1 | Ubuntu24 Host 的 g++ |
C/C++ 扩展会询问它,并自动得到 Host 系统 include:
1 | /usr/include/c++/... |
所以不要手工堆:
1 | "/usr/include/**", |
如果 Boost 已安装,Host g++ 的系统 include 就应该能找到它。
Step 10:理解四组 ROS include path
配置中最重要的是:
1 | "includePath": [ |
按职责理解,不要死记路径。
10.1 ros_ws/src/**
自己的 package 源码。
10.2 ros_ws/devel/include 与 /**
自己的 workspace 生成 header,例如以后自己定义 message 后产生的:
1 | my_pkg/MyMessage.h |
10.3 roscpp 精确 include root
1 | ros_debug_ws/src/ros_comm/clients/roscpp/include |
这是一个标准 C/C++ include root。
因为:
1 |
要求编译器/IntelliSense 能找到:
1 | <某个 include root>/ros/ros.h |
当前源码实际位置:
1 | ros_comm/clients/roscpp/include/ros/ros.h |
所以 include root 应该是它上一级:
1 | ros_comm/clients/roscpp/include |
10.4 ros_debug_ws/src/**
用于广泛递归索引其它上游源码:
1 | roscpp_core |
但要注意:
1 | 递归索引范围 |
和:
1 | C/C++ 的精确 include root |
不是完全相同的概念。
在 Remote-SSH Host 下,src/** 可以帮助 cpptools“看到”很多文件,但 <ros/ros.h> 的 F12 导航仍需要 roscpp 真实 include root 才稳定。
10.5 ros_debug_ws/devel/include
用于 catkin 生成 header:
1 | ros/common.h |
第 05、06 篇会专门讲这三类 header 的差异。
Step 11:第一次做 F12 前,先准备 roscpp 源码
如果:
1 | ros_debug_ws/src/ros_comm |
还不存在,在 Host:
1 | cd /home/wdfk/share/ros1-docker/ros_debug_ws/src |
然后确认:
1 | test -f \ |
再回 hello_node.cpp:
1 |
把光标放到:
1 | ros/ros.h |
按 F12。
目标:
1 | /home/wdfk/share/ros1-docker/ |
这一步只证明:
1 | Header 导航已经能到源码 |
它还不证明运行时真正加载了自己编译的 Debug libroscpp.so。后者属于第 05 篇。
Step 12:使用 C/C++: Log Diagnostics 看事实
如果 F12 或红线不正常,不要第一反应继续添加 includePath。
执行:
1 | Ctrl+Shift+P |
重点看:
1 | Current Configuration |
例如你应该能确认:
1 | Compiler Path: /usr/bin/g++ |
以及:
1 | include (recursive): .../ros_debug_ws/src |
如果配置文件里写了路径,但 Diagnostics 里没有,就说明:
1 | 当前 Translation Unit 没有真正使用那套配置 |
这比继续猜路径更可靠。
Step 13:理解 Reset、Rescan 和 Reload 的区别
修改 C/C++ 配置或重新生成大量 header 后,可以按顺序:
1 | C/C++: Reset IntelliSense Database |
清理 cpptools 数据库。
然后:
1 | C/C++: Rescan Workspace |
重新扫描 workspace。
最后必要时:
1 | Developer: Reload Window |
重新加载 VS Code 窗口和扩展。
不要把这三个动作当成“万能修复”。它们适合在路径已经正确、但缓存状态可能过期时使用。
Step 14:Remote-SSH Host F5 的总体结构
本工程的 Host Debug 配置使用:
1 | VS Code cppdbg |
所以 Host 不需要自己运行 Noetic 的 GDB target。
.vscode/launch.json 中:
1 | "pipeProgram": "docker" |
让 cppdbg 调用 Docker。
1 | "debuggerPath": "/usr/bin/gdb" |
这个路径是 Container 里的 GDB。
1 | "sourceFileMap": { |
负责把 DWARF 中记录的 Container 源码路径映射回 Host 文件路径。
第 05 篇会实际使用这条链路。
Step 15:Reopen in Container 后 F5 为什么更简单
如果已经 Reopen in Container:
1 | cpptools |
都在同一个 Container namespace。
因此 Debug 配置可以直接:
1 | "miDebuggerPath": "/usr/bin/gdb" |
不需要:
1 | pipeTransport |
这也是为什么本工程保留两套 launch config:
1 | [HOST] |
不要把两套字段混在同一个配置里。
本章常见问题
VS Code 能搜索到 ros.h,但 #include <ros/ros.h> F12 不跳
“文件搜索能找到”不等于“它被识别成正确 include root”。
确认配置中存在:
1 | ${workspaceFolder}/ros_debug_ws/src/ros_comm/clients/roscpp/include |
再用:
1 | C/C++: Log Diagnostics |
确认当前 TU 真的加载了它。
Host 上 /opt/ros/noetic/include 不存在
这是正常的。
Remote-SSH Host 模式下主要依靠:
1 | ros_debug_ws/src |
要直接访问 Container /opt/ros/noetic,使用:
1 | Reopen in Container |
或 Container Tools Files。
ROS1 插件能用环境,但检测不到 catkin workspace
当前 VS Code 根目录是:
1 | /workspace |
真正 catkin workspace 是:
1 | /workspace/ros_ws |
所以插件根目录自动检测不一定成功。
使用仓库提供的 Task:
1 | ROS1 [DEV CONTAINER]: Build ros1_hello |
而不是为了迎合插件,把整个 VS Code 根目录改成 /workspace/ros_ws。
本章练习
- 在 Remote-SSH Host 普通 Terminal 和
ROS1 Container Bash中分别执行pwd; - 在 Container Tools 中找到
/ros_entrypoint.sh; - Reopen in Container 后执行
which g++、echo $ROS_DISTRO; - 回 Remote-SSH Host,对
<ros/ros.h>做 F12; - 执行
C/C++: Log Diagnostics,找出 Host g++ 的 system include path; - 解释为什么
ros_debug_ws/src/**和 roscpp 精确 include root 不能简单看成同一件事。
下一章会继续沿着 hello_node.cpp 往下走:
1 | F12 到 ros/ros.h |









