RustDesk 大概是目前唯一一个让普通开发者愿意花一下午去折腾源码编译的远程桌面项目——它是开源的,客户端核心是 Rust 写的,界面已经全面迁移到 Flutter,编译完的产物不仅能自己用,还能顺手改包名、改图标、接上自己的中继服务器。这篇文章就把我在 Ubuntu 上编译 RustDesk 桌面版和 Android 版的全过程拆开讲清楚,重点说清楚工具链之间怎么配合、哪些坑最容易让新手卡住一整天,以及怎么把常常超过一小时的编译时间压下来。如果你已经在用 RustDesk、想定制一个专属版本,或者单纯想研究远程桌面的实现方式,这篇可以当作一份可以直接上手操作的手册。
1. 动手前先搞清楚 RustDesk 的编译架构
1.1 Rust 核心加 Flutter 界面的分工
很多第一次编译 RustDesk 的人会习惯性把它当成一个“普通 Rust 项目”,上来就cargo build。这样确实能编出一个可执行文件,但大概率是旧版 Sciter 界面或者不完整的壳,而且会和现在主线版的编译流程对不上。
RustDesk 的实际结构是“Rust 核心 + Flutter 前端”的混合工程。核心的 NAT 穿透、音视频编码解码、输入控制、剪贴板同步这些逻辑,全部在 Rust 代码里,位于仓库根目录下的src/;而你在屏幕上看到的界面、窗口管理、托盘菜单、手机端的触控交互,则是由flutter/目录下的 Flutter 工程负责。两个部分通过一个叫rust_lib_rustdesk的 Flutter 插件桥接起来,这个插件在构建阶段会自动调用 cargo 把 Rust 代码编译成动态库,再打包进 Flutter 的产物里。
这个架构带来的直接影响是:编译桌面版和 Android 版的入口其实都是 Flutter 侧,而不是直接敲 cargo。你可以理解为整个构建流程分了两层,Flutter 是外层包装,Rust 是内层引擎。理解这一点,后面所有命令你就能对号入座了,不会出现“明明 cargo 成功了,为什么 APK 还是旧的”这种困惑。
1.2 编译工具链的全景图:三路并进
RustDesk 的构建不是一个工具链就能搞定的,至少需要三套环境协同工作:
第一套是 Rust 工具链,负责编译核心引擎,包括rustup、cargo,以及对应目标平台的交叉编译 target。第二套是 Flutter SDK,负责编译界面和打包整体应用,它需要配套的 Dart SDK 和平台构建工具,在桌面端是 GTK 依赖加 CMake/Ninja,在 Android 端是 Gradle。第三套是平台原生工具链,Ubuntu 桌面版需要一系列 GTK/X11 开发库,Android 版则需要 JDK、Android SDK 和 NDK。
这三套环境并不是装好就能直接配合,它们之间通过环境变量和配置文件发生关系。比如 Flutter 在构建 Android 时,需要在gradle.properties里知道 NDK 的路径,同时 Rust 的交叉编译器也需要通过ANDROID_NDK_HOME找到对应平台的链接器。任何一个环节没对上,构建就会在某个你完全没预料的地方崩掉。
1.3 版本选择:不要一上来就编最新 commit
RustDesk 主干分支的开发节奏很快,UI 层有时会引入新依赖,Rust 侧也在持续重构。我个人的习惯是编译之前先看一下官方 Release 的 tag,挑一个相对稳定的版本去编译,比如 1.2.x 或者 1.3.x 系列,避免直接编最新的 main 分支时被一个刚提交的 bug 卡住。
仓库克隆时还有一个容易忽略的点:这个工程带了子模块,必须用--recurse-submodules参数一次性拉完整,否则后面编译会报缺文件,排查起来很麻烦。如果已经用普通方式克隆了,也可以在仓库根目录执行一次git submodule update --init --recursive补上。
git clone --recurse-submodules https://github.com/rustdesk/rustdesk.git我建议你在动手前先理清自己要哪个版本、要哪条平台线,然后用不超过十分钟把工具链装好。不要边编边装,那种体验非常糟糕。
2. Ubuntu 版编译:从裸机到能跑起远程桌面
2.1 系统依赖安装与不同 Ubuntu 版本间的名字差异
Ubuntu 桌面版编译 RustDesk,第一步就是把系统依赖补齐。这一步用到的包比较多,而且不同 Ubuntu 版本之间,某些依赖的包名会不一样,这是新手最容易出错的地方。
sudo apt update && sudo apt upgrade -y sudo apt install -y git curl wget unzip zip \ ninja-build cmake pkg-config \ libgtk-3-dev \ libayatana-appindicator3-dev \ libxdo-dev \ libxcb-shape0-dev libxcb-xfixes0-dev \ libclang-dev逐个解释一下这些包为什么会出现:
libgtk-3-dev:Flutter Linux 桌面端的基础,窗口、按钮、输入框都靠它,这个几乎绕不开。libayatana-appindicator3-dev:系统托盘的实现依赖。RustDesk 在桌面端把窗口最小化时会收进系统托盘,没有这个库,编译可能能过,但运行起来托盘会消失。libxdo-dev:远程桌面最关键的一环——模拟键盘和鼠标输入。RustDesk 在 Linux 上通过 XTest 相关接口模拟远端输入,这个库就是提供这套接口的。libxcb-shape0-dev和libxcb-xfixes0-dev:窗口形状和区域刷新相关的 X11 库,Flutter Linux 插件层有时会直接引用。libclang-dev:Rust 项目里有些 crate 需要在编译时解析 C/C++ 头文件生成绑定,比如某些音视频库,没有 clang 库会报libclang: "couldn't find any valid shared libraries"这类错误。
我这里特别提醒一下libayatana-appindicator3-dev这个名字。在 Ubuntu 20.04 上这个包可能叫做libappindicator3-dev,到了 22.04 和 24.04 才全面改成libayatana-appindicator3-dev。如果你 apt 安装时报找不到包,先搜一下实际名字再装,不用死磕。
apt search appindicator2.2 Rust 和 Flutter 工具链的安装细节
Rust 工具链我建议直接用 rustup 安装,不要用 apt 装 rustc,因为 apt 里的版本通常偏旧,而 RustDesk 这种项目对 Rust 版本有最低要求,缺了某个新语法特性就编译不过。
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh source "$HOME/.cargo/env" rustup default stableFlutter SDK 也是同样思路,直接从 GitHub 拉稳定分支,不要用 apt 装。
git clone -b stable https://github.com/flutter/flutter.git "$HOME/flutter" export PATH="$PATH:$HOME/flutter/bin" flutter --version首次执行 flutter 命令时,SDK 会做一次自举,需要下载 Dart SDK 和各类组件,这个过程比较慢。如果网络不太好,可以先配置 Flutter 官方提供的国内镜像:
export PUB_HOSTED_URL=https://pub.flutter-io.cn export FLUTTER_STORAGE_BASE_URL=https://storage.flutter-io.cn配置完之后运行flutter doctor确认环境没问题。正常情况下它会列出 Linux 工具链的状态,只要没有红色叉号就可以继续。
2.3 克隆源码与两条构建路线
工具链就位后,回到 RustDesk 仓库目录,进入flutter/子目录,先执行flutter pub get把 Dart 依赖拉齐,然后就可以构建了。
cd rustdesk/flutter flutter pub get flutter build linux --release这条命令实际会做两件事:先通过插件机制调用 cargo 把 Rust 核心编译成动态库,再用 Flutter 的 Linux 桌面构建流程打包整个应用。整个过程第一次会比较漫长,因为 Rust 侧有很多第三方 C/C++ 依赖需要编译,比如音视频编解码相关库,耗时通常在二十分钟到一小时之间。
如果你只想快速验证 Rust 核心逻辑,或者在做某个 Rust 模块的修改调试,也可以直接回到仓库根目录执行cargo build --release,但这样编出来的是不带 Flutter UI 的早期版本,现在主线分支已经不太推荐这种用法了。我的建议是:调试界面和整体功能走 Flutter 路线,调试纯 Rust 逻辑才用 cargo 路线,两者不要混着来。
2.4 运行验证与产物位置
构建成功后,产物在flutter/build/linux/x64/release/bundle/目录下,里面是一个完整的应用程序包,包含可执行文件和相关资源。你可以直接运行:
./build/linux/x64/release/bundle/rustdesk如果想调试,不跑 release,而是用开发模式启动,还能享受 Flutter 的热重载:
flutter run -d linux运行起来之后,建议做几个最基本的验证:窗口能否正常打开,系统托盘图标能不能显示,能否通过 ID 连接到另一台设备。如果只是窗口起来了但连不上,那问题多半不在编译,而在网络和配置层面。顺手提一句,flutter/config/下有一些客户端设置文件,桌面端的配置和日志通常存在~/.local/share/rustdesk/和~/.config/rustdesk/,排查问题时可以去翻日志。
3. Android 版编译:交叉编译才是重头戏
3.1 JDK、Android SDK 和 NDK 的准备
Android 版比桌面版多了一道交叉编译的工序,而且对 JDK、SDK、NDK 的版本组合很敏感。先说结论:JDK 用 17,SDK 平台用编译仓库里要求的版本,NDK 强烈建议用 r23b,也就是版本号23.2.8568313。
sudo apt install -y openjdk-17-jdkAndroid SDK 建议用 Android Studio 的安装器装,或者自己下载 command-line tools 命令行安装,关键是确认ANDROID_HOME这个环境变量被正确设置。如果你已经装了 Android Studio,SDK 通常在~/Android/Sdk:
export ANDROID_HOME="$HOME/Android/Sdk" export ANDROID_NDK_HOME="$ANDROID_HOME/ndk/23.2.8568313"NDK 版本这里我特意强调,因为很多人会顺手装最新版 NDK,结果 Rust 的交叉编译链和最新版 LLVM 之间存在兼容性问题,链接阶段报各种奇怪的undefined reference。我自己实测下来 r23b 配合 RustDesk 主线的 Rust 版本是没问题的。如果你在构建时发现 ndk 目录下没有 23.2.8568313,就去 SDK Manager 里勾选安装。
$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager --list | grep "ndk;23" $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager --install "ndk;23.2.8568313"3.2 Rust 交叉编译 target 与 NDK 工具的配合
Android 设备的 CPU 架构并不统一,RustDesk 至少要支持 arm64-v8a 和 armeabi-v7a 两种主流架构,所以你得让 Rust 工具链具备交叉编译能力。先添加对应 target:
rustup target add aarch64-linux-android armv7-linux-androideabi x86_64-linux-android i686-linux-android这里顺带解释一下为什么要在 Rust 侧加 target。Rust 核心代码需要编译成运行在 Android 系统上的.so动态库,而你的编译宿主是 Ubuntu,两个平台的系统接口完全不一样,所以必须用 Android NDK 里的交叉编译工具链来链接。Flutter 的插件层通过cargokit在 Gradle 构建时自动完成这一步,它会读取ANDROID_NDK_HOME环境变量找到对应的 clang 编译器和链接器。
很多人在这一步卡住,是因为忘了设置环境变量,导致构建过程报unable to find utility "clang"这类错误。确认一下你的 environment:
echo $ANDROID_HOME echo $ANDROID_NDK_HOME如果输出为空,就在~/.bashrc里把这几个变量加上,再source ~/.bashrc。
3.3 flutter build apk 的完整构建流程
进入flutter/目录,和桌面版一样先拉依赖,然后执行 Android 构建:
cd rustdesk/flutter flutter pub get flutter build apk --release这条命令默认会打出包含所有支持 ABIs 的 fat APK,体积比较大。如果你只需要 arm64 的设备,可以用--split-per-abi参数按架构拆包:
flutter build apk --release --split-per-abi构建过程中,Gradle 会先解析插件依赖,然后调用 cargokit 编译 Rust 核心。这一阶段会占满 CPU,耗时比桌面版更长。在 8 核 16G 内存的机器上,单编 arm64 大概需要三十分钟到一小时,全 ABIs 可能需要两小时以上,请做好准备。
3.4 产物说明与安装验证
构建完成后,APK 在flutter/build/app/outputs/flutter-apk/目录下。用--split-per-abi的话,你会看到app-arm64-v8a-release.apk、app-armeabi-v7a-release.apk、app-x86_64-release.apk三个文件。从体积可以看出差别,arm64 的包通常比 armv7 大不少,这也是正常的。
安装到手机可以直接用 adb:
adb install build/app/outputs/flutter-apk/app-arm64-v8a-release.apk装好后打开应用,看到 ID 列表能正常刷新,说明网络穿透部分工作正常。如果打开即闪退,先查日志,Android 上的 logcat 会明确告诉你崩溃发生在哪个 so 库,百分之八十的情况还是 Rust 核心和 NDK 版本不匹配导致的。
4. 编译过程中最常见的坑与排查链路
4.1 依赖下载慢或失败:镜像配置与超时处理
RustDesk 的依赖来源横跨 crates.io、pub.dev、Gradle Plugin Portal 和服务器的多个 CDN。对网络状态不理想的用户来说,构建失败的第一大原因就是下载超时,而不是代码问题。crates.io 的下载慢可以通过配置镜像解决,在~/.cargo/config.toml里写入:
[source.crates-io] replace-with = "rsproxy" [source.rsproxy] registry = "https://rsproxy.cn/crates.io-index"同时设置环境变量让 rustup 也走加速:
export RUSTUP_DIST_SERVER=https://mirrors.tuna.tsinghua.edu.cn/rustup export RUSTUP_UPDATE_ROOT=https://mirrors.tuna.tsinghua.edu.cn/rustup/rustupFlutter 侧在之前已经配置过PUB_HOSTED_URL和FLUTTER_STORAGE_BASE_URL,Gradle 则要在flutter/android/build.gradle.kts(或对应 gradle 文件)里添加阿里云镜像,或者在gradle-wrapper.properties里把distributionUrl换成腾讯镜像。
4.2 内存不足与编译超时:ninja 被杀是典型信号
RustDesk 的音视频相关依赖编译时非常吃内存。我在编译时遇到过rustc进程被系统直接 kill,报错信息是signal: 9, SIGKILL,这种情况十有八九是 OOM。ninja 也会给出ninja: build stopped: subcommand failed这样的提示,但根本原因在系统内存不够。
解决办法是加 swap 空间,或者限制并行编译任务数。我的经验是 4G 内存的云主机至少要加 8G swap 才稳,16G 内存的本机不限制并行任务数也有可能被吃满。
# 限制 Rust 编译并行数 export CARGO_BUILD_JOBS=2同时修改flutter/android/gradle.properties,把 Gradle 的内存限制调大一点:
org.gradle.jvmargs=-Xmx4096m -XX:MaxMetaspaceSize=1024m4.3 NDK 版本和 Rust 工具链的链接错误
如果你使用的 NDK 版本过新,在链接阶段经常会遇到下面这类错误:
error: undefined reference to 'std::__1::mutex::lock()'这种问题跟你的代码没有关系,纯粹是 NDK 提供的 libc++ 版本和 Rust 使用的 Android target 之间的 ABI 不兼容。遇到这种情况,最直接的排查方法是切回官方文档推荐的 NDK 版本,也就是 r23b。如果你不想重装整套 SDK,可以直接下载 NDK 的 zip 包手动放到$ANDROID_HOME/ndk/目录下,然后重新指定ANDROID_NDK_HOME。
wget https://dl.google.com/android/repository/android-ndk-r23b-linux.zip unzip android-ndk-r23b-linux.zip -d "$ANDROID_HOME/ndk/"这里我再补一句经验:换 NDK 版本后,最好把 Rust 的构建缓存清理干净,否则旧的编译缓存可能还被继续引用:
cargo clean cd flutter && flutter clean4.4 GTK 头文件和系统库缺失的定位方法
桌面版编译时经常出现这种错误:
fatal error: gtk/gtk.h: No such file or directory这里的问题非常直接,就是系统缺了 GTK 开发头文件。按我之前给的 apt 命令把libgtk-3-dev装好就行。如果没有appindicator,会报找不到 appindicator/app-indicator.h。这种问题不要慌,先确认包里有没有:
dpkg -L libayatana-appindicator3-dev | grep "\.h$"然后看一下关键运行库是否齐全,用ldd查编译出的可执行文件即可:
ldd build/linux/x64/release/bundle/rustdesk | grep "not found"那个not found就是你缺的运行时依赖,按图索骥装回来就行。这里我把常见的几个症状整理成一张表,遇到问题时可以直接对号入座:
| 报错特征 | 可能原因 | 处理方式 |
|---|---|---|
gtk/gtk.h: No such file | 缺 GTK3 开发头文件 | 安装libgtk-3-dev |
app-indicator.h找不到 | 缺系统托盘开发库 | 安装libayatana-appindicator3-dev |
libclang相关报错 | 缺 C/C++ 绑定解析库 | 安装libclang-dev |
SIGKILL或 ninja 中断 | 内存不足 | 加 swap 或限制并行数 |
Android 链接期undefined reference | NDK 版本不兼容 | 换 NDK r23b |
| 下载超时或找不到依赖 | 网络源不通 | 配置国内镜像 |
5. 我的编译提速方案与后续定制方向
5.1 二次编译提速:sccache 是最大的功臣
RustDesk 最让人头疼的就是首次编译时间。但如果你要反复修改代码、重新编译,那么安装一个 sccache 是非常值得的。sccache 是一个编译缓存工具,可以把同一份编译器中间结果缓存下来,第二次编译能快很多。
cargo install sccache export RUSTC_WRAPPER=sccache把这个环境变量写入~/.bashrc,之后再编译 RustDesk 的时候,增量构建时间能从十几二十分钟缩短到几分钟。尤其对于 Android 这种多个架构挨个编译的场景,sccache 的收益非常明显,因为四个 ABI 编译的是同一份 Rust 源码,只不过目标平台不同,缓存命中率相当可观。
另外,不要轻易删除$HOME/.cargo和target目录,这些都是构建缓存。很多新手编译失败后第一反应就是删掉重来,反而把之前下载的依赖缓存也删了,陷入每次都是全量编译的恶性循环。
5.2 编译完成后还能做什么:定制与自建服务
编译成功只是开始。RustDesk 的开源属性让你可以在产物上做很多定制:
首先是改包名和图标。Android 的applicationId在flutter/android/app/build.gradle.kts里修改,图标在对应的mipmap目录替换。注意改了包名之后,自己是没法通过 OTA 覆盖安装官方版本的,需要先卸载旧版。
其次是接入自己的 ID 服务器和中继服务器。RustDesk 本身区分 ID 服务器(hbbs)和中继服务器(hbbr),这部分同样是开源的。客户端默认连接官方公共服务器,但你在编译时可以或运行前通过设置填入你的服务器地址。如果需要在公网部署自建服务器,可以额外克隆 rustdesk-server 仓库编译,那是在另一条构建链路上的事,和客户端编译是独立的。
最后,RustDesk 的flutter/目录里也有大量 UI 文案和主题配置,改起来比想象中容易。如果你只是想把主界面的语言、按钮文本或者默认配置改掉,不需要深入 Rust 代码,直接编辑 Dart 端的资源配置就能重新打包。
我自己实际编译时还有一个体会:如果既编 Linux 桌面版又编 Android 版,最好准备两个独立的源码副本,一个专门用于桌面版 cargo/flutter 编译,另一个专门用于 Android 交叉编译,因为两套构建的缓存目录混在一起时,偶尔会因为 NDK 和系统链接器的选择冲突出现难以排查的怪问题。分开后两个副本各用各的缓存,互不干扰,省去很多心智负担。编译这种东西,环境干净比什么都重要。