wgpu 在 Android 与 iOS 上运行测试:交叉编译配置与 adb/ssh 设备端执行完整指南
2026/9/13 22:55:15 网站建设 项目流程

wgpu 在 Android 与 iOS 上运行测试:交叉编译配置与 adb/ssh 设备端执行完整指南

【免费下载链接】wgpuA cross-platform, safe, pure-Rust graphics API.项目地址: https://gitcode.com/GitHub_Trending/wg/wgpu

wgpu 作为跨平台 Rust 图形 API,其 GPU 集成测试(tests/tests/wgpu-gpu)默认在桌面平台上运行,但要验证 Vulkan(Android)与 Metal(iOS)后端在真机上的行为,需要将测试二进制交叉编译后部署到设备上执行。本文基于仓库内的 docs/running-tests-on-android-and-ios.md 完整展开:如何在宿主机构建aarch64-linux-androidaarch64-apple-ios测试二进制,如何通过adb/ssh将其传输到设备并以正确环境执行,包括完整的.cargo/config.toml交叉链接配置、PowerShell runner 脚本与 iOS 越狱设备的 entitlements 文件。读完后你可以独立搭建一套真机测试流程,并用 docs/testing.md 中描述的测试体系(尤其 GPU 测试)在移动端进行回归验证。

整体思路:宿主交叉编译 + 设备端执行

文档给出的核心方法是三段式:

  1. 在宿主上通过 Cargo 的 per-targetrunner机制交叉编译出设备端可执行文件;
  2. runner 脚本(PowerShell 编写)负责用adb pushscp把二进制推送到设备的可执行目录;
  3. 通过adb shellssh在设备上以正确的LD_LIBRARY_PATH或 entitlements 环境运行,并把退出码传回,使cargo test/nextest 能正常判定成败。

Cargo 的runner配置是整套方案的关键:一旦在.cargo/config.toml中为特定 target 指定runner,该 target 下所有通过cargo test运行的测试二进制都会在构建完成后自动交给这个脚本处理,无需改动任何测试代码。

需要先说明适用前提:仓库的 CI(.github/workflows/ci.yml)中,Android aarch64 与 iOS aarch64 矩阵项(见 ci.yml L111-L116 与 L153-L158)执行的是Check native分支,即用 clippy 编译全部目标与测试(--tests --benches --all-features),保证测试代码能针对这两个 target 通过编译;而在设备上真正运行这些测试正是本文档要解决的、CI 不覆盖的环节。仓库工具链由 rust-toolchain.toml 指定为 Rust 1.93,交叉编译前可用rustup target add aarch64-linux-android aarch64-apple-ios添加对应目标。

Android:NDK 交叉链接配置

.cargo/config.toml 完整配置

原文档给出的 Android 配置如下(<...>为需替换的占位符,<version>是 API 级别,<host>是 NDK 预构建工具链的主机目录名,如linux-x86_64):

[target.aarch64-linux-android] # Runner script is written in powershell runner = ["pwsh", "-File", "<runner location>run-on-android.ps1"] rustflags = [ "-C", "linker=clang", "-C", "link-arg=-fuse-ld=lld", "-C", "link-arg=--target=aarch64-linux-android", "-C", "link-arg=--sysroot=<ndk/sysroot location>", "-C", "link-arg=-B<ndk/sysroot location>/usr/lib/aarch64-linux-android/<version>", "-C", "link-arg=-L<ndk/sysroot location>/usr/lib/aarch64-linux-android/<version>", "-C", "link-arg=-L<ndk/sysroot location>/usr/lib/aarch64-linux-android", "-C", "link-arg=-L<ndk location>/toolchains/llvm/prebuilt/<host>/lib/clang/20/lib/linux/aarch64" ]

逐行解释这些 rustflags 的作用:

  • linker=clang+--target=aarch64-linux-android:显式用 clang 作为交叉链接器并指定 Android 目标三元组,绕开 rust-lld 对 Android Bionic sysroot 的适配差异;
  • -fuse-ld=lld:链接阶段使用 LLD,与 NDK 工具链保持一致;
  • --sysroot:指向 NDK 的sysroot目录,提供 Bionic 头文件与库;
  • 三个-B/-L项分别把按 API 级别划分的 Bionic 库目录(usr/lib/aarch64-linux-android/<version>)和通用库目录加入链接搜索路径;
  • 最后一个-L指向 NDK 内置 clang 的资源目录中的lib/linux/aarch64,用于让编译器找到libc++_shared.so等 C++ 运行时——这也解释了文档后文"所有 Android 二进制都链接libc++_shared.so,记得把它放在二进制旁边"的说明。

对照仓库 CI 的做法可以印证这些要点:ci.yml L277-L284 在 Android 任务中把$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin加入PATH(让cccrate 自动探测到 NDK 的clang++),并显式设置AR_aarch64_linux_android=llvm-ar,因为 Android SDK 的归档工具名不符合常规;同时 ci.yml L38 全局设置了PKG_CONFIG_ALLOW_CROSS: 1,注释明确写着 "allow android to work",因为若干依赖 crate 在构建期需要pkg-config而交叉环境下默认会被拒绝。在本地复现 Android 构建时,同样建议设置该环境变量并保证 NDK 的bin目录在PATH中。

run-on-android.ps1 runner 脚本

完整脚本(来自原文档,<runner location>替换为你的脚本存放路径):

param( [string]$BinaryPath ) $ErrorActionPreference = "Stop" # /data/local/tmp is the most common directory where arbitrary binaries can be run $RemoteDir = "/data/local/tmp/runner" $BinaryName = Split-Path -Leaf $BinaryPath $RemotePath = "$RemoteDir/$BinaryName" adb push --sync "$BinaryPath" "$RemotePath" *>$null adb shell "chmod 755 $RemotePath" $EscapedArgs = $args | ForEach-Object { if ($_ -match " ") { "`"$_`"" } else { $_ } } $LinuxArgs = $EscapedArgs -join " " # Set LD_LIBRARY_PATH to the location of libc++_shared.so $DeviceCmd = "cd $RemoteDir && LD_LIBRARY_PATH=$RemoteDir ./$BinaryName $LinuxArgs" adb shell "$DeviceCmd" exit $LASTEXITCODE

脚本要点:

  • 目标目录/data/local/tmp是 Android 上少数允许任意二进制执行的目录,脚本将其下的runner子目录作为远程工作区;
  • adb push --sync推入测试二进制,chmod 755赋予执行权限;
  • 参数转义逻辑把含空格的参数加引号后拼接,保证cargo test传入的测试过滤参数(如 test name 参数)能原样到达设备端;
  • 运行时通过LD_LIBRARY_PATH=$RemoteDir找到与二进制同目录的libc++_shared.so——这就是"把 libc++_shared.so 副本放在二进制旁边"的具体落地方式:用adb push将该 so 一并推到$RemoteDir
  • 结尾exit $LASTEXITCODE把设备端测试退出码交还给 cargo,使测试框架能正确判定通过/失败。

设备端运行的是哪些后端

从 wgpu-hal/Cargo.toml 的 target 条件依赖看,Android 上可用的后端与依赖结构是明确的:vulkanfeature 中包含android_system_properties(L91-L95),glesfeature 依赖ndk-sys(L114-L116),且cfg(target_os = "android")下额外声明了android_system_propertiesndk-sys依赖(L312-L314)。因此在 Android 上运行 wgpu-gpu 测试时,Vulkan(主流路径)与 GLES 后端都可能被#[apply(gpu_test!)]harness 枚举到,这正是真机执行对覆盖移动端驱动问题不可替代的原因。

iOS:越狱设备 + sysroot 交叉编译

文档开头就给出两个关键前提:

  • iOS 通常不提供 shell 访问与sshd,因此设备必须越狱
  • 交叉编译只需要一份 iOS sysroot 拷贝,所以Linux 和 Windows 主机同样可以尝试(macOS 之外需额外设置SDKROOT)。

.cargo/config.toml 完整配置

[target.aarch64-apple-ios] # Runner script is written in powershell runner = ["pwsh", "-File", "<runner location>run-on-ios.ps1"] rustflags = [ "-C", "linker=clang", "-C", "link-arg=-fuse-ld=lld", "-C", "link-arg=--target=aarch64-apple-ios", "-C", "link-arg=--sysroot=<sysroot location>", "-C", "link-arg=-miphoneos-version-min=<minversion>", "-C", "link-arg=-rpath", "-C", "link-arg=@executable_path/Frameworks", "-Lnative=<sysroot location>/usr/lib", "-Lframework=<sysroot location>/System/Library/Frameworks", ]

与 Android 配置的关键差异在于 Apple 链接语义:

  • -miphoneos-version-min=<minversion>声明最低部署 iOS 版本,clang 会据此选择可用的符号;
  • -rpath @executable_path/Frameworks是 Mach-O 的动态库搜索路径写法,等价于 ELF 的LD_LIBRARY_PATH机制;
  • -Lnative-Lframework分别对应 Apple 平台的普通库搜索路径与 Framework 搜索路径(如Metal.frameworkIOSurface.framework)。

从 wgpu-hal/Cargo.toml 的结构可以印证 iOS 构建的依赖形态:metalfeature(L72-L90)依赖objc2-metalobjc2-core-foundation等 Objective-C 绑定 crate,且仅在 Apple 平台激活;cfg(target_vendor = "apple")下还声明了这些 objc2 系依赖(L294-L306)。这意味着 iOS 交叉编译时 C 语言代码与 objc 绑定的编译都由 sysroot + clang 承担,与本文 rustflags 的设定完全对应。

macOS 之外的主机(Linux/Windows)必须设置SDKROOT环境变量指向 sysroot 位置。文档解释了原因:构建期依赖(如cc-rs)在非 macOS 环境下无法使用xcrun探测 SDK,设置SDKROOT后它们会直接使用该路径,从而避免构建失败。

run-on-ios.ps1 runner 脚本

param( [string]$BinaryPath ) $SshTarget = "<target user>@<target host>" $BinaryName = Split-Path -Leaf $BinaryPath # Select one of the following paths depending on rootless or rootful jailbreak $RemoteDir = "/var/jb/var/mobile/runner" #$RemoteDir = "/var/root/runner" $RemotePath = "$RemoteDir/$BinaryName" $EscapedArgs = $args | ForEach-Object { if ($_ -match " ") { "`"$_`"" } else { $_ } } $LinuxArgs = $EscapedArgs -join " " $DeviceCmd = "./$BinaryName $LinuxArgs" ssh $SshTarget "test -f $RemotePath" if ($LASTEXITCODE -ne 0) { scp -q "$BinaryPath" "$SshTarget`:$RemotePath" $DeviceCmd = "chmod 755 $BinaryName && ldid -Sent.xml $BinaryName && $DeviceCmd" } ssh $SshTarget "cd $RemoteDir && $DeviceCmd" exit $LASTEXITCODE

脚本逻辑:

  • 先通过ssh <target> "test -f $RemotePath"探测远程是否已存在同名二进制;
  • 不存在时才执行scp上传,并用ldid -Sent.xml注入 entitlements(ent.xml必须放在 runner 目录中);若已存在则跳过传输与签名,直接运行——这使同一测试二进制的重复执行(cargo test 的常规节奏)不必反复传文件;
  • $RemoteDir二选一是由越狱类型决定的:rootless越狱使用/var/jb/var/mobile/runnerrootful越狱使用/var/root/runner,脚本中已给出两个变体供切换;
  • 同样以exit $LASTEXITCODE结束,向 cargo 回传设备端测试结果。

iOS 为何必须注入 entitlements:ent.xml

由于 iOS 的安全机制,一个要在**无容器(no container)**环境下运行并访问 IOKit 的用户客户端类(GPU 驱动接口正属于此类)的二进制,必须携带特定 entitlements。这就是脚本要求设备上安装ldid工具并用ent.xml签名的原因。完整ent.xml(需与二进制同目录放置,由ldid -Sent.xml读取):

<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>platform-application</key> <true/> <key>com.apple.private.security.container-required</key> <false/> <key>com.apple.security.iokit-user-client-class</key> <array> <string>AGXCommandQueue</string> <string>AGXDevice</string> <string>AGXDeviceUserClient</string> <string>AGXSharedUserClient</string> <string>IOSurfaceRootUserClient</string> </array> </dict> </plist>

三个键分别对应文档描述的两个目的:

  • platform-application = true:声明自己是平台级应用,是获取私有 entitlements 的前提;
  • com.apple.private.security.container-required = false:允许进程在沙盒容器之外运行("run without a container");
  • com.apple.security.iokit-user-client-class列表:授权访问 IOKit 的指定用户客户端类。其中AGXCommandQueueAGXDeviceAGXDeviceUserClientAGXSharedUserClient正是 Apple GPU(AGX 驱动)的 Metal 驱动接口,IOSurfaceRootUserClient则是 GPU 缓冲共享(IOSurface)所必需的类——这与 wgpu 在 iOS 上走 Metal 后端(objc2-metal绑定,见上文 wgpu-hal/Cargo.toml L72-L90)的运行时需求完全吻合。

实操要点与限制汇总

项目AndroidiOS
目标三元组aarch64-linux-androidaarch64-apple-ios
交叉编译工具NDK(sysroot + clang/lld,见 rustflags)iOS sysroot(任意主机均可,非 macOS 需设SDKROOT
传输通道adb pushscp(设备需 sshd,即越狱)
执行通道adb shell,在/data/local/tmp下运行ssh到越狱设备
运行时依赖libc++_shared.so与二进制同目录(LD_LIBRARY_PATHldid+ent.xml注入 entitlements
环境准备设备开启 ADB 调试越狱(rootless 用/var/jb/var/mobile/runner,rootful 用/var/root/runner
退出码回传exit $LASTEXITCODEexit $LASTEXITCODE

需要注意的限制与前提:

  • 两条流程的 runner 脚本均用 PowerShell(pwsh)编写,Windows 上开箱即用,Linux/macOS 需安装 pwsh 并保证adb(Android)或ssh/scp(iOS)可用;
  • Android 配置中 NDK 的 clang 资源目录写死了clang/20路径,使用其他 NDK 大版本时需按实际目录调整该-L项;
  • iOS 方案对设备有硬性要求(越狱 +ldid),且 CI 中 iOS 任务仅保证编译通过(ci.yml L111-L116 的kind: native走 clippy 检查),真机执行完全依赖本文的 runner 方案;
  • 具体要运行哪些测试,遵循 docs/testing.md 的测试分类:移动端 GPU 测试位于tests/tests/wgpu-gpu(可参考cargo xtask test --test wgpu-gpu的本地跑法,在移动端则等价地由 runner 逐个执行编译出的测试二进制),而仅验证 API 校验逻辑的wgpu-validation类测试基于 noop 后端、无需真机。

按以上配置完成后,cargo test --target aarch64-linux-android(或aarch64-apple-ios)即可像在本机一样发起测试:Cargo 完成交叉构建后自动调用 runner,二进制被推送到设备执行,结果退出码驱动整个测试框架的成败判定——这就是 wgpu 仓库把移动端测试从"能编译"推进到"能验证"的完整链路。

【免费下载链接】wgpuA cross-platform, safe, pure-Rust graphics API.项目地址: https://gitcode.com/GitHub_Trending/wg/wgpu

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询