PyStudio Termux 构建踩坑记(一):从 bootstrap 大包到可维护构建体系
这篇是 PyStudio Termux 构建系列的第一篇,也是一开始方向转弯最大的一篇。最初我们想直接打一个“什么都有”的 bootstrap,后来发现能编译一次不等于能长期维护。真正的问题不是 CI 多跑几次,而是 bootstrap 承担了太多职责。
后来路线变成:bootstrap 只做基础环境,工具链拆成可选包,包索引负责依赖,清单站负责发现,大文件镜像负责下载。这条线一旦拆清楚,后面 Python LSP、C++、debug、proot 才有地方放。
这张图是这个系列的底图:bootstrap 越小,后续包管理和镜像策略越有空间。
最早的问题:能编译不等于能长期维护
最开始的目标很朴素:给 PyStudio 打出一个能在 Android App 私有目录里运行的 Termux 风格环境。能运行 pwd、cd、ls、pkg、python、pip、node,再逐步加上 C/C++、LSP、debugger、proot、distro 这些能力。
早期方案有几个典型坑:
- bootstrap 成功解压,不代表命令都能跑。
pwd、cd这类 shell 内建命令快,不代表ls、pkg这些 ELF 或脚本没有路径问题。- 把 Python、pip、node、clangd、lldb、proot 全塞进一个大包,下载和更新都会变重。
- GitHub Actions 能编译成功一次,不代表下一次上游变动后还能成功。
- Android 私有 app 路径不是
/data/data/com.termux/files/usr,很多上游包默认路径会失效。
这类问题不是靠多跑几次 CI 能解决的,根子在架构。
termux-generator 给出的启发
msmt2018/termux-generator 的价值在于,它证明了一件事:bootstrap 可以先变成一个小而清楚的产物,只负责把基础终端环境搭起来。
这比“直接把所有功能烤进 bootstrap”更好,因为:
- bootstrap 构建失败时,问题范围小。
- 工具链可以按需安装,不必每个用户都下载一大坨。
- Python、Node、C/C++、debug、proot 可以分组构建和分组发布。
- app 侧可以做自己的包管理,已经安装的包可以复用。
于是构建体系开始从“大包思路”转向“Termux pkg 思路”。
真正需要拆开的几层
构建侧的职责不是只产出一个压缩包,而是把包、索引、镜像和最终清单一起交付。
后来稳定下来的分层大致是这样:
1 | bootstrap |
这样做以后,构建和安装的边界都清楚很多。
构建侧负责:
- 找源仓库。
- 应用 PyStudio 适配补丁。
- 按架构构建
.deb。 - 生成
Packages.xz和清单。 - 把产物发布到 GitHub Release。
- 可选同步大文件到 ModelScope。
- 把最终 JSON 清单推到专用清单仓库。
app 侧负责:
- 拉取
runtime-packages.json。 - 根据设备 ABI 选择包源。
- 解析
Packages.xz。 - 递归解析
Depends/Pre-Depends。 - 下载缺失
.deb。 - 校验 SHA256。
- 安装并记录已安装包版本。
这就接近 Termux 官方 pkg 的思路,而不是“下载一个巨型工具链压缩包”。
主仓库和薄仓库的关系
仓库架构也踩过坑。最开始容易把每个工具链都做成一个“完整仓库”,里面复制大量公共构建脚本和源仓库逻辑。这样短期方便,长期会变成维护灾难。
后来更合理的是:
1 | pystudio-termux-builds |
这个结构的好处是:大部分重复工作在主仓库完成,工具链只是构建目标不同。将来再接入新的源仓库,也不需要为每个工具链复制一套。
GitHub Actions 里的几个坑
GitHub Actions 很适合做这种构建,但要注意几个细节。
构建矩阵要按风险分组。稳定包可以一起跑,新包、大包、容易失败的包单独跑。比如 git 这种已经成功过的包,就不要和 python-viz、cpp-lsp、debug-tools 这类复杂组合绑在一个 job 里,否则排查成本很高。
构建复用必须看版本。复用旧包不是简单地“文件存在就跳过”。上游补丁、PyStudio 适配补丁、构建脚本版本、Android 兼容补丁都可能变化。调试清单里要记录批次、包名、版本、release tag、源 commit 或补丁信息,否则后面很难判断一个包是不是“旧但还能用”。
发布和清单更新要集中处理。GitHub API 有速率限制,不要为了每个文件单独查一次是否存在。更好的办法是先拉一次 release asset 列表,建立本地索引,再查漏补缺。清单更新也尽量集中提交,减少请求次数。
跨仓库同步需要专门 token。GITHUB_TOKEN 通常只对当前仓库有权限。构建仓库要推送到专用清单仓库时,需要一个有目标仓库写权限的 <GITHUB_PAT>,在 Actions secrets 里保存为类似 <MANIFESTS_REPOSITORY_TOKEN> 的名字。
结论
这次最大的经验是:bootstrap 不应该承载全部野心。
对于 PyStudio 这类 App 内置运行时,合理结构应该是:
- bootstrap 小而稳定;
- 工具链拆成可选包;
- 包源像 Termux 一样用
.deb和Packages.xz管; - 清单站只放轻量 JSON;
- 大文件交给 GitHub Release 和 ModelScope;
- app 侧做自己的解析、依赖复用和安装状态管理。
这样后续要加 Python LSP、C++ LSP、debug、proot、distro、treesitter,都不会逼着用户重复下载同一份 Python 或 openssl,也不会让每次构建都从头烤一遍完整世界。