18、AOSP中的Python脚本:Soong的Python插件、编译辅助脚本、构建系统工具链
说到AOSP的构建系统,大家第一反应肯定是Soong和Blueprint。但说实话,Python在AOSP里的地位远比很多人想象的要重要。我刚开始接触AOSP时,以为Python只是些零散的辅助工具,后来深入才发现——Python脚本几乎贯穿了整个构建流程的方方面面。
这一章,我们就来聊聊AOSP里的Python生态。我会从三个维度展开:Soong的Python插件机制、那些藏在角落里的编译辅助脚本、以及构建系统工具链中的Python身影。
18.1 Soong的Python插件:不只是“胶水”
Soong本身是用Go写的,但它的插件系统允许我们用Python来扩展构建逻辑。为什么会这样?因为有些场景下,Go的静态类型和编译特性反而成了掣肘——比如需要动态解析配置文件、或者跟外部工具交互时。
核心概念:Soong的Python插件本质上是一个“桥接层”。Soong在构建过程中会调用Python解释器,执行特定的插件脚本,并将结果回传给构建系统。
我记得第一次在项目中用Python插件,是为了处理一个自定义的镜像打包逻辑。当时需要根据设备型号动态生成分区表,用Go写的话要重新编译Soong,太麻烦了。用Python插件,改个脚本就行。
来看一个典型的插件结构:
# external/soong/plugins/my_plugin.py
def get_partition_table(device_variant):
"""根据设备变体返回分区表"""
tables = {
'phone': ['boot', 'system', 'vendor', 'userdata'],
'tablet': ['boot', 'system', 'vendor', 'userdata', 'media'],
'tv': ['boot', 'system', 'vendor', 'cache']
}
return tables.get(device_variant, tables['phone'])
def on_soong_load(ctx):
"""Soong加载时自动调用"""
variant = ctx.Config('TARGET_DEVICE')
partitions = get_partition_table(variant)
ctx.SetGlobal('PARTITION_LIST', partitions)
print(f"[Plugin] Loaded partitions for {variant}: {partitions}")
我的习惯:插件脚本里尽量少写复杂逻辑。Python插件只是“胶水”,真正的业务逻辑应该放在独立的Python模块里,插件只负责调用和传参。这样调试起来方便很多。
18.2 编译辅助脚本:那些“看不见”的功臣
AOSP里散落着大量Python脚本,它们不直接参与编译,但少了它们,整个构建流程会寸步难行。我粗略统计过,build/目录下就有超过200个Python脚本。
我把它们分成几类:
| 类别 | 典型脚本 | 作用 |
|---|---|---|
| 环境检测 | checkenv.py | 检查JDK、Python版本、磁盘空间等 |
| 配置生成 | genconfig.py | 根据BoardConfig生成编译配置 |
| 依赖分析 | depcheck.py | 检查模块间的依赖关系是否完整 |
| 日志处理 | logcat_parser.py | 解析编译日志,提取错误信息 |
| 镜像工具 | img_from_target.py | 从编译产物生成刷机镜像 |
这里我想重点聊聊checkenv.py。我曾经在一个新人的机器上遇到过编译到一半报错,原因是Python版本是3.11,但AOSP某个脚本只兼容到3.10。这种问题最坑了——编译跑了半小时才暴露出来。
避坑指南:我曾经因为没仔细看checkenv.py的输出,直接跳过了环境检查。结果编译到90%时崩溃,排查了整整一天才发现是OpenJDK版本不对。现在我的习惯是:每次编译前,先跑一遍source build/envsetup.sh,它会自动调用这些检测脚本。
18.3 构建系统工具链:Python的“隐藏技能”
除了Soong插件和辅助脚本,Python在AOSP构建工具链中还扮演着更底层的角色。比如:
- Ninja文件生成器:Soong生成的Ninja文件里,有一部分构建规则是用Python脚本动态生成的
- 符号处理工具:
symbols.py负责处理动态库的符号表,用于调试和性能分析 - 增量编译优化:
incremental.py分析文件变更,决定哪些模块需要重新编译
嗯,这里要特别提一下增量编译。AOSP的增量编译一直是个老大难问题——全编译一次要两三个小时,谁受得了?Python脚本在这里起到了关键作用。
来看一个简化版的增量编译判断逻辑:
# build/soong/incremental_check.py
def should_rebuild(module_path, last_build_time):
"""判断模块是否需要重新编译"""
import os
from pathlib import Path
# 检查模块目录下所有源文件的时间戳
module_dir = Path(module_path)
for file_path in module_dir.rglob('*'):
if file_path.is_file():
mtime = os.path.getmtime(file_path)
if mtime > last_build_time:
return True
return False
def get_affected_modules(changed_files):
"""根据变更文件,找出受影响的模块"""
affected = set()
for f in changed_files:
# 解析文件路径,找到所属模块
module = resolve_module_from_path(f)
if module:
affected.add(module)
# 还要考虑依赖传递
affected.update(get_dependent_modules(module))
return list(affected)
你想想看,如果没有这些Python脚本,每次编译都要全量扫描所有文件,那效率得多低?
18.4 实战:写一个自己的编译辅助脚本
光说不练假把式。我建议你动手写一个简单的脚本,用来统计编译产物的大小分布。这个需求我在项目中遇到过好几次——产品经理总问“为什么system分区又变大了?”
#!/usr/bin/env python3
# tools/analyze_out.py
import os
import sys
from collections import defaultdict
def analyze_out_dir(out_path):
"""分析out目录下的文件大小分布"""
size_map = defaultdict(int)
file_count = 0
for root, dirs, files in os.walk(out_path):
for f in files:
file_path = os.path.join(root, f)
try:
size = os.path.getsize(file_path)
# 按文件类型分类
ext = os.path.splitext(f)[1] or 'no_ext'
size_map[ext] += size
file_count += 1
except OSError:
continue
print(f"Total files: {file_count}")
print(f"{'Extension':<15} {'Size (MB)':>15} {'Percentage':>15}")
print("-" * 45)
total_size = sum(size_map.values())
for ext, size in sorted(size_map.items(), key=lambda x: -x[1]):
mb = size / (1024 * 1024)
pct = (size / total_size) * 100
print(f"{ext:<15} {mb:>15.2f} {pct:>14.2f}%")
if __name__ == '__main__':
if len(sys.argv) != 2:
print("Usage: analyze_out.py <out_dir>")
sys.exit(1)
analyze_out_dir(sys.argv[1])
我的建议:把这个脚本放到build/tools/目录下,然后加到你的PATH里。以后每次编译完,跑一下就能直观看到哪些文件占用了空间。我曾经靠这个脚本发现了一个重复打包的so库,直接省了50MB的系统分区空间。
18.5 知识体系总览
说了这么多,我们来用一张图把整个知识体系串起来。这张图展示了Python脚本在AOSP构建系统中的位置和关系:
从这张图可以看得很清楚:Python脚本在AOSP构建系统中不是孤立存在的,它上接Soong核心,下连各种具体任务,中间还穿插着环境检测、配置生成等辅助功能。说白了,Python就是整个构建系统的“神经网络”——虽然不显眼,但少了它,整个系统就动不了。
我个人觉得,理解Python在AOSP中的角色,比死记硬背那些API更重要。因为当你真正理解了它的定位,遇到问题时自然知道该去哪里找答案——是写个插件?还是改个辅助脚本?还是直接动工具链?
嗯,这一章的内容就到这里。记住:Python脚本在AOSP里不是配角,而是真正的幕后英雄。