- AI Agent
- 人工智能
- 大模型
- AI 应用
- 工具调用
- 本地部署
- MCP Clients
- Agent 记忆
【免费下载链接】Operit
The most powerful AI agent and AI chat software on Android/Operit是一款Android上能力最为强大、发展最久的AI Agent
Operit 的 PR Check 工作流通过android_jvm与android_full两条车道分别执行 Android JVM 单元测试与完整 Android 构建,而应用源码中的System.loadLibrary("operit_ripgrep")决定了这两条车道都必须先拿到原生liboperit_ripgrep.so。本文以docs/TODO/native_ripgrep_pr_check_20260809/下的修复方案文档为主线,完整还原"JVM 测试车道缺少原生 ripgrep 库导致 test-only PR 在测试执行前即失败"这一问题的成因、修复设计与验证方式,并结合仓库中的工作流、Rust 源码、JNI 绑定与 Gradle 校验任务,深入讲解该原生搜索库的构建与接入全貌。读完本文,你将掌握 Operit 中 lane 化 CI 的准备逻辑、native ripgrep 的离线构建命令,以及"Gradle 前置校验 + 工作流准备"这一原生依赖保障模式的实现细节。
问题背景:PR Check 中的三条 Android 车道
Operit 的 PR Check 工作流(.github/workflows/pr-check.yml)根据 PR 改动范围,通过fastjob 中的plan步骤动态决定需要执行的检查车道。与 Android 相关的输出有三个:
| 输出 | 含义 | 典型触发改动 |
|---|---|---|
android_resources | 仅资源检查 | 翻译字符串、locales_config.xml等纯资源改动 |
android_jvm | Android JVM 单元测试 | app/src/main下的 Kotlin/Java 代码改动 |
android_full | 完整 Android 构建 | 原生模块、CMake、Gradle 配置、CI 工作流改动 |
车道的判定逻辑集中在 ci/script/pr_check.py 的classify_paths:改动路径命中ANDROID_FULL_PATTERNS(包括tools/native_ripgrep/**、app/src/main/cpp/**、cmake/**、各原生模块根目录等)即归入android_full;其余落在app/下的非资源改动归入android_jvm;纯翻译资源改动归入android_resources。三者互斥,且android_full优先级最高。
在pr-check.yml中,android_testsjob 的触发条件是:
if: >- needs.fast.result == 'success' && (needs.fast.outputs.android_jvm == 'true' || needs.fast.outputs.android_full == 'true')也就是说,只要改动涉及 Android 代码(无论 JVM 车道还是完整车道),都会运行:app:testDebugUnitTest。这正是问题爆发的入口。
现有行为:为什么 test-only PR 会提前失败
修复方案文档 1_PrepareNativeRipgrep.md 中记录的问题描述非常明确:
pr-check.ymlruns:app:testDebugUnitTestwhenandroid_jvmis enabled. Gradle requiresliboperit_ripgrep.sobefore its pre-build phase. The workflow installs the NDK and builds that library only whenandroid_fullis enabled, so test-only Android pull requests fail before their test suite executes.
根因链条可以拆解为三步:
应用启动即加载原生库:
app/src/main/java/com/ai/assistance/operit/util/ripgrep/NativeRipgrep.kt中的 JNI 对象在init块执行System.loadLibrary("operit_ripgrep"),并将searchJson声明为external函数:internal object NativeRipgrep { init { System.loadLibrary("operit_ripgrep") } @JvmStatic external fun searchJson( path: String, patterns: Array<String>, filePattern: String, caseInsensitive: Boolean, literal: Boolean, contextLines: Int, maxResults: Int ): String }这条加载语句使得任何 JVM 侧编译与打包路径都依赖
liboperit_ripgrep.so的存在。Gradle 有前置校验:
app/build.gradle.kts中注册了verifyExternallyBuiltNativeLibraries任务(app/build.gradle.kts),把src/main/jniLibs/arm64-v8a/liboperit_ripgrep.so列入"必须在 Gradle 之外预先构建"的原生库清单,校验规则为library.isFile && library.length() > 0L,否则直接抛出错误并提示运行tools/native_ripgrep/build_native_ripgrep.ps1:Missing or empty externally built native library: ... . Run tools/native_ripgrep/build_native_ripgrep.ps1 before packaging.因此,只要
jniLibs/arm64-v8a/下没有非空的.so,testDebugUnitTest会在测试真正执行前就失败。旧工作流只在完整车道准备原生库:修复前的
pr-check.yml仅在android_build(android_full车道)job 中安装 NDK 并执行 native ripgrep 构建;android_testsjob 虽然负责运行 JVM 测试,却没有对应的原生准备步骤。于是"只改了 Kotlin 测试或普通 Kotlin 代码、完全不触及原生模块"的 PR 也会因缺少.so而在测试套件执行前挂掉——这本应是最轻量的检查车道之一,却成了最容易失败的一环。
预期修正:让原生准备跟随 JVM 与完整车道
方案文档给出了明确的修复边界:
- Android JVM 检查必须获得非空的
liboperit_ripgrep.so; - 完整 Android 检查保留其既有的原生准备逻辑;
- 纯资源检查(resource-only)不安装 NDK、不构建原生库。
对应的实现原则是:
Install the NDK and build native ripgrep for either
android_jvmorandroid_full. Keep CMake, full dependency preparation, and all resource-only paths scoped to the full lane.
即"NDK + native ripgrep 构建"的准备动作从android_full专属提升为android_jvm || android_full共同需要;而 CMake 工具链安装、完整依赖下载(download_android_dependencies.sh full/prepare_android_dependencies.py --profile full)、STT 资产生成等重活仍然只属于完整车道。这样既修复了 test-only PR 的失败,又避免了资源改动(例如新增一种语言翻译)被拖入昂贵的原生构建流程。
仓库现状:修复在android_testsjob 中的落地形态
从当前仓库的 .github/workflows/pr-check.yml 看,修复后的android_testsjob 内已经包含一组无车道条件限制的"Restore native ripgrep cache + Build native ripgrep"步骤(只要 job 本身因 JVM 或完整车道被触发即执行),与方案文档的预期一致:
- name: Restore native ripgrep cache uses: actions/cache@... with: path: | ~/.cargo/registry ~/.cargo/git tools/native_ripgrep/target key: native-ripgrep-arm64-${{ runner.os }}-${{ runner.arch }}-ndk-${{ env.ANDROID_NDK_VERSION }}-api-${{ env.ANDROID_API_LEVEL }}-rust-${{ env.RUST_VERSION }}-${{ hashFiles('tools/native_ripgrep/Cargo.toml', 'tools/native_ripgrep/Cargo.lock', 'tools/native_ripgrep/**/*.rs', 'tools/native_ripgrep/build_native_ripgrep.ps1') }} - name: Build native ripgrep shell: bash run: | set -euo pipefail export ANDROID_NDK_HOME="$ANDROID_HOME/ndk/$ANDROID_NDK_VERSION" export CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER="$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android${ANDROID_API_LEVEL}-clang" rustup toolchain install "$RUST_VERSION" --profile minimal rustup target add --toolchain "$RUST_VERSION" aarch64-linux-android cargo "+$RUST_VERSION" build \ --manifest-path tools/native_ripgrep/Cargo.toml \ --release \ --target aarch64-linux-android \ --locked install -Dm755 \ tools/native_ripgrep/target/aarch64-linux-android/release/liboperit_ripgrep.so \ app/src/main/jniLibs/arm64-v8a/liboperit_ripgrep.so test -s app/src/main/jniLibs/arm64-v8a/liboperit_ripgrep.so关键点在于:android_testsjob 的环境变量(pr-check.yml)为这条命令提供了全部输入——ANDROID_NDK_VERSION: '25.1.8937393'、ANDROID_API_LEVEL: '26'、RUST_VERSION: '1.88.0';同 job 更早的"Install required Android toolchain"步骤(sdkmanager --install ... "ndk;$ANDROID_NDK_VERSION")则负责把 NDK 装进$ANDROID_HOME/ndk/,二者配合才构成完整的原生准备链路。末尾的test -s断言确保产物非空,与 Gradle 校验任务前后呼应。
同样地,android_build(完整车道)job 保留了独立的 NDK/CMake 安装、CMake 源码缓存恢复、full 依赖下载/准备以及相同的 native ripgrep 构建与缓存步骤(pr-check.yml),满足"full Android checks retain their existing native preparation"的要求。而android_resources车道只跑aapt2 compile资源编译,完全不涉及 NDK 与 Rust 工具链,验证了"resource-only checks do not install the NDK or build the native library"。
原生库本体:operit_ripgrep 的 Rust 实现与构建方式
crate 结构与依赖
tools/native_ripgrep/目录下是一个完整的 Rustcdylibcrate(tools/native_ripgrep/Cargo.toml):
[package] name = "operit_ripgrep" version = "0.1.0" edition = "2021" [lib] crate-type = ["cdylib"] [dependencies] globset = "0.4" grep-matcher = "0.1" grep-regex = "0.1" ignore = "0.4" jni = "0.21" serde = { version = "1", features = ["derive"] } serde_json = "1"依赖选型直接对应其功能定位:grep-regex/grep-matcher提供 ripgrep 同源的 Rust 正则匹配引擎,ignore负责WalkBuilder目录遍历(自动识别.gitignore),globset用于文件包含/排除 glob 匹配,jni提供 JNI 桥接,serde/serde_json负责把搜索结果序列化为 JSON 字符串回传给 Kotlin。
JNI 导出与搜索语义
入口函数是 tools/native_ripgrep/src/lib.rs 中的Java_com_ai_assistance_operit_util_ripgrep_NativeRipgrep_searchJson,其 JNI 签名与 Kotlin 侧external fun searchJson(path, patterns, filePattern, caseInsensitive, literal, contextLines, maxResults): String一一对应。从源码可以看到几个值得注意的实现细节:
- 多模式支持:
patterns是字符串数组,每个模式独立编译为RegexMatcher,逐行做"任一匹配"判定; - gitignore 感知遍历:
WalkBuilder开启git_ignore(true)、git_global(true)、git_exclude(true)、parents(true),与 ripgrep 的遍历语义一致; - 内置排除:
build_exclude_globs硬编码排除.backup/**、.operit/**、backup/**等目录(lib.rs); - 二进制探测:读取文件头 8KB 检查是否含
\0字节来跳过疑似二进制文件; - 结果聚合:命中文件输出
SearchBlock(filePath、firstMatchLine、lineContent、matchContext、matchCount),上下文行、单行内容、摘要均做了字符截断(400/300/80/4000 字符上限),防止超大文件撑爆模型上下文。
Kotlin 侧的消费方是app/src/main/java/com/ai/assistance/operit/core/tools/defaultTool/standard/StandardFileSystemTools.kt:searchNativeRipgrepBlocks在Dispatchers.IO中调用NativeRipgrep.searchJson,随后parseNativeRipgrepBlocks把 JSON 解析为RipgrepBlock列表与filesSearched计数(StandardFileSystemTools.kt),最终经由grepCodeWithNativeRipgrep供 Agent 的代码检索工具使用。这也解释了为何该库对 JVM 测试是硬性前置:编译期链接与运行期加载都离不开.so。
本地构建命令
面向本地开发者的构建入口有两个:Windows 下直接运行 tools/native_ripgrep/build_native_ripgrep.bat(内部转发到 PowerShell 脚本),或直接执行 tools/native_ripgrep/build_native_ripgrep.ps1。脚本支持三个参数:
| 参数 | 默认值 | 说明 |
|---|---|---|
-Targets | @("aarch64-linux-android") | 目标三元组列表,支持aarch64-linux-android、armv7-linux-androideabi、x86_64-linux-android、i686-linux-android |
-SdkDir | 空 | Android SDK 路径;缺省时依次读取local.properties的sdk.dir=、ANDROID_HOME、ANDROID_SDK_ROOT |
-ApiLevel | 23 | 链接器使用的 Android API 级别,如aarch64-linux-android23-clang.cmd |
脚本按目标三元组维护 ABI 映射(arm64-v8a/armeabi-v7a/x86_64/x86),依次执行rustup target add、设置CARGO_TARGET_<TARGET>_LINKER环境变量指向 NDK 的 LLVM 链接器、cargo build --release --target,最后把产物复制到app/src/main/jniLibs/<ABI>/liboperit_ripgrep.so。NDK 路径同样支持ANDROID_NDK_HOME、ANDROID_NDK_ROOT环境变量,或在 SDK 的ndk/目录下自动选取版本号最高的 NDK。CI 中的 bash 版构建命令正是这套流程在 Linux runner 上的等价实现。
车道分类的测试保障
修复不只是改工作流 YAML,车道判定逻辑本身也有单元测试守护。ci/test/test_pr_check.py(ci/test/test_pr_check.py)覆盖了关键场景:
test_default_strings_use_jvm_lane:改默认values/strings.xml归入android_jvm(会触发 JVM 测试,因此必须走原生准备);test_translation_only_uses_resource_lane:只改values-ro/strings.xml与locales_config.xml时归入android_resources(不安装 NDK、不构建原生库);test_native_change_uses_full_lane:改cmake/operit_git_source.cmake归入android_full;test_android_workflow_changes_use_full_lane:改动pr-check.yml等三个工作流文件均归入android_full;test_relocated_native_modules_use_full_lane:改avator/dragonbones、llm/llama等原生模块的CMakeLists.txt归入android_full。
这些用例印证了三车道互斥且优先级正确的设计,为"JVM 车道必然获得原生准备、资源车道必然跳过原生准备"提供了回归保障。工作流侧还有一个兜底:fastjob 中candidate判定 job 会根据android_build/android_tests的实际结果汇总成 PR 门禁,任何一条被要求但失败的车道都会让候选提交整体失败。
验证方式
方案文档记录的验证策略是:
Validate the workflow diff statically, then use the PR Check run as the build verification. No local build or test command is run because repository guidance requires explicit user authorization.
即两步走:第一步对pr-check.yml的 diff 做静态审查(确认 NDK 安装与 native ripgrep 构建步骤出现在android_testsjob 中,且未被android_full条件包裹;android_resources路径无原生准备);第二步直接以真实 PR 的 PR Check 运行结果作为构建验证——提交一个仅含 Kotlin 代码/测试改动的 PR,观察android_testsjob 是否成功走到:app:testDebugUnitTest执行阶段而非在 Gradle 前置校验处失败。
对复现问题感兴趣的读者,也可以在本地复刻验证链条:删除或清空app/src/main/jniLibs/arm64-v8a/liboperit_ripgrep.so后运行./gradlew :app:testDebugUnitTest,即可看到verifyExternallyBuiltNativeLibraries抛出的 "Missing or empty externally built native library" 错误;随后按提示执行 build_native_ripgrep.ps1 重新生成.so,测试任务即可正常推进。这一"先失败、后修复"的过程,正是 CI 与本地构建共享同一套原生库契约的直观体现。
小结
本次修复的本质,是把"原生搜索库准备"从完整车道下沉到所有会运行 Gradle 测试的车道,同时严格守住资源车道不做原生工作的成本边界。整条链路中,pr_check.py负责车道分类,pr-check.yml的android_testsjob 负责在 JVM/完整车道安装 NDK 并构建liboperit_ripgrep.so,verifyExternallyBuiltNativeLibraries负责在 Gradle 侧兜底校验产物非空,NativeRipgrep.kt+ Rustlib.rs则定义了库的最终消费方式。四者环环相扣,构成了 Operit 中"原生依赖由 CI 显式准备、Gradle 只做一致性校验"的工程范式,可供其他同时含有 JVM 测试与原生库的大型 Android 项目直接借鉴。
- AI Agent
- 人工智能
- 大模型
- AI 应用
- 工具调用
- 本地部署
- MCP Clients
- Agent 记忆
【免费下载链接】Operit
The most powerful AI agent and AI chat software on Android/Operit是一款Android上能力最为强大、发展最久的AI Agent
相关推荐
Onyx 仓库实战:用 Greptile check-pr 技能实现 GitHub / GitLab / Perforce 全平台 PR 自动检查与修复
Onyx 仓库实战:用 Greptile check pr 技能实现 GitHub / GitLab / Perforce 全平台 PR 自动检查与修复 导读
AI 应用大模型RAGAI Agent后端前端Cilium 实战:用 cilium-dbg bpf ipcache delete 精确删除 BPF IPCache 中的 IP 与身份条目
Cilium 实战:用 cilium dbg bpf ipcache delete 精确删除 BPF IPCache 中的 IP 与身份条目 凌晨三点,一台节点
AI Agent人工智能大模型AI 应用工具调用本地部署MCP ClientsAgent 记忆GUI 自动化深度学习图像处理终极指南:如何用TensorFlow-Course快速掌握CNN技术
深度学习图像处理终极指南:如何用TensorFlow Course快速掌握CNN技术 TensorFlow Course是一个专注于提供简单易用TensorFl
AI Agent人工智能大模型AI 应用工具调用本地部署MCP ClientsAgent 记忆GUI 自动化
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考