- AI Agent
- 人工智能
- 大模型
- AI 应用
- 工具调用
- 本地部署
- MCP Clients
- Agent 记忆
【免费下载链接】Operit
The most powerful AI agent and AI chat software on Android/Operit是一款Android上能力最为强大、发展最久的AI Agent
导读
本文是 Operit 构建系统重构系列的第 4 步技术指南,聚焦于Gradle 输入规范化:将app模块对本地 AAR/JAR 的宽泛目录扫描(fileTree)、对.so的模糊选择规则(pickFirst)以及 Wrapper 分发地址的单一镜像依赖,改造为"清单确认、精确声明、逐项校验、错误即终止"的可审计构建输入模型。读完本文,你将掌握如何为大型 Android 仓库建立 Gradle 输入校验任务、编写 manifest 驱动的精确依赖声明,以及如何用官方 SHA-256 锁定 Gradle Wrapper 分发内容。
一、步骤定位:构建系统重构中的"输入可信化"关键一环
在 refactor_building_sys 重构总计划 中,整个构建系统重构围绕一个总目标展开:为 JavaScript assets、外部二进制、Android 模块、APK 变体和补丁发布建立唯一且可审计的构建输入。该计划以 14 个编号步骤推进,每步可独立审查和提交,后一步只依赖已完成的前一步。
本步骤(步骤 4)直接建立在 步骤 3:外部制品清单 之上。步骤 3 负责在版本控制中建立一份 manifest,登记全部外部归档与解包文件的来源、版本、许可证、SHA-256、大小、ABI 与唯一消费者;本步骤则负责让 Gradle只消费这份 manifest 已确认的文件,把"构建图能否追溯到制品清单"这个问题落到实处。两者结合,才能形成从"清单登记"到"构建消费"的闭环。
按总计划中的构建流水线段落划分,本步骤对应environment_prepare(准备固定工具链并输出环境报告)、dependency_prepare(执行 pnpm 冻结安装并确认 Gradle 输入)与artifact_verify(校验 AAR、JAR、JNI、models 与 subpack)三个阶段的核心工作。
二、旧实现情况:构建输入为何不可信
原文档明确列出了当前实现存在的四类问题,它们是本步骤要解决的"病灶":
app/libs目录通配扫描:通过implementation(fileTree(...))无条件接收该目录下的全部 AAR 和 JAR。这种声明方式意味着:只要有人往目录里丢一个文件,构建图就会自动变化——文件来源、版本、许可证、hash 全部不可追溯,也无法从 Gradle 依赖图反向定位到步骤 3 的制品 manifest。- 宽泛的
.so选择规则:packaging.resources中存在pickFirsts += "**/*.so"。这条规则允许打包阶段在遇到同名 native 库时"随便挑一个",直接掩盖了重复提供者问题——GIF native 库、libc++_shared.so等究竟由谁提供、内容是否一致,构建本身无法回答。 - 本地二进制与 native 库缺乏 Gradle 输入校验:外部构建的 native 库、本地 AAR 在进入打包流程前,没有文件名、大小、hash、ABI 层面的强制校验。
- Wrapper 依赖单一镜像地址:Gradle Wrapper 的分发地址指向单一镜像(阿里云);PR 门禁重构虽已补入 Gradle 8.13 官方
distributionSha256Sum,但分发 URL 与校验和是否指向同一发行内容仍需核对。
仓库现状验证
对照 app/build.gradle.kts 的实际源码,可以确认当前工作树的部分现状:
- 依赖声明区(约 L578 起)已不使用
fileTree,而是采用精确声明:implementation(files("libs/ffmpeg-kit-local.aar"))(L595),注释明确写着"The only vendored artifact is the custom FFmpegKit AAR";app/libs目录当前为空,本地库通过 Maven 坐标与精确文件声明引入。 packaging.resources中仍保留pickFirsts += "**/*.so"(L519),这正是旧实现情况中待删除的宽泛规则。- gradle/wrapper/gradle-wrapper.properties 中
distributionUrl指向https://mirrors.aliyun.com/macports/distfiles/gradle/gradle-8.13-bin.zip,distributionSha256Sum已写入20f1b1176237254a6fc204d8434196fa11a4cfb387567519c61556e8710aed78,印证了"Wrapper 校验和已写入"的完成记录。
三、预期的新实现情况:输入契约的五项指标
重构完成后,Gradle 输入应满足以下五个指标:
| 指标 | 说明 |
|---|---|
| 精确文件声明 | 本地 AAR 和 JAR 使用精确文件名声明,不再扫描整个目录 |
| 无重复 native 提供者 | JNI 与 native 冲突通过删除重复提供者解决,而非靠pickFirst掩盖 |
| 解析前校验 | Gradle 在解析相关任务前校验制品文件名、hash、ABI 和所有者 |
| Wrapper 内容锁定 | Wrapper 使用项目确认的单一地址与 Gradle 官方 SHA-256 |
| 清单闭环 | Gradle 只消费步骤 3 manifest 中已经确认的文件 |
这五项指标共同指向一个核心设计哲学:输入错误必须明确终止,而不是静默选择其他文件或地址——这也是后续所有重构步骤(APK 审计、补丁链等)得以成立的前提。
四、修改作用域
本步骤的修改被严格限定在构建输入层面:
计划修改:
app/build.gradle.kts—— 依赖声明与打包规则gradle/wrapper/gradle-wrapper.properties—— Wrapper 分发地址与校验和config/build-system/artifacts/—— 制品清单目录(承接步骤 3)- 与 Gradle 输入校验有关的 build logic
本步骤不修改:
- Android 源码能力边界
- product flavor 和 build type
- JS assets 生产任务
这与总计划的执行约束一致:"同一类输入只能有一个受版本控制的事实来源;每个二进制、asset 和 native 库只能有一个消费模块;文件缺失、hash 不符、签名不符、版本不递增或变体未批准时直接终止。"
五、实施计划详解
1. 将fileTree替换为 manifest 已确认文件的精确依赖声明
implementation(fileTree(...))的问题在于它把"目录内容"当作依赖输入,构建图无法回答"这个文件是谁、为什么在这里"。替代方案是:只声明 manifest 中已确认的文件,即
implementation(files("libs/ffmpeg-kit-local.aar"))这种写法的意义在于:每个本地制品都以精确路径进入依赖图,删除、替换文件都会产生明确的构建差异,而不是被目录扫描静默吞没。从当前源码看,app/build.gradle.kts 已迈出这一步——注释 "The only vendored artifact is the custom FFmpegKit AAR" 表明仓库自带的 vendored 制品已被收敛为唯一一个。
2. 删除宽泛.so选择规则,并删除重复 native 提供者
pickFirsts += "**/*.so"(app/build.gradle.kts)是一条典型的"症状治理"规则:当多个来源提供同名.so时,打包器无法判断哪个是正确的,只能按顺序取第一个。重构方向是:
- 删除宽泛
pickFirst规则; - 审计同名
.so的全部提供者,只保留唯一所有者,其余删除。
步骤 3 的审计结果提供了重要线索:"arsc.jar已无当前消费者;GIF native 库由 Maven 的android-gif-drawable提供且与归档副本一致;libc++_shared.so的唯一所有者仍需完成审计"。这正说明重复提供者需要逐一确认唯一所有者,而非继续用选择规则掩盖。
3. 将无消费者的arsc.jar和其他文件移出构建输入
步骤 3 已完成对arsc.jar的源码 import、字节码引用和运行时加载点搜索,结论标记为[DONE: 当前无消费者]。本步骤的任务就是把这些"死文件"从构建输入中移出——它们既不参与编译,也不应留在依赖扫描的范围内,否则只会增加构建图噪声并误导审计。
4. 增加只读的 Gradle 输入校验任务,输出可审计报告
这是本步骤的核心工程产出:一个只读的校验任务(不修改任何输入文件),在 Gradle 解析相关任务前完成校验并输出结构化报告。校验项包括:
- 文件名:与 manifest 条目逐一对应,不存在未声明文件;
- hash:SHA-256 与 manifest 记录一致;
- ABI:native 库的 ABI 目录与项目唯一支持的
arm64-v8a一致; - 所有者:每个文件只映射到一个消费模块。
当前仓库已存在一个可视为雏形的实现——verifyExternallyBuiltNativeLibraries任务(app/build.gradle.kts):
val verifyExternallyBuiltNativeLibraries by tasks.registering { description = "Checks native libraries built outside Gradle before Android packaging." group = "verification" inputs.property("requiredLibraries", requiredExternallyBuiltNativeLibraries.map { library -> library.path }) inputs.property("ffmpegKitAar", ffmpegKitLocalAar.path) inputs.property("ffmpegKitArm64Libraries", requiredFfmpegKitArm64Libraries) outputs.upToDateWhen { false } doLast { // 缺失或空文件 -> require 失败终止 // FFmpegKit AAR 内 arm64 native 条目缺失 -> require 失败终止 } }该任务在preBuild阶段被挂载(tasks.named("preBuild") { dependsOn(verifyExternallyBuiltNativeLibraries) },L563-L566),校验策略为"缺失即require失败、明确终止",与总计划"文件缺失、hash 不符……直接终止"的执行约束完全一致。它校验的内容包括:src/main/jniLibs/arm64-v8a/liboperit_ripgrep.so必须存在且非空(构建提示运行tools/native_ripgrep/build_native_ripgrep.ps1),FFmpegKit AAR 必须存在且包含 10 个指定的jni/arm64-v8a/native 条目。未来可在此基础上补入 SHA-256、ABI 与所有者核对,并输出可审计的结构化报告。
5. 写入 Wrapper 官方 SHA-256,并核对当前分发地址对应的内容
Gradle Wrapper 的distributionSha256Sum是供应链安全的关键防线:它让 Wrapper 在解压分发 zip 前先校验内容 hash,任何内容被篡改或地址指向不同发行都会直接失败。当前 gradle/wrapper/gradle-wrapper.properties 已写入:
distributionUrl=https\://mirrors.aliyun.com/macports/distfiles/gradle/gradle-8.13-bin.zip distributionSha256Sum=20f1b1176237254a6fc204d8434196fa11a4cfb387567519c61556e8710aed78验收要求是"Wrapper URL 与 SHA-256 指向同一发行内容"——即镜像上的gradle-8.13-bin.zip必须与 Gradle 官方发布的 8.13 二进制包内容一致(可对比官方 sha256 记录)。作为对照,仓库内 tools/shower/gradle/wrapper/gradle-wrapper.properties 使用官方https://services.gradle.org/distributions/gradle-8.13-bin.zip,而app/src/main/assets/templates/android/gradle/wrapper/gradle-wrapper.properties模板则指向gradle-9.1.0-bin.zip——这说明仓库内不同构建入口使用的 Gradle 版本并不统一,本步骤要求项目确认的"单一地址 + 官方 SHA-256"正是为了收敛这一局面。
6. 静态扫描 Gradle 配置,确认不存在其他本地目录依赖扫描
最后一步是防御性验证:对全部 Gradle 配置做静态扫描,确认除app模块外不存在其他fileTree、flatDir等本地目录依赖扫描,防止"旧模式换了个位置继续存在"。从当前工作树搜索看,fileTree已不再出现在任何.kts构建脚本中,app/libs目录为空——这与旧实现情况中"通过fileTree无条件接收全部 AAR 和 JAR"的描述形成对比,说明目录扫描模式正在被清除。
六、验收标准
本步骤的完成以五条验收标准为准,全部满足方可标记完成:
- Gradle 依赖声明中不存在本地 AAR 或 JAR 通配扫描——所有本地制品均以精确文件名声明;
.so没有宽泛pickFirst规则——重复提供者已被删除,不再依赖选择规则;- manifest 与 Gradle 消费文件一一对应——构建图可追溯到步骤 3 的制品清单;
- Wrapper URL 与 SHA-256 指向同一发行内容——分发地址与校验和一致;
- 输入错误会明确终止——不会选择其他文件或地址,错误以构建失败形式暴露。
七、当前完成状态与后续验证要求
原文档的完成记录明确了当前进度:状态:部分准备。Wrapper 校验和已写入;本步骤其余输入规范化尚未开始。这意味着:
- 已完成:
gradle/wrapper/gradle-wrapper.properties的distributionSha256Sum(Gradle 8.13 官方校验和); - 未完成:
fileTree精确声明收尾、宽泛pickFirst删除、无消费者文件移出、只读校验任务补全、全量静态扫描。
按原文档要求,本步骤完成前仍需记录Gradle 解析与编译验证结果——即在实际构建中验证:校验任务在解析相关任务前正确触发、输入错误导致明确终止、依赖图与 manifest 一一对应。同时需注意文档头部的For_Agent约束:未经用户明确授权,不执行编译、构建、测试或发布命令。
八、延伸阅读
- 重构总计划与构建流水线段落 —— 了解 14 步整体编排与本步骤的上下游关系
- 步骤 3:外部制品清单 —— 本步骤依赖的 manifest 登记工作
- app/build.gradle.kts —— 依赖声明、打包规则、校验任务与 STT asset 同步的完整实现
- gradle/wrapper/gradle-wrapper.properties —— Wrapper 分发地址与官方 SHA-256
- app/config/stt-model-assets.properties —— 步骤 3 清单思想的资产侧范例:6 字段管道分隔 manifest(目标路径 | 来源 URL | 字节大小 | SHA-256 | 来源描述 | 许可证)
其中 STT 模型资产 manifest 是理解"清单驱动构建输入"的最佳旁证:syncSttModelAssets任务(app/build.gradle.kts)逐行解析该 properties 文件,校验字节数与 SHA-256,下载失败或校验不符即终止,并清理未声明的陈旧文件——这正是本步骤要在 AAR/JAR/native 库层面推广的同一套"清单确认、逐项校验、错误即终止"模式。以它为参照,可以更清晰地看到 Gradle 输入规范化后整个构建输入的最终形态:每一项输入都有版本控制的事实来源,每一项校验失败都以明确错误终止,构建图与制品清单始终一一对应。
- AI Agent
- 人工智能
- 大模型
- AI 应用
- 工具调用
- 本地部署
- MCP Clients
- Agent 记忆
【免费下载链接】Operit
The most powerful AI agent and AI chat software on Android/Operit是一款Android上能力最为强大、发展最久的AI Agent
相关推荐
DeepSeek Harness 工具输出规范契约:从 `ContentBlock[]` 到可校验规范化值的架构演进
DeepSeek Harness 工具输出规范契约:从 ContentBlock 到可校验规范化值的架构演进 DeepSeek Harness 在 2026 0
人工智能AI AgentAgent 框架DeepSeekARIS proof-orchestrator 输出契约详解:结构化证明审计的 Markdown 与 JSON 规范
ARIS proof orchestrator 输出契约详解:结构化证明审计的 Markdown 与 JSON 规范 本指南系统解析 ARIS(Auto Res
AI 技能/插件AI 评测科研人工智能MCP 服务dsh-pluginDeepSeek Harness Composer 输入编辑范围修复:从 draft 扫描回退到 `beforeinput` 精确范围
DeepSeek Harness Composer 输入编辑范围修复:从 draft 扫描回退到 beforeinput 精确范围 导读:本文基于 DeepSe
人工智能AI AgentAgent 框架DeepSeek
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考