ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

rosdep update 超时排查:镜像源替换与离线构建指南

rosdep update 超时排查:镜像源替换与离线构建指南 折腾过 ROS 的人大概都遇到过这个场景终端里敲下rosdep update进度条卡在reading in sources list data from /etc/ros/rosdep/sources.list.d这一行光标闪啊闪等了五分钟最后蹦出一串红字ERROR: error loading sources list: (The read operation timed out,)。删掉重来、切网络、重启机器一圈折腾下来还是老样子。这个 rosdep update 报 time out 的问题本质不是你的环境装坏了而是 update 过程中需要访问几个固定的远端地址去拉取依赖规则数据库这些地址在部分网络环境下连通性很差请求发出去迟迟没有响应程序等够时间就抛出超时。这篇文章我把自己在不同机器、不同 ROS 版本上处理这个问题的完整思路和可复现步骤整理出来从原理到三种由易到难的方案都会讲清楚不管你是刚装完 Melodic、Noetic还是 ROS 2 的 Foxy、Humble都能找到对得上号的做法。1. rosdep update 到底在干什么为什么会 time out先把机制讲透不然换源也是盲改。rosdep 是 ROS 生态里的系统依赖管理器你在编译工作空间时经常看到的 System dependencies not met 或者编译报缺库很多就是靠rosdep install来补的。它本身不存依赖信息依赖信息都在一个叫 rosdistro 的规则库仓库里rosdep update干的事情就是把这个规则库的索引和规则文件拉到本地缓存之后rosdep install才能离线查表、知道某个包在 Ubuntu 上该装哪个 apt 包。1.1 rosdep 的定位和 update 更新的到底是一份什么数据rosdistro 仓库里存的是一堆 YAML 文件。最上层是index-v4.yaml它像个总目录列出了每个 ROS 发行版distro对应的描述文件位置然后每个发行版有自己的distribution.yaml描述这个发行版下所有功能包的名字、版本、源码仓库、依赖关系再往下还有base.yaml、python.yaml、ruby.yaml这些 rosdep 规则文件它们把「依赖键」翻译成「系统包名」比如把boost映射成libboost-all-dev。rosdep update就是逐层把这几类文件下载下来按发行版和规则整理后缓存到~/.ros/rosdep/sources.cache/里。理解了这一点就能明白它必须联网而且必须访问到那份原始规则库本地断网或者访问不通update 就没有数据可缓存。1.2 update 流程里几个必须联网的环节默认配置下rosdep update大致会依次访问这几类地址。第一类是它自己读取的源列表也就是sources.list.d里20-default.list记录的那些 URL默认指向代码托管平台上的 raw 文件地址第二类是 rosdistro 的索引文件index-v4.yaml这个在较新版本的 rosdep 里会由rosdistro这个 Python 包去下载第三类是各个发行版的distribution.yaml以及releases/targets.yaml第四类是 rosdep 规则文件本身。这几个环节里只要有一个请求超时整个 update 就会中断并报The read operation timed out。所以真正的问题不是「网络全断」而是其中某个地址响应慢得超过了程序内置的超时阈值。1.3 快速判断卡在哪一步不要一上来就瞎改先看报错和进度。运行rosdep update时它会把当前正在读的内容打印出来比如卡在reading in sources list data from /etc/ros/rosdep/sources.list.d说明读取源列表这一步有问题卡在Query rosdistro index或Add distro noetic之类说明是中游的索引和发行版文件下载慢。还有一种情况是全部进度都走完了最后卡在updated cache in ~/.ros/rosdep/sources.cache附近超时那多半是某个规则文件没下全。先用rosdep update --debug或者加-v看详细日志把卡点定位清楚后面的方案才能对症下药。这是我在处理这类问题时养成的第一个习惯先定位再动手。2. 处理思路怎么选三种方案背后的取舍逻辑定位完卡点接下来就是选方案。社区里流传的办法很多质量参差不齐我按「改动成本」和「可靠性」把它归成三条路线调大超时、切换镜像源、手动离线构建。下面说清楚每种适用什么场景为什么我把换源放在首选位置。2.1 只把超时时间调大为什么大多数时候治标不治本很多人第一反应是去改 rosdep 源码里的超时参数比如把rosdistro里download_rosdistro_index用的 timeout 从 15 秒改成 60 秒甚至更久。这招在某些「只是偶尔抖一下」的网络里确实能蒙混过关但你要明白它解决的是「等待不够久」而不是「访问不到」。如果你的网络到那些远端地址的响应本身就是几百毫秒到几十秒的剧烈波动甚至动不动就连接重置那调大超时只会让你从「等 15 秒失败」变成「等 120 秒失败」体验更差。所以这招我只在临时应急、或者网络只是轻微抖动时用不会当成长期方案。2.2 换镜像源为什么是首选方案真正稳定的思路是把那几个远端地址替换成国内同步了 rosdistro 内容的镜像站地址。清华 TUNA、中科大 USTC 等镜像都同步了 rosdistro 目录里面的index-v4.yaml、rosdep规则文件、releases目录一应俱全。镜像站从国内访问延迟低、带宽足请求基本秒回超时自然就消失了。它的另一个好处是改动集中要么设一个环境变量要么改一两个配置文件不需要动整个工具链。这就是我默认推荐先试镜像的原因收益最高、风险最低、可回滚。2.3 什么情况下必须上手动离线方案镜像也不是万能。有些内网、离线开发环境或者镜像站当天恰好没同步全、某个发行版文件缺失这时候就得走手动方案自己在能联网的机器上把 rosdistro 规则库 clone 下来整包拷到目标机器然后把源列表和索引都指向本地文件路径。这条路最麻烦但最可靠一旦搭好之后 update 完全不依赖外网哪怕断网也能跑。我在做机器人交付、给客户现场部署时就经常用这套因为它把「网络不可控」这个变量彻底消掉了。3. 实操三种方案逐步落地这一节是重点三种方案我都会给到能直接复制的命令。建议按顺序试方案一不行再上方案二前两个都搞不定再考虑方案三。每一步我都标注了意图别只照抄不理解。3.1 方案一用环境变量把索引指向国内镜像较新版本的 rosdep0.20 及以上支持通过ROSDISTRO_INDEX_URL这个环境变量覆盖索引地址这是改动最小的方式。先确认你的 rosdep 版本rosdep --version如果版本是 0.20 往上直接这样设置并更新export ROSDISTRO_INDEX_URLhttps://mirrors.tuna.tsinghua.edu.cn/rosdistro/index-v4.yaml rosdep update为了让这个变量对每个新终端都生效把它写进~/.bashrcecho export ROSDISTRO_INDEX_URLhttps://mirrors.tuna.tsinghua.edu.cn/rosdistro/index-v4.yaml ~/.bashrc source ~/.bashrc提示改完环境变量后一定要新开一个终端或者 source 一下否则当前 shell 里不生效你会以为方法没用。这个方案的原理是让 rosdistro 从镜像站拉索引索引里对发行版文件的引用会基于索引所在地址做相对解析所以跟着也走镜像。实测下来Noetic 和 Humble 上只要网络能通到清华镜像这一步就能把超时消灭掉。如果设完之后还报错注意看报错地址是哪个站如果还是原始地址说明你的 rosdep 版本没读这个变量得走方案二。3.2 方案二改源列表文件和源码里的硬编码地址方案一无效通常是因为老版本 rosdep 把地址硬编码在 Python 源码里不认环境变量。这时候得手动改两处一是源列表文件二是几个 Python 常量。先改源列表。编辑/etc/ros/rosdep/sources.list.d/20-default.list把里面的原始地址整行替换成镜像地址# os-specific listings first yaml https://mirrors.tuna.tsinghua.edu.cn/rosdistro/rosdep/osx-homebrew.yaml osx # generic yaml https://mirrors.tuna.tsinghua.edu.cn/rosdistro/rosdep/base.yaml yaml https://mirrors.tuna.tsinghua.edu.cn/rosdistro/rosdep/python.yaml yaml https://mirrors.tuna.tsinghua.edu.cn/rosdistro/rosdep/ruby.yaml gbpdistro https://mirrors.tuna.tsinghua.edu.cn/rosdistro/releases/fuerte.yaml fuerte改之前先备份一份出问题能回滚sudo cp /etc/ros/rosdep/sources.list.d/20-default.list /etc/ros/rosdep/sources.list.d/20-default.list.bak然后改 Python 源码里的常量。用你熟悉的编辑器打开下面这几个文件路径可能因 Python 版本略有不同Python3 是dist-packages老环境可能是python2.7/dist-packages。先找到 rosdistro 的索引地址# /usr/lib/python3/dist-packages/rosdistro/__init__.py DEFAULT_INDEX_URL https://mirrors.tuna.tsinghua.edu.cn/rosdistro/index-v4.yaml DEFAULT_TARGETS_URL https://mirrors.tuna.tsinghua.edu.cn/rosdistro/releases/targets.yaml再找 rosdep2 里的两个常量# /usr/lib/python3/dist-packages/rosdep2/sources_list.py DEFAULT_SOURCES_LIST_URL https://mirrors.tuna.tsinghua.edu.cn/rosdistro/rosdep/sources.list.d/20-default.list# /usr/lib/python3/dist-packages/rosdep2/rep3.py REP3_TARGETS_URL https://mirrors.tuna.tsinghua.edu.cn/rosdistro/releases/targets.yaml改完保存清一下旧缓存再重新 updaterm -rf ~/.ros/rosdep/sources.cache rosdep update注意直接用pip装的 rosdep 和apt装的路径不一样。apt装的在前面的/usr/lib/python3/dist-packages/pip装的在~/.local/lib/python3.x/site-packages/。改之前用python3 -c import rosdistro; print(rosdistro.__file__)确认真实路径别改错文件。这一步的逻辑是把所有会出去请求的地址统一换成镜像等于把整个下载链路都搬到国内。我在 Noetic 上处理一个顽固超时就是靠这一手改完之后第一次就过了。3.3 方案三完全离线手动构建本地 rosdep 数据库如果目标机器根本连不上外网或者镜像也不稳那就上离线方案。核心思路是在能联网的机器上把 rosdistro 仓库完整拉下来拷到目标机器然后把 rosdep 的所有引用都指向本地文件。第一步在一台能联网的机器上克隆规则库。用镜像站或者代码托管平台的镜像仓库都行git clone https://mirrors.tuna.tsinghua.edu.cn/git/rosdistro.git克隆完检查一下目录应该能看到index-v4.yaml、rosdep/、releases/、noetic/这些内容。把它打包拷到目标机器比如放到/opt/rosdistrosudo mv rosdistro /opt/rosdistro第二步把源列表改成指向本地文件。编辑20-default.list用file://协议yaml file:///opt/rosdistro/rosdep/osx-homebrew.yaml osx yaml file:///opt/rosdistro/rosdep/base.yaml yaml file:///opt/rosdistro/rosdep/python.yaml yaml file:///opt/rosdistro/rosdep/ruby.yaml gbpdistro file:///opt/rosdistro/releases/fuerte.yaml fuerte第三步让 rosdistro 也读本地索引。设置环境变量或用方案二的方式改源码常量指向本地文件export ROSDISTRO_INDEX_URLfile:///opt/rosdistro/index-v4.yaml这里有个细节要注意索引文件内部对各发行版文件的引用默认可能是相对路径也可能是绝对 URL。克隆下来的index-v4.yaml里如果写的是远端地址本地解析时仍会去联网。稳妥做法是打开这个文件确认distribution和distribution_cache字段指向的是相对路径或者手动改成file:///opt/rosdistro/...。我一般会直接检查一遍再 updategrep -n distribution /opt/rosdistro/index-v4.yaml | head确认无误后执行rosdep update这套走下来目标机器断网也能 update 成功。缺点是规则库不会自动更新过一段时间想让 rosdep 认识新的包得重新同步一次。4. 换完源还是报错常见问题与排查即使方法对落地时也会撞上各种坑。这部分我把踩过的、帮别人排查过的典型问题汇总成一个速查表再补充几个容易被忽略的细节。4.1 问题现象和对应的排查方向报错或现象大概率原因处理办法仍报timed out且地址没变环境变量没生效或版本不认该变量新开终端确认变量值或走方案二改源码报yaml.scanner.ScannerError镜像上的某个 yaml 下载不完整或格式错rm -rf ~/.ros/rosdep/sources.cache后重试报ERROR: unable to process source镜像没同步对应发行版文件换另一个镜像或改用手动离线方案报Permission denied改文件时没用 sudo 编辑系统目录加sudo编辑前先备份update 成功但 install 还缺库缓存没刷新或发行版判断错误清缓存重 update确认ROS_DISTRO正确换了 pip 装的 rosdep 仍超时改的是 apt 版的路径用rosdistro.__file__找真实路径4.2 几个容易忽略但其实很关键的细节第一个细节是缓存。改完源之后不一定要删缓存但如果你之前已经下了一半的旧数据残留有可能导致解析异常。养成习惯改配置后先rm -rf ~/.ros/rosdep/sources.cache再跑。第二个细节是发行版环境变量。rosdep判断当前用哪个发行版靠的是ROS_DISTRO或者它自己探测如果你的系统里同时装了 ROS 1 和 ROS 2ROS_DISTRO没设对它可能去找一个不存在的发行版文件从而报错。跑之前先echo $ROS_DISTRO看一眼是否为你期望的值。第三个细节是镜像的同步状态。镜像站偶尔会有同步延迟或者临时故障。如果某个镜像连续两次都拉不下来果断换一个镜像站别死磕。提示rosdep update和sudo rosdep init是两个不同步骤。init负责把20-default.list写到/etc/ros/rosdep/sources.list.d/update才是真正拉数据。如果你连init都失败那要先单独处理init它同样可能因为访问不通而卡住思路和本文一致。5. 版本差异在我踩过的坑里的体现最后说点版本差异的事因为很多人照着老教程操作却怎么都不生效问题往往出在版本对不上。ROS 1 的 Melodic、Noetic 和 ROS 2 的 Foxy、Humble它们自带的 rosdep 版本不完全一样源码路径、支持的变量、索引文件名都可能有差别。老版本 rosdep 用的是index-v4索引文件名是index-v4.yaml更早的版本甚至用index-v3改的时候先看一眼镜像目录里实际提供的是哪个文件名别照着网上的老例子硬套。还有一个区别在于环境变量支持。前面提过ROSDISTRO_INDEX_URL是较新版本才认的如果你的rosdep --version显示是 0.19 及以下那么这个变量很可能被忽略这时候方案一不生效是正常的直接上方案二改源码常量。我自己就吃过这个亏在一台老 Melodic 机器上设了半天环境变量没反应后来才发现版本太老转去改rosdistro/__init__.py里的DEFAULT_INDEX_URL一次就成了。另外用pip install rosdep升级和用apt install python3-rosdep装的版本管理方式不同混用时容易改错文件。最稳的做法是无论装在哪都用python3 -c import rosdistro, rosdep; print(rosdistro.__file__); print(rosdep.__file__)把真实路径打出来对着路径去改别猜。把确认路径这一步做成固定动作后我处理这类超时问题的成功率基本稳定了剩下的就只是挑一个连通性好的镜像站而已。
返回列表