OpenClaw Android 发布代理策略与第三方许可证合规实战指南
【免费下载链接】openclawThe AI that really does things. Any OS. Any Platform. The lobster way. 🦞项目地址: https://gitcode.com/GitHub_Trending/cl/openclaw
导读
本指南聚焦 OpenClaw 仓库中 Android 应用的两个核心工程治理主题:Google Play 发布流程的自动化代理约束与Settings 中第三方许可证(Licenses)屏幕的维护规范。前者定义了 Agent 驱动发布时唯一合法的上传入口、失败后的熔断规则与生产环境升级的边界;后者规定了许可证文件的存放格式、运行时发现机制与排序约定。读完本文,你将掌握pnpm android:release:upload的完整调用链(从 pnpm 脚本到 fastlane lane)、失败恢复时哪些命令必须回避,以及如何正确地为 Android 依赖变更增补或移除许可证文件并验证其呈现。
本文所有论断均以仓库中 apps/android/CLAUDE.md 为纲领,并结合 scripts/android-release-upload.sh、scripts/lib/android-fastlane.sh、apps/android/fastlane/Fastfile、AndroidLicenseNotices.kt 与 AndroidLicenseNoticesTest.kt 等仓库文件进行印证与扩展。
Google Play 发布:Agent 驱动的唯一合法路径
发布上传的唯一入口
apps/android/CLAUDE.md明确要求:Agent 驱动的 Google Play 上传必须且只能使用pnpm android:release:upload。这一约束在根目录 package.json 中可以看到完整的脚本族:
android:release→bash scripts/android-release.shandroid:release:archive→run_android_fastlane android play_store_archiveandroid:release:auth:check→run_android_fastlane android auth_checkandroid:release:metadata→run_android_fastlane android metadataandroid:release:preflight→run_android_fastlane android release_preflightandroid:release:signing:check→run_android_fastlane android signing_checkandroid:release:signing:plan→run_android_fastlane android signing_planandroid:release:signing:sync:pull/sync:push→run_android_fastlane android signing_sync_pull / signing_sync_pushandroid:release:upload→bash scripts/android-release-upload.sh
其中android:release:upload是唯一的"上传入口"。从源码看,scripts/android-release.sh 仅是一层转发壳(exec bash "${ROOT_DIR}/scripts/android-release-upload.sh" "$@"),真正的逻辑在 scripts/android-release-upload.sh:它在仓库根目录解析路径、校验未知参数(-h/--help除外)、随后进入apps/android目录调用run_android_fastlane android release_upload。
值得注意的细节:脚本开头有一段针对 macOS 的 Bash 版本兼容逻辑——当OSTYPE为darwin*且当前 Bash 版本 ≥ 5.3 时,会先exec /bin/bash "$0" "$@"重新执行,原因是 Bash 5.3+ 在 macOS 上向 heredoc 管道写内容时可能先于读取方启动而死锁。这意味着即便在 macOS 环境下,该脚本也会保证以系统自带的/bin/bash运行,避免发布过程因管道死锁中断。
fastlane 解析链:从 bundle 到 lane
run_android_fastlane定义于 scripts/lib/android-fastlane.sh,其解析优先级是:
- 校验
apps/android/Gemfile是否存在; - 优先使用
BUNDLE_GEMFILE指向该 Gemfile 的bundle _2.6.9_ exec fastlane(锁定 Bundler 版本 2.6.9,并设置_OPENCLAW_ANDROID_FASTLANE_EXECUTION_PROVENANCE=locked); - 若 bundle 未安装且环境变量
OPENCLAW_MOBILE_RELEASE_REF_MODE为intent,则直接报错并返回失败状态; - 否则回退到全局
fastlane; - 仍不可用时,尝试遍历
rbenv versions找到可用的 fastlane; - 全部失败则输出安装提示:
Install Ruby 3.4.10, then run: cd apps/android && gem install bundler -v 2.6.9 && bundle _2.6.9_ install。
因此,Agent 在运行pnpm android:release:upload前,宿主环境需要具备 Ruby 3.4.10、Bundler 2.6.9 以及apps/android下的 fastlane bundle 依赖。
release_uploadlane 的完整执行序列
apps/android/fastlane/Fastfile 中的release_uploadlane 展示了上传前的一整套流水线:
lane :release_upload do check_android_versioning! # 版本号/版本码校验 version_metadata = read_android_version_metadata validate_android_release_preflight!(version_metadata) # 预检 screenshots # 确定性截图生成 ENV["SUPPLY_UPLOAD_METADATA"] = "1" # 强制上传元数据 ENV["SUPPLY_UPLOAD_SCREENSHOTS"] = "1" # 强制上传截图 build_release_artifacts! # 本地构建签名发布产物 upload_play_store_build!(version_metadata, upload_metadata: true, upload_screenshots: true) UI.success("Uploaded Android Play build to #{play_track}: ...") UI.important("Production promotion remains manual in Google Play Console.") end即一次pnpm android:release:upload会依次完成:版本校验 → 发布预检 → 截图生成 → 元数据上传 → 签名产物构建 → 将 Play AAB 上传到internal(内部测试)轨道。Fastfile 顶部定义了DEFAULT_PLAY_TRACK = "internal",且受保护的 beta CI 只允许上传到internal与wear:internal(见 Fastfile 中play_track == "internal" && wear_play_track == "wear:internal"的守卫逻辑),这是"不越权推广"的又一重保险。
失败熔断:上传失败后必须停止
apps/android/CLAUDE.md规定了严格的失败处理纪律:
- 一旦
pnpm android:release:upload以非零退出码结束,必须立即停止并报告失败步骤; - 失败后不得继续以下任何操作:
pnpm android:release:archive;pnpm android:release:metadata;fastlane android play_store;fastlane android metadata;- 直接使用 Gradle 发布产物配合 Google Play 上传命令;
- Google Play API 变更(mutation)命令;
- mobile release ref 记录。
其设计意图从 Fastfile 可以印证:play_store_archive被定位为"本地构建签名发布产物、不上传"的校验用途(desc "Build signed Android release artifacts locally without uploading"),因此android:release:archive不是截图、元数据、签名、校验或上传环节失败后的兜底发布路径。也就是说,一旦上传链路失败,Agent 只能上报问题等待人工介入,禁止"绕道"完成发布。
生产(Production)推广边界
- Agent 默认不得将 Android 发布推广到生产(production)轨道,生产推广保持人工操作,由 Google Play Console 手动完成;
- 唯一例外是:失败状态已被报告之后,用户明确要求推广某个已准备好的具体发布,此时才可执行。
这与 Fastfile 中UI.important("Production promotion remains manual in Google Play Console.")的提示保持一致。
Licenses 屏幕:Android 第三方许可证合规维护
许可证文件的存放与格式
Settings 页签下的 Licenses 屏幕用于展示第三方/开源依赖致谢。相关规则:
- 捆绑的许可证文件存放于 apps/android/THIRD_PARTY_LICENSES/openclaw/licenses/;
- 文件必须是UTF-8 编码的
.txt文件,不得使用 Markdown、HTML、RTF、JSON、XML 或自动生成的 notice 汇总包; - 文件名应是纯粹的依赖名,例如
Manrope.txt;文件名即列表行标题,不得再显示为行副标题; - 许可证文件打包为 Android assets(asset 目录
openclaw/licenses),运行期由代码发现,禁止在 Compose 中硬编码逐条许可证行。
从仓库现状看,该目录当前收录了约 90 个许可证文件,覆盖 AndroidX 组件、Coil、OkHttp and Okio、Bouncy Castle Provider、llama.cpp、d3 系列、cytoscape 系列、KaTeX、Mermaid、Manrope(字体)等依赖,文件命名遵循"依赖名.txt"规范,例如Accompanist Drawable Painter.txt、SLF4J API.txt。
运行期发现机制:AndroidLicenseNotices
许可证屏幕的数据加载完全由 AndroidLicenseNotices.kt 驱动:
- 常量
ANDROID_LICENSE_ASSET_DIRECTORY = "openclaw/licenses"对应 assets 下的目录; isAndroidLicenseFileName只接受以.txt结尾(忽略大小写)的文件;androidLicenseTitleFromFileName取文件名去除最后一个.后缀的部分作为显示标题,例如Manrope.txt → "Manrope"、OkHttp and Okio.txt → "OkHttp and Okio";若去除后缀后为空,则回退为"License";loadAndroidLicenseNotices(assetManager)通过AssetManager.list枚举目录、过滤.txt、逐个以UTF-8读取正文,封装为AndroidLicenseNotice(title, fileName, text),最后进行排序。
UI 侧 SettingsScreens.kt 的LicensesSettingsScreen用remember(context) { loadAndroidLicenseNotices(context.assets) }一次性加载,列表为空时展示空态,否则通过ClawListPanel渲染每一条许可证。
排序规则与"无数字前缀"约束
排序约定(代码中用compareBy(String.CASE_INSENSITIVE_ORDER){ title }.thenBy(...){ fileName }实现):
- 按派生的显示标题进行不区分大小写的字母序排序;
- 标题相同时,以文件名作为平局决胜(tiebreaker);
- 禁止使用数字文件名前缀来人为控制顺序。
因此新增许可证时,应保持"依赖名.txt"的纯命名,不要添加01_、02_之类的序号前缀。
展示与放置要求
- 许可证正文以等宽字体原样渲染(verbatim monospace),不得改写内容;
- Settings 中的
Licenses区块固定在设置页底部、位于Account之后,且只有一行Licenses,不带行副标题(除非产品方向改变)。
首方依赖排除与维护时机
- 禁止添加 OpenClaw、OpenClaw Foundation 或其他第一方/自有实体的许可证条目——该屏幕只用于第三方/开源依赖致谢;
- 每当新增、移除或升级Android 依赖时,必须审计 apps/android/THIRD_PARTY_LICENSES/openclaw/licenses/ 是否需要同步更新,并排除 OpenClaw Foundation 自有的依赖。
变更后的测试与验证
规则要求:改动许可证加载或呈现逻辑时,必须同步更新 AndroidLicenseNoticesTest.kt 并运行聚焦的 Android 验证。现有测试覆盖了以下关键断言(可视为行为契约):
isAndroidLicenseFileName_acceptsTxtOnly:.txt/.TXT通过,.md与无扩展名文件被拒绝;androidLicenseTitleFromFileName_usesExactFileNameStem:标题取文件名主干,如Manrope.txt → "Manrope"、SLF4J API.TXT → "SLF4J API";androidLicenseTitleFromFileName_fallsBackForBlankStem:空主干回退为"License";loadAndroidLicenseNotices_readsPackagedTxtAssets:用 Robolectric 读取真实 assets,断言全部 80+ 个标题的字母序列表与文件内容高度吻合,并验证正文确实包含各类开源许可证文本(SIL Open Font License、Apache License、BSD 2/3-Clause、MIT License、Bouncy Castle Licence),且text不以Title:开头(保证是纯正文而非结构化的 notice bundle)。
关键路径速查表
| 关注点 | 仓库依据 | 关键结论 |
|---|---|---|
| 唯一上传入口 | package.json → android-release-upload.sh | Agent 只能用pnpm android:release:upload上传 |
| lane 解析 | android-fastlane.sh | 优先bundle _2.6.9_ exec fastlane,回退全局/rbenvfastlane |
| 上传轨道 | Fastfile | 默认internal内部测试轨道,不推广生产 |
| 失败熔断 | CLAUDE.md | 上传失败即停止,禁止走 archive/metadata/play_store 等旁路 |
| 许可证目录 | THIRD_PARTY_LICENSES/openclaw/licenses | UTF-8.txt纯文本,依赖名.txt命名 |
| 运行期发现 | AndroidLicenseNotices.kt | assets 枚举 + 字母序排序 + 文件名 tiebreaker |
| 变更验证 | AndroidLicenseNoticesTest.kt | 聚焦测试覆盖格式、标题派生、排序与正文完整性 |
实践要点与常见陷阱
- 不要在失败后"换一条路发布":
play_store_archive只是本地归档校验,上传失败后执行它会绕过上传链路应有的校验与上报,违反策略。 - 许可证正文保持原样:以等宽字体原样展示,不要格式化或截断;不要使用 Markdown/HTML 等富文本格式存放。
- 命名即 UI:文件名就是行标题,因此命名要清晰、可读(如
OkHttp and Okio.txt),避免缩写歧义,也不要加数字前缀打乱字母序。 - 首方依赖不入列:OpenClaw 自身的许可信息不属于"第三方致谢"范畴。
- 环境依赖:运行
pnpm android:release:upload需要 Ruby 3.4.10、Bundler 2.6.9 与apps/android下的 Gemfile bundle;若 bundle 缺失,脚本会给出明确的安装提示。 - Agent 边界:生产推广默认保留在 Google Play Console 人工执行;除非用户明确指示且失败状态已上报,否则 Agent 不得越权推广。
通过遵循上述策略,Agent 可以在 OpenClaw 的 Android 工程中既高效地完成 Google Play 内部测试发布,又始终守住"失败即熔断、生产保持人工、许可证合规可审计"的工程底线。
【免费下载链接】openclawThe AI that really does things. Any OS. Any Platform. The lobster way. 🦞项目地址: https://gitcode.com/GitHub_Trending/cl/openclaw
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考