ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

WSL2下OpenClaw源码编译安装全流程与踩坑指南

WSL2下OpenClaw源码编译安装全流程与踩坑指南

最近我把一台 Windows 开发机上跑 OpenClaw 的流程整个重做了一遍,这次直接选择了在 WSL2 里从源码安装 2026 年最新主线版本,而不是下载现成的二进制包。结果不出意外地踩了一堆坑:编译脚本动不动就 OOM、文件系统慢到怀疑人生、端口转发偶尔抽风、eula 没同意连日志都刷不完。这篇文章就是我这次完整安装过程的全记录,包含每一步命令、判断依据、踩坑现场和最终能直接复用的解决方案。如果你也打算在 WSL2 下从源码构建 OpenClaw,尤其是想跟最新版本而不是稳定版,这篇文章能帮你少走至少两三个小时的弯路。

1. 为什么非要从源码装,不能直接拿现成包

先说结论:官方确实会发布预构建的 jar 产物,日常开服直接用那个几乎零成本。我之所以坚持从源码走一遍,是因为这次要跑的是 2026 年的主线开发版本,很多新特性、新协议处理和 mod 加载逻辑只存在于源码仓库的最新提交里。预构建包是打给大多数用户用的,往往滞后几个迭代。从源码构建,意味着我可以精确锁定某个 commit、按需裁剪模块、给社区提交修复 patch,这是黑盒 jar 给不了的。

1.1 源码安装到底解决了什么问题

从源码编译最直接的好处是可控。编译过程会把整个项目从 Java 源码变成可执行的 class 和最终 jar,所有依赖关系、版本冲突、资源打包规则都会在构建阶段暴露出来。像 OpenClaw 这种架构里涉及大量第三方库的项目,有些时候预构建包只能告诉你“不行”,而源码构建能告诉你“哪里不行、哪个依赖版本冲突、哪个模块没编进去”。

另一个好处是方便打补丁。我这次实际动过两处代码:一处是服务端默认的视距设置,另一处是日志输出的时间格式。改完重新编译只要几分钟,如果用二进制包就得靠外部脚本去改运行时参数,操作起来既绕又不稳定。对于想深入读读服务端内部实现的人,源码安装基本是必经之路。

1.2 为什么是 WSL2,而不是虚拟机或双系统

我手头这台开发机装的是 Windows,直接跑 Linux 服务端有两个老选择,一是装个完整虚拟机,二是搞双系统重启切换。这两条路我都试过,最后都弃了。完整虚拟机启动慢、磁盘占用大,还要单独配网络和共享目录;双系统就更折腾,开个服务端还得重启电脑,根本不现实。

WSL2 本质是一个轻量级虚拟机,但它跟 Windows 的文件系统、网络、端口转发做了深度集成,启动一个实例只需要几秒钟,CPU 指令集级别也是真正跑在虚拟化硬件上的。尤其适合编译这种吃 CPU、吃内存、吃 I/O 的任务。WSL2 下面是可以直接用 systemd 的(新版本默认启用),所以后面我用 systemd 托管服务端的时候,整个体验跟独立 Linux 服务器几乎没有区别。唯一要留神的是文件系统的跨盘访问问题,后面我会专门说。

1.3 2026 版对构建链路带来的变化

2026 年的主线版本有几个变化直接改变了构建方式。首先是整体构建从旧的 Maven 迁移到了 Gradle,build 脚本变成了build.gradle.kts,入口命令从mvn package变成了./gradlew build。其次是 JDK 版本下限抬高到了 Java 21,低于这个版本连 Gradle 插件都无法加载,编译会直接报 “Unsupported class file major version”。再有就是新增了一些需要额外工具生成的资源文件,比如实体模型定义和语言文件,构建脚本里写死了要调用特定版本的资源生成器。

这三个变化意味着:以前那套“随便装个 JDK,拉下代码就 mvn package”的老方法已经行不通了。这也是为什么我主张先从官方文档和仓库的 README 确认构建要求,再动手装环境。2026 年这个版本对构建链的“型号匹配”要求明显更严格,越是新版本越不能凭经验瞎猜。

2. 先把 WSL2 环境调到能直接开工的状态

环境准备这一步看起来简单,反而是我这次踩坑最多的地方。很多问题表面上是编译失败,根子其实在 WSL2 版本太旧、发行版没有初始化完、或者项目目录放错了位置。这里我按顺序拆开讲。

2.1 确认 WSL2 而不是旧版 WSL1

执行任何操作前,先确认你装的到底是 WSL2 还是 WSL1。两者文件系统模型完全不同,WSL1 没有真正的 Linux 内核,很多系统调用是翻译执行的,跑 Java 编译这种负载会慢得离谱。在 Windows 的终端里执行:

wsl -l -v

输出里应该能看到类似这样的表格,VERSION 列必须是 2。如果显示的是 1,就用下面的命令转换:

wsl --set-version <发行版名称> 2

另外强烈建议先把 WSL 内核更新到最新:

wsl --update

这个命令更新的是 WSL 本身的虚拟化组件,不是某个具体发行版。我遇到过不止一次“明明操作都对但文件系统行为诡异”的情况,最后都靠这条命令解决。WSL2 的版本太旧时,9P 文件协议、端口转发和 systemd 支持都可能不在最佳状态,尤其 2026 年的最新主线已经用上了新内核特性,老内核会有各种稀奇古怪的兼容问题。

2.2 发行版初始化与工具链安装

我这次选用的是 Debian 系发行版。安装命令很简单,在 Windows 终端里:

wsl --install -d Ubuntu

首次进入会要求创建 Linux 用户和密码,这个用户不要设成 root,后面开发工作都基于这个普通用户来做。进入系统后先把包索引刷新,再做一次全量升级:

sudo apt update && sudo apt upgrade -y

这一步容易被人跳过,但强烈建议做。因为 WSL 发行版镜像默认的软件源索引可能是几天前甚至几周前的,直接装依赖容易装到旧版本,后面跟 Gradle 的依赖约束打架。

一次性把基础工具链装齐:

sudo apt install -y git wget unzip zip curl openjdk-21-jdk

其中 unzip 和 zip 很容易被忽略。OpenClaw 的构建过程需要解压一些资源包,生成发行版压缩包时会调用 zip 命令,缺了会在打包阶段报 “Cannot find zip” 之类的错误。提前装好,省得中途返工。

2.3 工作目录放到哪:一个影响编译速度的关键决定

这是我在这次安装里认为最重要的经验,没有之一:不要把源码放在 /mnt/c 开头的 Windows 挂载目录里编译。WSL2 访问 Windows 盘符靠的是 9P 协议,每一次文件读写都要跨过虚拟化边界,速度跟 WSL2 自己的 ext4 文件系统差一个数量级。编译 OpenClaw 涉及成千上万个小文件的读写,如果源码树在/mnt/c/...,构建时间会从几分钟变成几十分钟,而且容易出现 “Text file busy”“Stale file handle” 这类诡异报错。

正确做法是把开发目录放在 WSL 的 Linux 文件系统里,比如:

mkdir -p ~/build cd ~/build

后面所有源码拉取、编译、日志生成都放在这个 Linux 侧目录里。如果你需要把产物拿回 Windows 侧使用,编译完成后再用cp或explorer.exe .拷贝回 Windows 目录即可。这个切换,实测可以让构建总耗时缩短一半以上,非常值得。

3. 拉源码之前,先解决 JDK、Gradle 和分支问题

环境跑通后,接下来就是选 JDK、确认构建工具、拉取源码。每一步我都吃了点教训,这里写清楚判断依据。

3.1 JDK 版本怎么选,别被“最新”带偏

OpenClaw 2026 主线构建要求 Java 21 及以上。我一开始图省事装的是系统仓库默认的 OpenJDK 17,结果 Gradle 初始化时就报Unsupported class file major version 65,意思是对方 class 版本是 Java 21 编出来的,而当前 JDK 最高只能认到 Java 17。这不是代码问题,纯粹是工具链版本不匹配。

建议直接装 OpenJDK 21,这是长期支持版本,稳定性足够。装完后验证:

java -version

输出中版本号必须是 21 开头。另外记得确认JAVA_HOME环境变量干净。有些 WSL 发行版里装了多个 Java,java命令指向的可能不是最新版。用下面的命令快速检查:

update-alternatives --config java

确保当前选中的就是 JDK 21。如果后续启动服务端时发现内存参数不生效,多半也是JAVA_HOME指向了奇怪的位置,这一步排查要放在最前面做。

3.2 Gradle Wrapper 和手工安装的取舍

新版 OpenClaw 默认带gradlew脚本,这是 Gradle 官方推荐的 wrapper 方式。好处是构建版本被锁死在项目里,不会受系统全局 Gradle 版本影响。所以我并没有手工安装全局 Gradle,直接用项目自带的./gradlew。

第一次执行./gradlew --version时,wrapper 会自动下载项目锁定的 Gradle 发行版到~/.gradle/wrapper目录里。这个下载过程需要一点时间,下载完再执行构建就不会有网络依赖了。

这里我想特别提醒一个容易被忽略的点:不要用sudo运行 gradlew。Gradle 的 daemon 进程和缓存目录如果归属 root,后续普通用户再跑构建会出现 “Failed to create parent directory” 之类的权限报错。如果已经误用 root 跑过一次,最简单的方法是删掉~/.gradle然后重新构建,或者干脆sudo chown -R 当前用户名 ~/.gradle。我在这个坑上浪费了整整十分钟。

3.3 源码拉取与版本分支切换

拉源码的路径很简单:

cd ~/build git clone <OpenClaw官方仓库地址> OpenClaw cd OpenClaw

克隆完成后先看一眼分支和标签:

git branch -a git tag

2026 主线的默认分支名字一般是main或dev,具体以仓库为准。如果要跟最新的 2026 版本,切到对应主线分支;如果想复现某个官方稳定版本,直接切到 tag:

git checkout v2026.1.0

我个人建议:第一次构建,切到最新的发布 tag 而不是开发分支。开发分支可能刚好遇到某个刚合入的 breaking change,导致构建失败时你完全不知道是不是自己的环境问题。先跑通一个 tag,确认环境没问题,再切回开发分支去尝鲜,这是一种更低风险的上手顺序。如果源码下载因为网络波动中断了,git clone可以重试,或者用仓库提供的 zip 快照手动下载后解压,效果一样。

4. 编译构建:命令、耗时和产物

这是整篇文章的主角部分。环境完备以后,真正执行构建的时间其实不长,但第一次跑的时候会有一个较长的依赖下载和初始化过程,需要一点耐心。

4.1 构建命令逐个拆解

进入源码目录后,最常用的构建命令是这几个:

./gradlew --version

先跑这个,目的是测试 wrapper 是否正常、Java 版本是否被 Gradle 接受。如果这一步就报错,后面就不用继续了,问题一定出在 JDK 配置上。

--version成功后,执行完整构建:

./gradlew build -x test

这里的build会执行编译、资源处理、jar 打包、代码检查等一系列任务;-x test表示跳过单元测试。我建议第一次构建先跳过测试,因为 OpenClaw 的测试套件比较重,有些测试需要加载完整的虚拟资源,全量跑一遍可能要额外花二十分钟以上,而我们的目的是先把产物跑起来。等确认环境没问题后,再回头执行:

./gradlew check

把测试补上,这是最节省时间的顺序。如果之后你想生成可直接启动的目录结构,可以用:

./gradlew installDist

它会生成一个包含启动脚本和依赖库的目录,类似 Linux 软件安装后的 bin + lib 布局,比 fat jar 更适合日常维护。

4.2 首次构建的阶段判断

第一次构建输出会经历三个阶段:

第一阶段是 Gradle 自身的初始化和依赖下载,输出内容大多是Downloading ...。如果网络状况不太理想,这里容易显得像“卡住”,其实是在拉依赖,耐心等就好。

第二阶段是代码编译,控制台会以模块为单位输出Task :claw-server:compileJava之类的信息。这部分 CPU 会明显占用,风扇声变大是正常现象。

第三阶段是打包与资源处理,出现jar、processResources、distZip等任务名时,说明已经接近完成了。整个第一次构建在配置合理的机器上大约需要 5 到 15 分钟,具体取决于 CPU 性能和依赖下载速度。如果构建在中途抛出异常,先看错误信息里的 “What went wrong” 小节,那一行往往直接说明是网络、内存还是代码问题。

4.3 构建产物到底长什么样

构建结束后,产物通常集中在两个地方:

  • 单个 fat jar:路径类似claw-server/build/libs/claw-server-<版本>-all.jar,这个 jar 可以直接用java -jar运行。
  • 发行版压缩包:在build/distributions/下面,有 tar 和 zip 两种格式。解压后会看到bin/和lib/两个目录,启动脚本就在bin/里。

我实际用的是installDist生成的目录,因为里面自带claw-server启动脚本,脚本里预配置了 classpath,不用自己写一长串依赖列表。后面做 systemd 托管时,直接指向这个启动脚本即可。

5. 踩坑实录:七个高频问题与解决方案

这部分是整篇实录价值最高的地方。我按“现象、原因、解法”的格式把这次遇到的问题整理出来了,后面再有人搭环境,对着表排查就能少走弯路。

5.1 问题一览表

问题现象根本原因快速解法
Gradle 启动报 class version 错误JDK 版本低于 21安装并切换 OpenJDK 21
构建中途 OOM 被杀Gradle Daemon 堆内存太小在 gradle.properties 加大 -Xmx
源码在 /mnt/c 下编译极慢9P 文件协议性能差源码移到 Linux 侧目录
“Text file busy” 或 “Stale file handle”Windows 挂载目录下文件锁问题避免在 /mnt/c 下构建
错误提示找不到 zip/unzip打包环节缺少系统工具apt 安装 unzip zip
启动时报 locale / ICU 初始化错误Linux 区域设置不完整生成 en_US.UTF-8 并设置 LANG
Windows 访问 localhost 失败WSL2 端口转发偶发失效查看 WSL IP,改用 IP 访问

5.2 内存不足:Gradle Daemon 和 WSL2 的默认限制

我在第一次构建时遇到最典型的中断原因是 OOM,日志末尾出现:

Execution failed for task ':claw-server:compileJava'. > GC overhead limit exceeded

原因有两个层面。第一层,Gradle Daemon 默认最大堆内存较小,编译较大的模块时不够用。第二层,WSL2 默认只会分配物理内存的一半给虚拟机,如果宿主机只有 8G 内存,WSL2 里可能只剩 4G,再算上 Gradle 和编译子进程,内存确实紧张。

解决方法是双管齐下。先在项目根目录的gradle.properties里给 Gradle 加内存:

org.gradle.jvmargs=-Xmx3G -XX:MaxMetaspaceSize=1G

然后调整 WSL2 的内存配额。在 Windows 用户目录下创建.wslconfig文件(如果不存在),写入:

[wsl2] memory=6GB swap=4GB

保存后,在 Windows 终端执行wsl --shutdown,再重新进入 WSL2 让配置生效。注意:这个配置只影响 WSL2,不影响 Windows 本体。我实际把这个值设成 6G 后,构建再也没有出现过 GC overhead 的问题。

5.3 /mnt/c 挂载目录带来的文件系统怪症

我把源码放在 Windows 桌面上进行过一次构建,结果遇到了两个非常诡异的报错:一个是Text file busy,发生在执行 gradlew 时;另一个是Stale file handle,发生在 Gradle 读取缓存文件时。这两类错误在纯 Linux 文件系统下几乎不会出现,根源都在于 9P 文件协议对文件锁和 inode 的处理跟原生 Linux 不是一回事。

遇到这类问题不要试图去换文件系统挂载选项或者加同步参数,最干脆的方案就是把整个工作目录迁回 Linux 侧。我是这样处理的:

mkdir -p ~/build cp -r /mnt/c/Users/用户名/Desktop/OpenClaw ~/build/ cd ~/build/OpenClaw

把旧目录下的产物和.gradle缓存一并删除后重新构建,所有怪问题全部消失。这个经验我后来基本固定为流程:源码开发一律在 WSL 侧目录,Windows 侧只保留最终产物或备份压缩包。

5.4 服务端端口被占与 Windows 侧访问失败

OpenClaw 服务端默认监听 25565 端口。启动后第一次从 Windows 的客户端连接,我遇到两种现象。第一是端口被其他进程占用,服务端启动日志直接报Address already in use。在 WSL 里用:

ss -tlnp | grep 25565

能立刻看到是哪个进程占用的。如果是 WSL 内部的旧服务端残留,杀掉即可;如果是 Windows 侧进程占了端口,那就换个端口启动,比如--port 25566。

第二类现象更隐蔽:服务端在 WSL 里正常监听,但 Windows 客户端访问localhost:25565就是不通。WSL2 的 localhost 转发大多数时候是自动的,但偶尔会失效,尤其是重启过网络或休眠唤醒后。解决办法是直接查 WSL2 的 IP,然后用 IP 连接。在 WSL 里执行:

ip addr show eth0

输出里的 inet 地址就是 WSL2 的 IP,Windows 客户端直接连接<这个IP>:25565即可,往往立刻就能通。这个问题不算大坑,但非常让人困惑,因为它不影响 WSL 内部访问,只影响跨系统访问。

5.5 Linux 侧构建为什么能快一倍以上

我实际做过一次对比:同样的源码,放在/mnt/c下构建需要 11 分 40 秒;放在~/build下构建只需要 5 分 20 秒。差距非常可观,原因在于 9P 协议每次文件操作都会把请求从虚拟化层送到 Windows 宿主,而编译过程要创建和读写成千上万个中间 class 文件,原本是纯内存和磁盘操作,硬生生变成跨系统通信。一个直观类比:你在本地编辑文件是“在自家书桌上写字”,跨盘操作等于每次写字前先跑到隔壁房间拿纸再跑回来写,来回次数多了自然就慢。

所以这个选择不是“性能优化技巧”,而是基本操作纪律。如果发现构建突然变慢,先检查当前路径:

pwd

只要路径里出现/mnt/,赶紧迁目录吧。

5.6 locale 相关初始化错误

启动服务端时我曾经遇到过这样一段输出:

java.lang.ExceptionInInitializerError Caused by: java.util.MissingResourceException: Could not load ICU ...

一开始以为是 Java 包缺失,后来发现是 WSL 发行版里根本没有生成任何 locale。Java 在读取某些本地化资源时会依赖系统的 locale 数据,缺失就会初始化失败。解决办法是安装语言环境并设置环境变量:

sudo locale-gen en_US.UTF-8 sudo update-locale

然后在启动脚本或执行命令前设置:

export LANG=en_US.UTF-8 export LC_ALL=en_US.UTF-8

设置完成后重新启动服务端,问题不再出现。这个问题在普通 Linux 服务器上少见,因为安装系统时一般会生成 locale;而 WSL 的极简发行版经常省略这一步,值得留意。

5.7 主线版本翻车时的快速回退策略

我尝试过跟踪 2026 开发主线时,刚好碰到一次代码合并破坏了解析器模块,构建直接失败。这时候硬修源码不现实,最理智的做法是切回上一个稳定 tag 继续工作。操作很简单:

git stash git checkout v2026.1.0 ./gradlew clean build -x test

这个操作不会破坏已有的代码修改,git stash会把改动暂存起来,之后可以随时切回开发分支再取回来。这种“先跑稳定版,再尝试新版本”的顺序,能最大限度避免因上游一时的问题卡住自己的开发进度。开发者分支本来就不是给所有人保证可用的,遇到构建失败先怀疑代码合并问题,再怀疑自己环境,这个排查顺序能省很多时间。

6. 第一次启动与验证

构建成功后,最后一步就是把服务端真正跑起来,验证客户端可以正常连接。这块我也遇上了几个不大不小的问题,一起说清楚。

6.1 启动命令与 eula 处理

我使用 installDist 方式,启动命令非常简单:

cd ~/build/OpenClaw ./claw-server/bin/claw-server --nogui

第一次启动时,服务端会在工作目录生成eula.txt,如果不同意协议,服务端会直接退出并在日志里提示 “You need to agree to the EULA”。处理方式是编辑这个文件,把eula=false改成eula=true,然后重新启动。这一步是硬性要求,没有任何绕过办法,也不需要绕过。

启动成功的标志,是日志中出现一行:

[main] INFO - Done (3.214s)!

看到Done就说明服务端已经完成加载,开始监听端口了。此时按Ctrl+C可以正常停止。如果你希望后台运行,可以配合 nohup 或 systemd,我更推荐后者。

6.2 从 Windows 客户端连接

服务端起来后,打开客户端,选择多人游戏,直接添加服务器localhost:25565,正常情况下就能看到服务器状态,连接进去。如果连接不上,按顺序排查:

# 1. 服务端是否在监听 ss -tlnp | grep 25565 # 2. Windows 侧能否访问 localhost # 在 Windows 终端执行 Test-NetConnection -ComputerName localhost -Port 25565

如果 localhost 不通但服务端确实在监听,就改用 WSL2 的 IP 连接,方法前面已经说过。另外一个经验是:WSL2 的 localhost 转发偶尔会因为 Windows 防火墙配置弹窗而失效,第一次连接时看到防火墙提示,允许专用网络访问即可,不要取消。

6.3 用 systemd 托管服务端:日志更好管

每次手动启动服务端不太符合长期运行的需求。WSL2 新版本默认启用 systemd,所以我可以把服务端注册成系统服务。在 WSL 里创建服务文件:

sudo tee /etc/systemd/system/claw.service > /dev/null <<'EOF' [Unit] Description=OpenClaw Server After=network.target [Service] WorkingDirectory=/home/与你自己的用户名/build/OpenClaw ExecStart=/home/与你自己的用户名/build/OpenClaw/claw-server/bin/claw-server --nogui Restart=on-failure RestartSec=10 [Install] WantedBy=multi-user.target EOF

然后:

sudo systemctl daemon-reload sudo systemctl enable claw.service sudo systemctl start claw.service

日志查看非常方便:

journalctl -u claw.service -f

之后源码更新,只需要重新构建,然后sudo systemctl restart claw,整个流程就变成了一套标准化的运维流程。我自己实际上已经把 WSL 当成了一个常驻的沙盒服务端环境,重启电脑后也能通过wsl -d <发行版> -u root systemctl start claw快速恢复服务。

最后再分享一个我在这次安装过程中养成的习惯:每完成一个阶段,就把当时的命令和输出截到本地笔记里,尤其是gradlew --version的结果和第一次构建成功时的任务列表。后面遇到诡异问题时,这些记录能帮你快速判断是环境变了还是代码变了。另外~/.gradle这个缓存目录非常宝贵,重装 WSL 前记得把它备份出来,还原后能省掉几乎所有依赖下载时间。源码安装这条路,第一次走完觉得繁琐,但有了这套流程之后,我再也不担心服务端出问题只能等别人发新版了。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进