Environment:运行时环境包
这里讨论的 Environment 包,不是普通运行时插件,而是 Rootless Store 在本地安装、管理、启用后,为 Plugin 和 Shell 执行链路提供运行时能力的包。
它们直接面对三件事:
- 用户如何安装、启用、禁用和删除。
- 宿主如何把它提供的能力注入到
PATH、LD_LIBRARY_PATH和环境变量中。 - Environment 自身如何组织解释器、可执行二进制文件、脚本、动态库和工具链。
换句话说,Plugin 更关心“我要做什么”,Environment 更关心“我怎么提供运行条件”
EnvironmentManifest
interface EnvironmentManifest {
// 当前安装版本。
// 建议遵循 SemVer,例如 `1.2.19`
// 用于版本展示、更新判断和兼容性比较
// 也可以使用例如 `arm64-v8a`,区分设备架构
val installedVersion: String
// Environment 的 UI 展示名称。
// 这是渲染字段,不具备唯一性语义
val environmentRenderingName: String
// Environment 逻辑包名。
// 建议使用简易好记的名称,例如:
// `Python3`
val environmentPackageName: String
// Environment 唯一 ID。
// 推荐使用稳定高熵值,适合承担 Room 主键和跨版本识别
val environmentID: String
// Environment 图标引用。
// 允许为 `null`
// 应存 URI 或路径引用,不应直接内嵌二进制
val iconURI: String?
// Environment 作者 / 发布者名称
val author: String
// Environment 短描述。
// 适合列表页和详情页摘要展示
val environmentDescription: String
// Environment 声明的最低宿主环境要求。
// 这是声明值,不是运行时实时状态
val requiredEnvironment: HosterOverallStatus
// Environment 入口或主要可执行目标。
// 当前安装时会尝试对它设置可执行权限
val entryPoint: String
// 需要追加到 LD_LIBRARY_PATH 的包内路径列表。
// 每一项都会按 Environment 包目录拼接成绝对路径
val ldLibraryPath: List<String>
// 需要注入到执行环境中的环境变量。
// 当前值会按字面量注入,不会自动做包目录模板替换
val env: Map<String, String>
}
enum class HosterOverallStatus {
LIMITED, PERMISSIVE, ADB, ROOTD
}一个提供 Python 运行时的 Environment manifest 可以先写成这样:
{
"installedVersion": "3.14.0",
"environmentRenderingName": "Python 3",
"environmentPackageName": "Python3",
"environmentID": "d4f3c2a6a7b44c0fa5a7c6a7b9d8e001",
"iconURI": null,
"author": "Baidaidai",
"environmentDescription": "Provides a Python runtime for Rootless Store plugins.",
"requiredEnvironment": "LIMITED",
"entryPoint": "python3",
"ldLibraryPath": [
"lib"
],
"env": {
"PYTHONUNBUFFERED": "1"
}
}字段说明
installedVersion版本声明字段。当前类型仅为String,但语义上应视为可比较版本号,不建议把构建日期、压缩包文件名或渠道信息直接塞进这里。environmentRenderingName纯展示字段。允许后续改名,不应用它承担唯一标识、依赖定位或安装记录关联。environmentPackageNameEnvironment 逻辑命名空间,它应稳定、可重复。Rootless Store 会用它组织本地 Environment 目录。environmentIDEnvironment 主标识。这个字段的稳定性要求高于environmentRenderingName。如果后续 Room 以它作为主键,那么同一个 Environment 跨版本升级时不应变化。iconURI图标引用字段。null表示没有独立图标。这里存的是引用,不是图标内容本体。引用形式可以是content://...、文件路径或包内相对路径。author作者 / 发布者字段。适合用于展示、来源归属和问题追溯。environmentDescriptionEnvironment 摘要字段。建议说明它提供的运行时能力,例如“提供 Python 解释器与标准库”,而不是写成某个具体插件的功能说明。requiredEnvironment宿主环境声明字段。当前可序列化值来自HosterOverallStatus,即:LIMITED、PERMISSIVE、ADB、ROOTD。
它表达的是这个 Environment 包对宿主能力的要求,不是宿主机当前实时状态。entryPointEnvironment 的主要入口或主要可执行目标。当前系统会在安装后尝试对这个路径设置可执行权限。它不等同于 Plugin 的业务启动入口,Environment 通常不会被用户直接点击运行。ldLibraryPath动态库路径列表。每一项都应是 Environment 包内的相对路径,例如lib、lib64。执行时会被拼接到 Environment 安装目录下,再追加到LD_LIBRARY_PATH。env环境变量表。它会被注入到 Plugin 执行和 Shell 执行环境中。当前值按字面量使用,不会自动把./lib、$ENV_ROOT这类路径转换成 Environment 包的实际安装路径。
注入链路
当前 Environment 包的主路径,可以先按下面这条链理解:
- 先通过
SAF(Storage Access Framework)选择 ZIP 文件。 - Rootless Store 在 ZIP 中查找
EnvironmentManifest.json。 - 如果识别为 Environment 包,就把内容解压到应用内部的
Environment/{environmentPackageName}目录。 - 安装完成后,系统会尝试对
entryPoint指向的目标设置可执行权限。 - 用户在 Plugin 页面里的
Environment标签中启用这个 Environment。 - 当 Plugin 或 Shell 开始执行时,Rootless Store 会收集所有已启用的 Environment,并注入 运行时PATH 等信息。
可以把它理解成这样一条链:
SAF 选文件
-> 读取 EnvironmentManifest.json
-> 解压到 App Internal Storage / Environment
-> 设置 entryPoint 可执行权限
-> 用户启用 Environment
-> Plugin / Shell 执行时注入 PATH、LD_LIBRARY_PATH 和 env这里最关键的是:Environment 包不是独立业务入口,而是执行环境提供者。
当前注入行为可以先这样理解:
PATH会追加所有已启用 Environment 的包目录。LD_LIBRARY_PATH会根据ldLibraryPath中声明的相对路径,拼接到对应 Environment 包目录下。env会把 manifest 中的键值对注入到执行环境中。
因此,如果你希望插件可以直接调用 python3,最稳妥的方式是让 python3 或一个同名 wrapper 位于 Environment 包根目录。
如果把二进制文件放在 bin/python3,当前系统只会按 entryPoint 对它设置可执行权限,但不会自动把 bin 加进 PATH。
Environment 包的组成
当前 Rootless Store 的 Environment 包格式,默认也是一个 ZIP 文件。
一个标准 Environment 包内部,至少应包含下面两类内容:
一个
EnvironmentManifest.json用于描述 Environment 的版本、入口、宿主要求、动态库路径和环境变量。一组运行时文件 例如:
- 一个
python3可执行文件 - 一个
busybox可执行文件 - 一个
node可执行文件 - 一组
lib动态库 - 一组脚本、标准库或工具链文件
- 一个
推荐目录结构可以用 ASCII tree 表示成这样:
python-environment.zip
├── EnvironmentManifest.json
├── python3
├── lib
│ └── libpython3.14.so
└── python-stdlib
└── ...如果提供的是 BusyBox 或单文件工具链,也可以更简单:
busybox-environment.zip
├── EnvironmentManifest.json
└── busybox如果确实需要把主程序放进子目录,也可以这样组织:
python-environment.zip
├── EnvironmentManifest.json
├── bin
│ └── python3
└── lib
└── libpython3.14.so但在当前实现下,PATH 默认加入的是 Environment 包根目录,不是 bin 子目录。
所以这种结构更适合由包根目录的 wrapper 转发到 bin/python3,或者等待后续更细粒度的路径声明能力。
Plugin 如何使用 Environment
Plugin 不需要把 Python、BusyBox 或其他大型运行时重复打进自己的插件包。
更合理的方式是:
- Environment 包负责提供运行时能力。
- 用户在 Rootless Store 中安装并启用 Environment。
- Plugin 的
entryPoint只调用这些运行时能力。
例如某个 Plugin 的 index.sh 可以这样写:
#!/system/bin/sh
python3 main.py前提是已经启用了一个可以在 PATH 中提供 python3 的 Environment 包。
这种分工能减少重复打包,也能让多个 Plugin 复用同一套运行时环境。
目前的局限性
1. Environment 选择还不是按插件精确绑定
当前执行时会收集所有已启用的 Environment,并统一注入到 Plugin 和 Shell 执行环境中。
这意味着现阶段更接近“全局启用环境”,而不是“某个 Plugin 精确绑定某个 Environment”。
后续更理想的方向是:
- Plugin 声明自己需要哪些 Environment。
- Rootless Store 根据声明检查是否已安装、已启用。
- 执行时只注入真正需要的 Environment。
2. PATH 注入粒度仍然比较粗
当前 PATH 注入的是 Environment 包目录本身。
也就是说,如果希望用户或 Plugin 能直接调用某个命令,最好把可执行文件或 wrapper 放在 Environment 包根目录。
如果运行时文件放在 bin、usr/bin 等子目录,当前不会自动把这些目录加入 PATH。
3. 可执行权限还不是全量递归处理
当前系统主要会尝试对 entryPoint 指向的目标设置可执行权限。
如果 Environment 包中包含多个 ELF、多个脚本或复杂调用链,仍然可能需要额外处理执行权限。
一个可用的 Python Environment 包
为了帮助开发者更快理解 Rootless Store Environment 包的实际结构,我们提供一个可用的 Python 3 Environment 包。
这个包内部包含:
EnvironmentManifest.jsonpython3libincludeLICENSE.txt
它的 EnvironmentManifest.json 声明了:
environmentRenderingName:Python 3environmentPackageName:Python3entryPoint:python3ldLibraryPath:["lib"]requiredEnvironment:ADB
安装并启用后,Plugin 或 Shell 执行时可以通过当前 Environment 注入链路调用 python3。
下载并使用这个 Python Environment 包,即视为你同意遵守包内 LICENSE.txt 中声明的 CC BY-NC-SA 4.0 协议。
除测试用途外,不得将该 Python Environment 包用于任何其他用途,包括但不限于:
- 二次分发
- 商业用途
- 作为正式 Environment 资源直接发布
- 改造后作为其他项目的默认运行时环境使用
