
WSL2刚装好那阵子我有一件事一直很头疼手头一个基于ROS 1的旧项目必须用Gazebo 11另一个基于ROS 2的新项目又要用新版Gazebo园gz-sim两边一碰就是依赖冲突。apt装完gazebo11再装gz-sim大概率会把系统的ignition相关库搅得一塌糊涂最后两边都起不来。折腾了一周最终老老实实走WSL2独立编译Gazebo这条路用源码把经典版和园版分别装到独立目录才真正实现了多版本共存。这篇文章就是把我整个过程里的设计思路、编译参数、踩坑记录和最终管理方案整理出来。适合正在WSL2里被Gazebo版本问题折磨的人也适合想在一个Windows环境下同时维护ROS 1和ROS 2仿真项目的人。文章会从为什么需要独立编译讲起一直讲到WSL2无GPU条件下的渲染取舍、多版本环境切换脚本和常见报错排查尽量让看完的人能直接照做。1. 为什么说多版本共存是个真需求——版本绑定关系与系统包管理器的困境1.1 每个ROS发行版都锁死了Gazebo的版本范围先说清楚冲突的根源。Gazebo并不是一个完全独立的软件它有一套庞大的依赖链包括sdformat、ignition-math、ignition-transport、ignition-msgs、Qt、Ogre等。而ROS生态里的gazebo_ros_pkgs或者ros_gz这些适配插件编译时都要求链接指定版本的Gazebo库。简单说ROS 1 Noetic默认配套的gazebo_ros_pkgs是按Gazebo 9/11编译的。ROS 2 Foxy、Galactic、Humble的ros_gz系列分别对应Ignition Fortress / Garden / Harmonic也就是Gazebo园的不同发行版。ROS 2 Jazzy开始则明确主推gz-sim v9Harmonic和v10Ionic。所以一个机器上如果同时有ROS 1和ROS 2或者有两个不同的ROS 2发行版就天然需要两套Gazebo。问题是apt装出来的gazebo11和gz-sim它们底层依赖的libsdformat、libignition-*系列库版本不同强行装在一起经常会让其中一个插件在运行时因为加载到错误版本的共享库而崩溃。这种问题不会在安装阶段报错往往是gazebo启动后加载插件那一刻才瞬间段错误排查起来非常痛苦。1.2 apt和PPA在处理多版本时的结构性短板用apt装软件本质上是一套全局依赖方案。它要求某个库在系统里只有一个版本所有包都链接到那个版本。这对追求稳定的服务器环境是优点但对我这种需要多版本并存的开发场景就是噩梦。有人说可以加第三方PPA比如某个社区维护的gazebo仓库但PPA的问题是它跟你当前Ubuntu发行版深度绑定而且往往只维护最新一两个版本。更麻烦的是PPA会覆盖系统里已有的ignition库某高校实验室朋友就遇到过一次装完某个PPA里的gz-sim后原来能跑的gazebo11启动时直接报找不到libignition-common3.so.3因为被升级成了4.x。全局依赖一旦被破坏恢复成本很高。1.3 独立编译的真实收益隔离、可控、可回滚独立编译Gazebo的意思是不依赖系统包管理器去装整套Gazebo而是从源码拉取通过CMake的CMAKE_INSTALL_PREFIX指定一个独立目录比如/opt/gazebo-classic-11或者/opt/gazebo-harmonic。这样每个版本连带着它自己的依赖全部装在自己的目录里通过环境变量切换当前用哪一套。这带来的好处很实在每个版本独占前缀目录相互之间完全不碰对方的.so文件。某个版本出问题改环境变量切回另一套即可不需要重装。可以自由选择源码分支、加补丁、禁用不需要的组件。编译产物不污染系统目录卸载时直接删文件夹。代价是编译时间确实长。我的机器是8核16线程Gazebo Classic 11全量编译大概40分钟gz-sim Harmonic依赖一套全编译下来接近一个半小时。但为了不再被依赖地狱反复折磨这个时间我觉得值得花。2. WSL2环境里提前铺平的准备工作2.1 确认系统版本与WSL内核配置开始编译前先确认环境。我用的WSL2发行版是Ubuntu 22.04和Ubuntu 24.04分别测过整个过程在两者上都跑通了。较新的WSL2默认支持WSLg和systemd但要注意两点第一WSL内核需要保持更新Windows端执行wsl --update确保内核版本不落后第二虽然在较新版本里systemd默认开启但保险起见仍然建议检查/etc/wsl.conf如果缺失systemd配置最好补上[boot] systemdtrue这个配置对后续跑ROS 2的daemon、gz sim的server进程管理都有帮助。改完wsl.conf记得在Windows终端里执行wsl --shutdown再重进配置才会生效。2.2 显卡与图形栈的前提认知WSL2里默认的图形方案是WSLg它把Linux侧的GUI程序直接呈现到Windows桌面不需要额外配置DISPLAY变量。但这里有个很关键的细节WSLg里GPU加速走的是Mesa的D3D12虚拟化驱动有些GL特性支持不完整。Gazebo的实体渲染默认依赖Ogre 1.x在WSL2里经常出现窗口能打开但画面全黑、或者转场极慢的情况。这不是编译的问题也不是代码的问题而是虚拟GPU驱动与Ogre兼容性有限。我在准备阶段就会装好Mesa工具链和必要的EGL库方便后面编译和排错sudo apt update sudo apt install -y build-essential cmake git pkg-config \ mesa-utils libegl1-mesa-dev libgl1-mesa-dev libglu1-mesa-dev \ libx11-dev libxext-dev libxcb-* x11-utils同时不要装Ogre的apt版本因为独立编译后我们要按需自己编译Ogre。如果系统apt里已有libogre-1.12-dev这类包建议先卸掉避免编译时CMake抓到错误版本。2.3 编译依赖的最小集合建议Gazebo的依赖列表很长逐个手敲apt命令太容易漏。我的习惯是先装一组基础构建工具和常用库再按具体版本补充。下面这组命令在不同Ubuntu版本上都能用sudo apt install -y \ libtinyxml2-dev libcurl4-openssl-dev libyaml-dev \ libprotobuf-dev protobuf-compiler \ libboost-all-dev libeigen3-dev libbullet-dev \ libgts-dev libode-dev libsimbody-dev \ libqwt-qt5-dev libqt5opengl5-dev qtbase5-dev \ libopenal-dev libsdl2-dev libjpeg-dev \ libtiff-dev libpng-dev \ python3-pip python3-setuptools为什么单独强调libtinyxml2、protobuf和qt5因为sdformat解析world文件依赖tinyxml2通信层依赖protobuf而Gazebo Classic的GUI和gz-sim的界面都依赖Qt5。这些库如果版本不对或者缺失编译期不一定报错但运行期很可能因为so文件版本不匹配而崩溃。安装完成后用ldconfig -p | grep检查几个关键库是否存在。2.4 工作目录与编译时长规划强烈建议把源码统一放在~/gazebo-src下安装目录统一放在/opt下比如mkdir -p ~/gazebo-src/opt目录需要root权限所以需要提前用sudo创建并设置好属主sudo mkdir -p /opt/gazebo-classic-11 /opt/gazebo-garden /opt/gazebo-harmonic sudo chown -R $USER:$USER /opt/gazebo-*-*这样整个编译过程不用反复sudo make install也比较安全。编译时长方面Gazebo Classic 11全量编译大概40到60分钟gz-sim系列因为要编译ignition全家桶至少1小时起步。编译器并行数建议用nproc的一半比如16线程机器用-j8避免内存被吃满。我第一次无脑-j16直接把16GB内存挤爆系统卡到鼠标都不动。3. 从源码独立编译Gazebo Classic与Gazebo园的核心流程3.1 编译Gazebo Classic 11的完整命令序列Gazebo Classic 11是ROS 1 Noetic会用到的主要版本它和后来改名后的gz-sim在构建系统上有区别Classic使用传统的CMake和make流程相对简单。下面这段命令是我实际完整跑通的cd ~/gazebo-src git clone https://github.com/gazebosim/gazebo-classic -b gazebo11 gazebo-classic cd gazebo-classic mkdir build cd build cmake .. \ -DCMAKE_INSTALL_PREFIX/opt/gazebo-classic-11 \ -DCMAKE_BUILD_TYPERelease \ -DENABLE_TESTINGOFF \ -DBUILD_TESTINGOFF \ -DUSE_UPSTREAM_OGREOFF make -j8 make install几个参数的解释CMAKE_INSTALL_PREFIX这步是整个多版本共存方案的核心单独指定安装目录避免所有文件散落到/usr/local。USE_UPSTREAM_OGREOFF是让Gazebo编译时使用其内置的Ogre版本否则会去抓系统里可能存在的错误Ogre版本也是避免黑屏渲染问题的一种手段。测试默认可以关掉Gazebo的测试套件对依赖要求很高开着容易在编译阶段额外碰壁。编完后设置临时环境变量做一次冒烟测试export LD_LIBRARY_PATH/opt/gazebo-classic-11/lib:$LD_LIBRARY_PATH export PATH/opt/gazebo-classic-11/bin:$PATH gazebo --version如果输出里有Gazebo multi-robot simulator, version 11.x.x说明基本成功。3.2 编译Gazebo园gz-sim系列时的依赖层级gz-sim原来的Ignition Gazebo和Classic完全是两套哲学。它依赖的ignition库非常多而且每个库都有严格的版本前缀符号比如libgz-transport13、libgz-math7等。编译前必须先确认要编译哪个release。这里以Harmonic对应gz-sim v8为例它的依赖组合比较成熟也是Jazzy之前ROS 2 Humble时代用得多的一套。我建议用gz-tools官方的构建脚本但自定义INSTALL_PREFIXcd ~/gazebo-src git clone https://github.com/gazebosim/gz-sim -b gz-sim8 cd gz-sim # 官方推荐用colcon编译workspace我们改成指定前缀 export GZ_VERSIONharmonic export GZ_INSTALL_PREFIX/opt/gazebo-harmonic这里的关键是gz-sim会在构建时自动下载依赖源码并且默认安装到/usr/local。我们不想污染系统所以要设置CMAKE_INSTALL_PREFIX让它安装到/opt下。如果依赖库存在源码目录的build里编译确实会稍微复杂一些需要逐个指定前缀。我的做法是先编译依赖库再返回编gz-sim。命令大致如下# 在gz-sim源码目录内逐个进入依赖工程 # 以gz-cmake为例 git clone https://github.com/gazebosim/gz-cmake -b gz-cmake3 cd gz-cmake mkdir build cd build cmake .. -DCMAKE_INSTALL_PREFIX/opt/gazebo-harmonic make -j8 make install cd ~/gazebo-src/gz-sim # 之后configure时告诉CMake去哪里找依赖 cmake .. \ -DCMAKE_INSTALL_PREFIX/opt/gazebo-harmonic \ -DCMAKE_PREFIX_PATH/opt/gazebo-harmonic \ -DCMAKE_BUILD_TYPERelease有个更省事的方法是用colcon管理整个workspace。如果你已经在用ROS 2那套工作流跟colcon天然匹配cd ~/gazebo-src/gz-sim colcon build --cmake-args \ -DCMAKE_INSTALL_PREFIX/opt/gazebo-harmonic \ -DCMAKE_PREFIX_PATH/opt/gazebo-harmonic \ --merge-install用colcon的好处是会按依赖顺序自动完成构建不容易漏。坏处是如果某个依赖需要自定义patchcolcon的方式反而不好干预。对大多数场景来说官方这套脚本已经足够稳定。3.3 为什么等了这么久——源码编译的三个关键收益有人可能会问直接下载官方预编译包不也能指定安装目录吗实际试用过就会发现预编译包的链接预期还是很强的系统绑定它们找依赖时依旧倾向于libz和libstdc等系统库虽然隔离效果比apt好但遇到Ogre和Qt这种重依赖还是容易跟系统已有的库发生ABI冲突。源码编译的好处一是能把Ogre、Qt这些重依赖统统编进你自己指定的前缀目录从根源上切断对系统的依赖二是可以打patch。比如Gazebo Classic在WSL2里的Ogre黑屏问题网上有各种渲染设置的修改源码编译后可以直接改渲染脚本三是你想同时保留Gazebo 11和Gazebo 9也可以从分支切出来重复该流程改一下前缀目录就行而预编译包很难做到两个大版本同时并排存在。4. WSL2无GPU条件下的渲染问题与无头运行方案4.1 WSL2里的实体渲染为什么慢到让人怀疑人生Gazebo Classic启动一个稍微复杂的world比如带地面纹理和几十个模型的场景在WSL2里转一圈镜头的时候帧率能掉到个位数。原因很直接WSL2默认的GPU路径是Microsoft的虚拟化驱动Gazebo用的Ogre渲染后端对这个环境的支持是有限的经常退化成软件渲染llvmpipeCPU直接满载。这一点必须在编译之前就明白我们在WSL2里跑Gazebo主要是跑仿真逻辑、传感器数据流和ROS接口不是拿它当游戏引擎看画面。把预期调整好后续很多卡顿就不再是焦虑点。4.2 软件渲染环境下的速度优化配置即便没有实体GPU也可以从几个方向把仿真跑得更顺畅第一降低渲染频率。Gazebo的渲染线程和物理线程耦合度不高很多稳定版本的默认渲染频率其实是固定值可以通过环境变量或者参数降低。比如在上层节点里用visual简化显示关闭阴影减少最大传感器更新频率。第二对Gazebo Classic可以设置export LIBGL_ALWAYS_SOFTWARE1强制GL走软件渲染路径这虽然听起来更慢但实际上在WSL2里避免了Mesa虚拟化驱动与Ogre的一些兼容性bug整体反而更稳定画面不会卡死。第三用无头模式。这是我最推荐的方式。Gazebo本身支持不加载GUI只跑服务端仿真gazebo --headless --verbose /path/to/world.world或者通过gz sim的server模式gz sim -s -v 4 /path/to/world.sdf无头模式下所有渲染工作都不做了CPU占用大幅下降仿真速度显著提升。需要看画面时再通过Gazebo GUI客户端连接上去或者用Rviz2订阅传感器话题即可。很多人在WSL2里折腾半天画面其实做好自动化测试完全不需要画面。4.3 如果你确实需要GUIWSLg与第三方X Server的取舍如果无头模式满足不了需求比如调试机器人模型时非得肉眼看姿态WSL2里也可以把GUI跑起来。WSLg在大多数情况下能用但注意如果你的Windows版本比较旧或者WSLg本身对Ogre窗口支持有问题可以退回用VcXsrv这种X Server方案export DISPLAY$(awk /nameserver / {print $2; exit} /etc/resolv.conf 2/dev/null):0.0然后引入WSL2的IP地址到Windows侧。具体配置网上很多但核心就一句话WSL2的网络是NAT方式Windows和WSL2互通DISPLAY要指向Windows侧监听端口同时要允许TCP客户端连接。这是很老的一套做法但对老版本WSL2非常管用。我的经验是能用WSLg就不用VcXsrvVcXsrv在跨窗口嵌套时反而容易字体渲染异常但遇到WSLg黑屏时VcXsrv是很好的兜底。5. 用安装前缀和环境函数管理多个Gazebo版本5.1 设计一套干净的切换脚本编译完成之后最核心的问题就是日常怎么切换版本。我不用alias的堆砌方法而是写了一个环境函数放到~/.bashrc里gazebo-env() { case $1 in classic) export GAZEBO_PREFIX/opt/gazebo-classic-11 ;; harmonic) export GAZEBO_PREFIX/opt/gazebo-harmonic ;; garden) export GAZEBO_PREFIX/opt/gazebo-garden ;; *) echo Usage: gazebo-env [classic|harmonic|garden] return 1 ;; esac export PATH$GAZEBO_PREFIX/bin:$PATH export LD_LIBRARY_PATH$GAZEBO_PREFIX/lib:$GAZEBO_PREFIX/lib/x86_64-linux-gnu:$LD_LIBRARY_PATH export CMAKE_PREFIX_PATH$GAZEBO_PREFIX:$CMAKE_PREFIX_PATH export PKG_CONFIG_PATH$GAZEBO_PREFIX/lib/pkgconfig:$PKG_CONFIG_PATH unset GAZEBO_MASTER_URI } gazebo-ls() { echo classic - /opt/gazebo-classic-11 echo harmonic - /opt/gazebo-harmonic echo garden - /opt/gazebo-garden }使用方式gazebo-env classic gazebo --version切换后每条新终端都要重新执行一次或者在需要同时跑多个版本时开不同终端分别source。这个设计思路是只切换环境变量不改系统文件所以永远不会出现装完新版把旧版搞坏的情况。5.2 让ROS插件对号入座多版本Gazebo能共存还要让对应的ROS插件链到正确版本。以ROS 1 Noetic为例gazebo_ros_pkgs在编译时要找到Gazebo Classic的头文件和库。如果你手动编译gazebo_ros_pkgs需要在configure前设置gazebo-env classic export GAZEBO_VERSION11这样CMake的find_package(gazebo)才会从/opt/gazebo-classic-11里找到正确的安装。而ROS 2 Humble里跑ros_gz时则执行gazebo-env harmonic然后让CMake找到gz-sim的库。有个容易踩的坑终端环境变量切换后如果还留着前一个版本的sdf相关环境变量会导致world文件解析错乱。所以我在函数里特意unset GAZEBO_MASTER_URI因为新旧版本对URI的处理方式不同。5.3 如何验证当前激活的正确性每次切换完不要急着跑先做三个快速检查which gazebo gazebo --version ldd $(which gazebo) | grep not foundwhich gazebo看路径是不是指向你选中的版本目录ldd排查有没有系统库缺失。如果ldd里有not found多半是LD_LIBRARY_PATH没设全比如gz-sim的库可能在/opt/gazebo-harmonic/lib下但部分依赖在/opt/gazebo-harmonic/lib/x86_64-linux-gnu下两个都要加进LD_LIBRARY_PATH。6. 编译与运行阶段的疑难杂症排查清单6.1 编译期高频报错与解决思路我列几个几乎每个人都会遇到的编译错误以及处理思路。Could NOT find sdformat。这个报错的出现原因是find_package在默认路径找不到sdformat。解决办法是设CMAKE_PREFIX_PATH指向你指定前缀目录并确认sdformat编装位置一致。比如安装到/opt/gazebo-harmonic就要让CMAKE_PREFIX_PATH包含/opt/gazebo-harmonic。protobuf版本冲突。gz-msgs和gz-transport在编译时会检查protobuf版本如果系统里同时有apt的libprotobuf-dev和手工编译的新版protobufCMake可能抓到旧版。我的做法是给每个前缀目录单独编一套protobuf然后在cmake命令里用-DProtobuf_DIR明确指定。这套路虽然繁琐但一劳永逸之后任何包问起来都不会乱。No package gz-transport found。这是缺少依赖工程导致的。gz-sim不像Classic那样一个仓库包含全部它的依赖是分布式的。所以必须按顺序编译还要在cmake时显式添加-Dgz-transport_DIR/opt/gazebo-harmonic/lib/cmake/gz-transport13之类的路径。建议在gazebo-env函数里把所有相关路径都加进CMAKE_PREFIX_PATH避免反复手动指定。6.2 运行期崩溃的三类现象第一种是Segmentation fault出现在gz sim启动时。这种情况最常见的原因是插件链接了不同版本的ignition-math。比如你用apt装过ignition-math6又手工编了ignition-math7运行时库路径优先级错了load到旧版瞬间崩。排查方法是用LD_DEBUGlibs gz sim -s启动看实际加载了哪个路径下的so文件。正常的话确认输出里全部指向/opt/gazebo-harmonic/lib。第二种是启动卡在Loading world不动。这种往往是模型资源路径没设对Gazebo找不到内置模型目录。需要设置export GAZEBO_MODEL_PATH/opt/gazebo-classic-11/share/gazebo-11/models或者对新版export GZ_SIM_RESOURCE_PATH/opt/gazebo-harmonic/share/gz-sim8/worlds第三类是[Err] [Server.cc] Unable to create server。这通常说明同一个GAZEBO_MASTER_URI已经被一个还在跑的实例占用。新旧版本混用时特别容易发生因为它们的默认master URI有时候是一样的。解决办法是给不同版本分配不同的端口export GAZEBO_MASTER_URIhttp://localhost:11346 # classic export GAZEBO_MASTER_URIhttp://localhost:11347 # harmonic6.3 关于性能把Gazebo当服务器来用而不是游戏最后说点我的体会。在WSL2里跑多版本Gazebo首先要端正心态WSL2不是原生Linux虚拟化的图形栈肯定拼不过物理机。我不再纠结画面流畅度而是把Gazebo主要当无头仿真服务器运行数据通过ROS话题输出再用Rviz2或者plotjuggler去看。这样一来实体渲染性能的影响降到最低仿真逻辑本身反而跑得飞快。另外一个实用技巧是给每个Gazebo版本配上独立的launch脚本模板脚本里先source对应的gazebo-env再启动无头模式并加载特定端口这样每次开始实验前只需执行一条命令即可。省心程度比一开始在系统里乱装高太多了。从依赖地狱里爬出来之后再回头看WSL2独立编译Gazebo这件事最值钱的不是编译成功这个结果而是你重新掌握了环境的控制权。以后想加一个gazebo 9测试环境无非是再走一遍流程换个前缀目录而已剩下的交给环境函数就行。希望这篇经验总结能帮你少走几个坑。