跳转至

第 3 章 编辑器、包管理与开发环境

装什么、怎么装、装了以后怎么调试,是开发环境的三个环节。

落在这三个环节里的对象是:编辑器(nano 与 vim 的最小可用集)、apt / dpkg 与 PPA / Snap 的分层与信任边界、Python 三生态(apt 的 python3-* / pip / conda)的适用分界与混装坑、C/C++ 工具链(gcc / make / cmake / pkg-config / ccache,含源码到可执行文件的编译流水线)、git 基线与日常分支工作流、配置格式读进程序(json / PyYAML / xml.etree 三解析器与选型判据,格式语法在第 2 章「结构化格式」)、gdb / strace / ltrace 三条调试切入线。

语言本身的语法与工程实践不在这里展开——机器人语境的 C++ / Python 节点写法归 ROS2-note,此处只讲工具链侧看得见的语言机制。VS Code Remote-SSH 与 clangd 作为 SSH 之上的应用组合,机制在第 10 章展开,此处只交代装法与最小联动。

1. 编辑器:nano 与 vim 的最小集

1.1 EDITOR 变量与 nano 三键

Ubuntu 默认装 nano 与 vim-tiny(不是完整 vim)。查当前 EDITOR:

echo "$EDITOR"                                # 未设时脚本会回退到 vi
select-editor                             # 交互式选当前用户的默认编辑器(写 `~/.selected_editor`,不需 sudo)
sudo update-alternatives --config editor  # 全局选一个编辑器,写系统别名

sudo 会保留部分调用者环境,git commit 打开的编辑器由 core.editor 或 $EDITOR 决定。把 EDITOR 写进 ~/.bashrc(第 2 章的 .bashrc 规则):

export EDITOR=vim
export VISUAL=vim

nano 是"进入即能编辑"的兜底:Ctrl+O 写盘、Ctrl+X 退出、Ctrl+W 搜索。适合临时改一行 /etc/hosts 或 systemd 单元;长文本与代码不建议。

1.2 vim 的十二个动作

vim 学习曲线陡,但机器人上位机 SSH 现场绕不开——精简系统常常只装了 vim-tiny。最小集五个模式动作加七个编辑动作:

动作 普通模式 说明
进入插入 i 光标前插入;a 光标后、o 下方新行
退出插入 Esc 回普通模式
保存退出 :wq 或 ZZ 有修改才写;:q! 强制退出不保存
搜索 /pattern n / N n 下一个、N 上一个;\c 忽略大小写
跳到行 :123 或 G G 到最后一行、gg 到第一行
删 / 复制 / 粘贴 x dd yy p 字符 / 行 / 复制 / 粘贴
撤销与重复 u . u 撤销、. 重复上一次修改

会这 12 个动作即可现场改配置文件。~/.vimrc 里三条最小配置:

set number                            " 显示行号
set expandtab tabstop=4 shiftwidth=4  " 缩进用空格、4 列
syntax on                             " 语法高亮

neovim(nvim) 与 vim 兼容配置文件(~/.config/nvim/),Lua 配置与内置 LSP 客户端是主要差异;本板块不展开。Jupyter 是数据侧笔记本,sudo apt install jupyter-notebook 后 jupyter notebook --no-browser 起服务、ssh -L 8888:localhost:8888 端口转发回本地(第 10 章)——机器人项目做 bag 数据分析时才用。

2. apt 系分层

2.1 apt 与 dpkg 各管什么

  • dpkg:低级包管理器,只装 / 卸 / 查 .deb,不管依赖。
  • apt:包索引、依赖解析、仓库配置、apt-get / apt-cache 的现代入口。
  • aptitude:带 curses UI 的高级前端,处理复杂依赖冲突时更直观;日常 apt 够用。
apt list --installed              # 已装列表
apt list python3-* --installed    # 通配过滤
apt show hello                    # 元信息
apt-file search setup.bash        # 反查"哪个包提供这个文件"(需 apt-file 包与 apt-file update)
dpkg -L python3                   # 列出某包安装的所有文件
debconf-show package              # 看 dpkg 层面的配置项

apt 与 apt-get 的差别是给人用与给脚本用的取向:apt 有进度条、交互友好;apt-get 输出对脚本更稳定。CI 与自动化脚本一律走 apt-get。

2.2 PPA 与第三方源的信任边界

PPA(Personal Package Archive)是 Canonical 托管的 Launchpad 上个人 / 团队的仓库。加 PPA 等于允许该团队向目标主机推 .deb:

sudo add-apt-repository ppa:graphics-drivers/ppa     # 例:显卡驱动团队
sudo apt update
apt-cache policy nvidia-driver-550                    # 查可用版本与来源

判据:PPA 装的是"哪个上游构建",不是"谁签了名"。加进 sources.list.d/ 后所有 apt upgrade 都会拉。生产环境优先选发行版仓库或经过审查的第三方仓库;用完不需要的 PPA sudo add-apt-repository --remove ppa:...。apt-key 已 deprecated,改用 signed-by 精确到源;添加 key 时放 /etc/apt/keyrings/ 而非全局。

2.3 Snap 与 Flatpak

Snap(Canonical 主推):自带依赖、沙箱、跨发行版;缺点是首次运行慢、循环挂载多、系统里 snapd 常驻。Ubuntu 桌面版把 Firefox、Chromium、部分 CLI(aws-cli、lxc)默认打成 snap。

Flatpak(社区方案):靠 Flathub 仓库、运行时共享,桌面分发更主流;服务端几乎不用。

机器人上位机(服务器 / 板端)建议禁用 Snap 桌面自动装并优先 apt:

## 关掉 Ubuntu 桌面版默认装 snap 的行为(影响 firefox 等)
sudo tee /etc/apt/preferences.d/nosnap.pref >/dev/null <<'EOF'
Package: snapd
Pin: release a=*
Pin-Priority: -10
EOF

这段偏桌面策略,服务器安装 Ubuntu 时不装 desktop 即无此问题。Pin-Priority 为负值时 apt 视为"不安装",该语义在 apt 各版本一致;Pin: release a=* 匹配所有归档,不依赖发行代号。

3. Python 多生态与混装坑

Ubuntu 里 Python 包有三个平行渠道,装错会互相破坏:

渠道 装的位置 谁能看到
apt install python3-xxx /usr/lib/python3/dist-packages/ 系统 Python 与所有 venv(默认继承)
pip install xxx ~/.local/lib/python3.10/site-packages/ 或 /usr/local/lib/...(视 pip 权限) 当前用户或全局 Python
conda install xxx <conda_env>/lib/pythonX.Y/site-packages/ 仅该 conda 环境

3.1 venv 是默认最稳的隔离

venv 把项目所需的 Python 与包关在一个目录里,不动系统:

python3 -m venv ~/ws/.venv                # 创建
source ~/ws/.venv/bin/activate            # 激活:改 PATH 与 PS1,之后 which python 指向 venv
pip install numpy                         # 装到 venv
deactivate                                # 退出

venv 默认继承系统 site-packages(可用 --system-site-packages 显式打开)。与 ROS 2 共存:ROS 2 Humble 的 rclpy 是 apt 装的系统 Python 扩展;venv 若开了 --system-site-packages 且系统 Python 与 venv Python 版本一致,import rclpy 能过;否则要重装。

解释器怎么选到模块:import 的查找顺序是 sys.path——脚本所在目录、PYTHONPATH 环境变量、解释器自身的 site-packages 依次拼进去。which python3 回答"用的是哪个解释器",python3 -c "import sys; print(sys.path)" 回答"这个解释器去哪找模块",两问各自独立:装了 venv 却没激活是 PATH 问题,激活了仍 ModuleNotFoundError 多半是包装进了另一个解释器的 site-packages。语言侧的包与模块组织、虚拟环境的工程惯例不在本板块展开,ROS 2 工作区里的 Python 包布局归 ROS2-note。

3.2 PEP 668 与 externally-managed-environment

Ubuntu 24.04 起 Python 3.12 落实 PEP 668,系统 Python 目录被打了 EXTERNALLY-MANAGED 标记,直接 pip install xxx 会报错并建议 venv / pipx。正确解法三选一:

  1. 建 venv(默认)。
  2. pipx:给独立 CLI 工具用(pipx install black,每个工具一个隔离 venv + PATH shim)。
  3. --break-system-packages:应急,会把系统 Python 搞脏,不推荐。

Ubuntu 22.04 的系统 Python 3.10 不带该标记——/usr/lib/python3.10/EXTERNALLY-MANAGED 不存在,pip 22.0.2 直接写系统目录不受阻;限制从 24.04 + Python 3.12 起生效。判据是标记文件是否存在而不是发行版号:存在即强制 venv / pipx,不存在则隔离只靠自律,22.04 上的“能装”不等于“该装”。

3.3 conda 系四支

conda 是语言无关的包管理 + 环境管理器,自带 Python 解释器与 C 库;适合科学计算与 GPU 依赖栈(PyTorch、CUDA runtime)。

发行版 渠道默认 授权
Anaconda defaults 渠道 商业使用受限(企业 ≥ 200 人另计)
Miniconda 同上,但只装最小解释器 同上
Miniforge conda-forge 社区渠道 无商业条款顾虑,本板块推荐主线
mamba / micromamba 同 conda-forge,C++ 重写解析器、快 无

推荐 Miniforge + mamba 前端:

## 装 miniforge(用户级、不动系统 Python)
curl -L -O https://github.com/conda-forge/miniforge/releases/latest/download/Miniforge3-Linux-x86_64.sh
bash Miniforge3-Linux-x86_64.sh -b -p ~/miniforge3
source ~/miniforge3/bin/activate
mamba create -n ros python=3.10 numpy pytorch -c pytorch -c conda-forge
mamba activate ros

base 常驻的坑:conda init 会把 conda activate base 塞进 ~/.bashrc,之后所有 shell 的 PATH 前置 <miniforge>/bin。系统 Python 编译的 C 扩展(rclpy、apt 侧 python3-*)与 conda Python ABI 不匹配时导入即崩;LD_LIBRARY_PATH 被 conda 改写还会污染非 Python 程序。判据:与 ROS 2 共存时 conda config --set auto_activate_base false,只在需要的项目里显式 conda activate。conda 并非万能环境——它只统一了 Python 与 C 库的版本,统一不了 ABI:apt 侧用系统 Python 编译的扩展(rclpy、python3-*)在 conda 解释器下仍会因版本错位而导入失败,正确用法是“分环境用、不混装、不常驻 PATH”,而非“装了 conda 就不用管系统 Python”。

破坏面可以精确到文件名:conda base 常驻后 source ROS 2 Humble,which python3 落在 conda 解释器,PYTHONPATH 却指向系统 python3.10 的 site-packages,import rclpy 报 ModuleNotFoundError: No module named 'rclpy._rclpy_pybind11',伴随提示直接点出 C 扩展按解释器 ABI 命名——rclpy 的 C 扩展只有 cpython-310 版本,conda 解释器(如 3.13/3.14)找的是 cpython-314 同名后缀,于是找不到。

ROS 2 的安装排障页对这一报错列的就是同一判据。错位是版本级的、可预测的,不是“conda 与 ROS 2 天生互斥”。Qt 插件侧是另一条冲突线:conda 自带的 Qt 库与系统 Qt 在插件搜索路径上重叠时,Qt GUI 程序会因插件与库版本不匹配拒绝加载,报 Could not load the Qt platform plugin "xcb";判据是 QT_DEBUG_PLUGINS=1 打印的插件路径落在哪个前缀。

预编译交付(Cython 扩展与 ABI 绑定)。有些包只发编译产物:核心逻辑用 Cython 或 C 扩展编译成 xxx.cpython-311-x86_64-linux-gnu.so,随包不含 .py。两条硬约束与 3.3 的 ABI 坑同源:ABI 跟解释器小版本走——cpython-311 的 so 只能进 3.11,升到 3.12 必须重编;系统库要能解析——ldd xxx.so 看是否有 not found 项,缺的是运行期依赖。

排查导入失败的对应关系:ModuleNotFoundError: No module named 'xxx._xxx' 伴随 ABI 后缀提示 → 解释器版本不匹配;ImportError: ... undefined symbol: ... → 编译期与运行期的同名库版本不一致(典型是 conda 的 libstdc++ 盖了系统版);FileNotFoundError: libcrypto.so.3 一类 → 系统库缺失。三种都指向“换解释器/换前缀”,不是改代码能修的。

这类包常配类型桩:__init__.pyi(只有签名,无实现)+ py.typed 标记包本身已带类型信息,IDE 补全靠它;桩与运行时 .so 是两套产物,升级时要成对更新,否则补全出来的签名与真实行为对不上。

源码到可执行的编译流水线(make / cmake 只是调度这四步):

flowchart LR
    src[".c / .cpp 源文件"] --> pre["预处理<br/>头文件展开"]
    pre --> comp["编译<br/>生成汇编"]
    comp --> asm["汇编<br/>生成 .o"]
    asm --> link["链接<br/>ld + 依赖库"]
    link --> bin["ELF 可执行文件"]

坑:混装同一解释器是依赖地狱的起点

apt 的 python3-* 属于系统解释器,pip 只应装进 venv,conda 用自有解释器;把 pip 装回系统 Python 会同时冒犯 PEP 668 与发行版依赖。

4. 开发工具链与构建

4.1 build-essential 与 make

sudo apt install build-essential        # gcc g++ make libc6-dev dpkg-dev 一组

Ubuntu 22.04 默认 gcc 11、24.04 gcc 13。想切版本:sudo apt install gcc-12 g++-12 后用 update-alternatives 注册。最小 Makefile:

CFLAGS := -O2 -Wall -Wextra
main: main.o util.o
    $(CC) $(CFLAGS) -o $@ $^
%.o: %.c
    $(CC) $(CFLAGS) -c $< -o $@
clean:
    rm -f *.o main

$@ 目标、$^ 所有依赖、$< 第一个依赖。Makefile 里 recipe 前必须是 TAB,不是空格——这是最常见的语法报错源头。

从源码到可执行文件是四步:预处理(#include 展开与宏替换,g++ -E main.cpp 可单独看结果)、编译(源码 → 汇编,-S)、汇编(.s → .o 机器码,-c)、链接(.o 与库 → 可执行文件)。日常一条 g++ main.cpp -o main 只是把四步压进了一次调用;报错信息按步分界——头文件找不到停在预处理、语法错停在编译、undefined reference 停在链接。链接期的两类高频现场:

g++ main.o -L/opt/mylib/lib -lmylib -o main   # -lmylib 对应 libmylib.so,链接顺序敏感:被依赖的库放在使用它的目标文件之后
nm -D /usr/lib/x86_64-linux-gnu/libstdc++.so.6 | c++filt | head   # 查动态符号表并还原 C++ mangled name
  • C++ name mangling 与 ABI:编译器把重载、命名空间、参数类型编码进符号名(_Z3addii 还原为 add(int, int)),因此 C++ 符号表里的名字不能直接当库名用——跨编译器或跨标准大版本编译的 C++ 库不保证能直接链接,extern "C" 是关闭 mangling 供 C 侧调用的标准做法。这也是 Python 侧「conda Python 与系统 Python 的 C 扩展 ABI 不兼容」在编译语言侧的同源机制。
  • 静态 .a 与动态 .so:默认优先链接 .so,运行时由动态链接器按 DT_RPATH / LD_LIBRARY_PATH 定位——undefined reference 是链接期缺 -l / -L,程序启动报 error while loading shared libraries 是运行期找不到 .so,两阶段判据不同。

4.2 cmake 与 pkg-config

CMake 是跨平台构建元编译器:写 CMakeLists.txt、生成 Makefile 或 ninja 文件。

cmake_minimum_required(VERSION 3.16)
project(demo LANGUAGES C CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

find_package(PkgConfig REQUIRED)
pkg_check_modules(EIGEN3 REQUIRED IMPORTED_TARGET eigen3)   # 通过 pkg-config 找依赖

add_executable(main main.cpp)
target_link_libraries(main PRIVATE PkgConfig::EIGEN3)
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release    # 配置:源码 S、二进制 B
cmake --build build -j$(nproc)                     # 构建

pkg-config 让编译器知道 -I<include> 与 -L<lib> -l<name>;.pc 文件在 /usr/lib/x86_64-linux-gnu/pkgconfig/ 与 /usr/share/pkgconfig/,PKG_CONFIG_PATH 可加自定义路径。找不到库时先 apt-file search <name>.pc,多半是没装 -dev 包。

ROS 2 的 ament_cmake 是 CMake 的一层封装:find_package(ament_cmake REQUIRED) + ament_package(),其余语法与 CMake 一致。细节在 ROS2-note 第 1 章「ROS 2 生态与工程基线」的 colcon 构建与测试。

4.3 ccache

重复构建大型 C++ 工程(含 ROS 2 工作区)时,ccache 命中能省一半时间:

sudo apt install ccache
export CMAKE_C_COMPILER_LAUNCHER=ccache CMAKE_CXX_COMPILER_LAUNCHER=ccache
ccache -s     # 查命中率

4.4 git 基线与日常工作流

机器人项目里 git 既管代码也管 ROS 2 工作区。最小配置与两个高频坑:

git config --global user.name "…"      # 提交身份
git config --global user.email "…"
git config --global core.editor vim     # 提交消息默认编辑器(与「EDITOR 变量与 nano 三键」的 EDITOR 可一致)
git config --global init.defaultBranch main
git config --global pull.rebase true    # 避免无意义的合并提交

凭据两条路:

  • SSH(本板块默认):ssh-keygen -t ed25519 后推公钥到 GitHub / GitLab / 内部 Gitea;remote URL 走 git@host:owner/repo.git。与第 10 章的 ~/.ssh/config 共用一份密钥体系;机器人现场机常无交互终端,需 ssh-agent 预加载或直接用无口令密钥。
  • HTTPS + PAT(个人访问令牌):适合临时机;凭据靠 git credential-store 或 cache --timeout=3600 保存,不写到仓库里。

日常循环是三区模型:工作区(文件本体)、暂存区(git add 后的快照)、仓库(commit 后的历史)。git status -s 与 git log --oneline --graph 是两条查看命令;git add -p 按块暂存(一次改动多个关注点时分批提交);撤销按所在区选择——git restore <file> 丢弃工作区改动、git restore --staged <file> 退回工作区、git commit --amend 改最后一条提交(仅限未推送的);git clean -fd 删除未跟踪文件,执行前先 git clean -nd 预览。

分支与同步:

git switch -c fix/jitter      # 建分支并切换(旧写法 git checkout -b)
git switch main
git merge fix/jitter          # 合入:保留分支形状,产生合并提交
git rebase fix/jitter         # 变基:把当前分支的提交重放到目标分支顶端,历史成直线
git fetch && git status       # fetch 只更新远程跟踪分支,合并由后续 pull / rebase 完成

merge 与 rebase 的差别只在历史形状:pull.rebase true(上面的配置)让日常同步保持直线历史;已推送的提交不要 rebase——改写哈希会让协作者的分支错位,必须强推时用 git push --force-with-lease(先检查远端未被他人更新,比 --force 安全)。

出错后的恢复工具:git reset --soft/--mixed/--hard HEAD~1 按保留程度回退本地历史;git revert <hash> 生成反向提交,适合已推送的撤销;git stash 临时收起未提交改动;git cherry-pick <hash> 把单个提交挪到当前分支;git reflog 记录 HEAD 的全部移动,误 reset / rebase 后靠它找回提交(git reset --hard <reflog 里的哈希>)。定位"哪个提交引入了 bug"用二分:git bisect start <坏> <好> 后 git bisect run python3 test.py,脚本退出码驱动自动收敛到肇事提交。

两个日常坑:

  • .gitignore 少一行:build/ install/ log/ .venv/ *.bag __pycache__/ .vscode/(除非团队约定提交)——ROS 2 colcon 工作区不写 build/install/log 会一次提交方 G 产物。
  • 行尾符(CRLF vs LF):与 Windows 侧仓库交互时,git config --global core.autocrlf input 避免代码里混入 \r;与第 2 章「Shell 与命令行」的 sed 去 CRLF 做法一起处理。

分支模型选型(GitFlow / trunk-based)、git rebase -i 交互式整理、LFS 大数据资产管理不在本板块展开。

配置格式读进程序:json / yaml / xml。第 2 章「结构化格式」的三种格式在 Python 侧对应三个解析器:标准库自带的 json 与 xml.etree.ElementTree,以及第三方的 PyYAML(pip install pyyaml 或 apt python3-yaml,不随发行版自带)。最小读取片段:

import json, xml.etree.ElementTree as ET
from pathlib import Path
import yaml                                    # 第三方:PyYAML,需单独安装

params = yaml.safe_load(Path("params.yaml").read_text())   # 层级 dict;safe_load 只构造基本类型
meta = json.loads(stdout_of_rosbag2_info)                   # 工具输出→ dict/list,无注释与尾逗号空间
root = ET.fromstring(Path("robot.urdf").read_text())       # 树遍历:root.attrib 取属性,root.iter("link") 按标签找

三条工程判据:

  • yaml.safe_load 而非 yaml.load:构造器的档位决定执行面。载荷 !!python/object/apply:os.system ['touch /tmp/…'] 喂给 safe_load 与不传 Loader 的 yaml.load(PyYAML 5.1 起发 YAMLLoadWarning、按 FullLoader 档处理;22.04 的 python3-yaml 为 5.4)都会报 ConstructorError: could not determine a constructor for the tag …,载荷不执行;只有显式 Loader=yaml.UnsafeLoader 才构造任意对象、命令被执行。风险不在 yaml.load 这个名字,而在 UnsafeLoader 档——只要不显式选它,未知标签进不来;但解析不受信的配置或录制数据仍应固定用 safe_load,不依赖默认档的历史行为。
  • 读回的就是 Python 结构:YAML 层级→嵌套 dict、XML 属性→attrib 字典、JSON 数组→list——与第 2 章「结构化格式」的"解析→结构→再序列化"是同一件事的程序侧版本;字段缺失在配置加载入口即检查(KeyError 比下游用到时才爆好查)。
  • 格式选型与场景绑定(沿第 2 章「结构化格式」的分工):参数与配置用 YAML、工具输出与 API 用 JSON、模型描述用 XML;ROS 2 参数文件的 /**: ros__parameters 层级约定归 ROS2-note 第 3 章,MJCF / URDF 的字段语义归各自板块。工具链节的语境补一句:git 仓库里的 package.xml(XML)、参数文件(YAML)与 .clang-format / CI 配置(YAML)同理——纳入版本管理的配置改动,提交前用对应解析器跑一次语法校验(python3 -c "import yaml,sys; yaml.safe_load(open(sys.argv[1]))" <file> 一类最小检查),比合入后在运行期爆解析错便宜得多。

日常分支工作流的形态(feature 分支开发、合回 main、打 tag 发布):

gitGraph
    commit id: "初始提交"
    branch feature
    checkout feature
    commit id: "改代码"
    commit id: "本地测试"
    checkout main
    merge feature id: "合回 main"
    commit id: "发布 tag"

判据:先 strace 再 gdb

进程还活着但行为不对,先看系统调用(打开哪个文件、连哪个地址);进程已经崩了,才上 gdb 看栈。两条线的入口不同,别一上来就挂调试器。

5. 调试三条切入线

症状 首选工具 拿到什么
崩溃、断点、变量看值 gdb 调用栈、局部变量、寄存器
Python 脚本断点与事后栈 pdb / breakpoint() 调用栈、逐行执行、事后可交互
程序"到底在跟内核要什么" strace 系统调用序列与返回值
库函数被谁调、参数是什么 ltrace 用户态库函数调用
卡住、CPU 100% gdb -p <pid> 或 perf 现场栈

5.1 gdb:崩溃栈与断点

gdb ./main                          # 需编译时带 -g
(gdb) run arg1 arg2
(gdb) bt                            # 崩溃后看 backtrace
(gdb) frame 3                       # 切到第 3 层栈帧
(gdb) print some_var                # 打印变量
(gdb) break main.cpp:42             # 断点
(gdb) continue

ROS 2 C++ 节点崩溃时先看是不是 libstdc++ 抛异常没接住——gdb 里 catch throw 打断点、run、崩溃即停在异常构造点。

5.2 strace 与 ltrace

第 1 章「用户态与内核态的分界」已演示 strace -c;跟踪单条调用序列:

strace -f -e trace=network -p <pid>       # -f 跟子进程,只看网络 syscall
strace -e openat,read -o out.txt cmd      # 只记录文件打开与读
ltrace -e 'libc.so.6*' ./main              # 库调用

Python 侧对应物是 pdb:脚本里插 breakpoint()(Python 3.7+)进交互断点,崩溃后 python3 -m pdb -c continue script.py 从事后异常现场直接进调试。

ROS 2 launch 起不来时 strace -f -e trace=execve 直接看它 exec 了什么、缺哪个文件——比翻日志快。

远程开发承接第 10 章的 SSH 主线:VS Code Remote-SSH 把编辑器进程留在开发机、执行放到远端;clangd + clang-format 在远端提供符号数据库(cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON 生成 compile_commands.json)。Remote-SSH 对远端的硬门槛是 glibc 2.28 与 libstdc++ 3.4.25(VS Code 1.86 起,见 VS Code 远程开发排障文档);Ubuntu 22.04(glibc 2.35)与 24.04(2.39)都在支持范围内,18.04 及更早则不再被新版 VS Code Server 支持。

6. 要点

  • 编辑器:nano 应急、vim 是上位机 SSH 现场的兜底;EDITOR / VISUAL 与 update-alternatives --config editor 一起设;~/.vimrc 三条最小配置够用;neovim / Jupyter 按需引入。
  • apt / dpkg 分层:dpkg 单包、apt 索引与依赖、apt-file 反查文件来源;CI 与脚本用 apt-get 不用 apt。
  • PPA 与 Snap 的信任边界:PPA 装的是"哪个上游构建",用后可移除;桌面 snap 化对服务器与板端有副作用,服务器安装走 server 或 cloud image。
  • Python 三渠道:apt 系统包、pip 用户/venv、conda 自带解释器;venv 是默认最稳隔离;PEP 668 强制 venv;conda 与 ROS 2 共存需 auto_activate_base false;which python3 与 sys.path 分别回答"哪个解释器"与"去哪找模块"。
  • C/C++ 工具链:build-essential 一把装齐、cmake -S . -B build 是标准用法、pkg-config 找不到库先补 -dev 包、ccache 让重复构建提速;源码到可执行文件分预处理 / 编译 / 汇编 / 链接四步,undefined reference(链接期)与 error while loading shared libraries(运行期)判据不同;C++ mangling 决定跨编译器库不保证可链。
  • git:基线靠 SSH 密钥与第 10 章共用;日常循环是工作区 / 暂存区 / 仓库三区,撤销按所在区选命令;已推送的提交不 rebase,强推用 --force-with-lease;误操作找回靠 reflog,定位肇事提交靠 bisect run;.gitignore 与行尾符是两个日常坑。
  • 调试三条切入线:崩溃 → gdb、syscall → strace、库调用 → ltrace、Python → pdb;ROS 2 C++ 崩在异常构造点用 catch throw;远程开发靠 VS Code Remote-SSH + clangd 组合。

参考文献与延伸阅读

  • GDB. 文档. 文档
  • strace. 手册页. 文档
  • Visual Studio Code. Remote SSH 文档. 文档