做了这么多年全志平台从A20一路做到T527我必须说一句T527这颗芯片本身没什么大脾气真正折磨人的是它配套的 Android 13 工程编译流程。网上资料散的散、旧的旧照着做的人多半会在 JDK 版本、磁盘空间、ninja 崩溃这些地方卡上几天。这篇东西我不讲虚的就把我在 Ubuntu 上从零编译 T527_Android13 的完整过程、踩过的坑和最终验证过的方案写出来给准备在这个平台上做系统定制、Framework 开发或者 BSP 移植的工程师当个参考。先说清楚这篇博文的适用范围。它适合三类人第一次接触全志 T527 的驱动工程师、要做 Android 13 SystemUI 或上层定制的应用框架工程师以及买不到靠谱技术支持、只能自己折腾的硬件产品团队。需要的基础是熟悉 Linux 基本命令、知道 repo 和 make 大概是怎么回事。如果你从来没编译过 AOSP建议先把 Android 官方文档里的 AOSP 环境搭建那一节翻一遍再回来看这篇会顺很多。1. 编译前必须搞清楚的环境与选型1.1 T527 平台和 Android 13 SDK 的大致关系全志 T527 是面向 AIoT 和边缘计算场景的八核 A55 芯片带 2TOPS NPU支持 Android、Linux、RTOS 多系统。厂商提供的 Android 13 SDK 并不是纯粹的谷歌 AOSP 主线而是在 AOSP 13 的基础上叠加了全志自家的硬件抽象层、电源管理、显示框架、多媒体编解码、NPU 运行时等大量客制化代码。这意味着你在编译时拿到的其实是一个“全志把 AOSP 拉下来改了一版”的巨型仓库而不是几个零散的 git 项目。明白这一点很重要因为很多人第一次编译失败就是把它当成标准 AOSP 来搞动不动就去 AOSP 官方查命令、查环境要求结果发现对不上。全志的 SDK 有自己固定的仓库组、固定的分支名和固定的编译入口你在 device 目录下看到的方案名比如 t527、t527_xxx才是指路的灯塔。先搞清楚自己手里的 SDK 是从哪来的、分支名是什么能省掉后面一大半的弯路。1.2 编译主机硬件建议T527_Android13 的源码大小和编译产物体积比很多人预想的要夸张得多。我给个我自己实测的参考源码仓库全量同步下来大约 120GB 到 160GB取决于 git 历史数据编译过程中生成的 out 目录在第一次全编后会逼近 150GB 到 200GB。如果加上 ccache、中间备份、固件打包产物一台编译服务器上给它划出 500GB 的可用空间是最稳妥的。很多人在这一步就开始踩坑磁盘不够导致链接阶段报错找半天问题根源其实只是 df -h 看一眼就明白的事。内存方面我的建议是 32GB 起步64GB 更好。Android 13 的编译系统比 10 以前的老版本更吃内存因为新的 Soong 构建系统和 link step 会起大量并行进程。我在 16GB 内存的机器上试过ninja 跑到一半被 OOM killer 杀掉是家常便饭。如果你实在只有 16GB那就只能在 make 的时候把并行数压到 -j4 甚至 -j2时间会变得非常难熬一次全编可能七八个小时起步。CPU 核心数自然是越多越好但要注意一个性价比点超过 32 核以后磁盘 IO 和内存带宽会成为瓶颈继续加核心数对编译时间的提升就很不明显了。操作系统我推荐使用 Ubuntu 20.04 或者 22.04 的 64 位版本尽量不要用 Fedora、Arch、Debian 测试版之类的“先进”发行版。原因不是它们跑不了编译而是 Android 的工具链对 glibc 版本非常敏感很多 prebuilt 工具是在特定 glibc 版本上编译的系统太新或太旧都可能出现“invalid ELF header”或者段错误。全志官方文档一般也会指定 Ubuntu 版本听着像废话但这是无数人用血泪换来的教训。1.3 系统版本与依赖软件的版本坑很多教程开篇就是 apt install 一大串但没有解释到底装什么、为什么。Android 13 的编译依赖其实分成两类一类是编译本身要的包如 git、gnupg、flex、bison、build-essential、zip、curl、libncurses5-dev 等另一类是系统镜像打包和烧录工具如 python3、ssh、repo、fastboot 等。通常你会需要类似这么一串sudo apt update sudo apt install -y git-core gnupg flex bison build-essential zip curl zlib1g-dev libc6-dev-i386 libncurses5-dev x11proto-core-dev libx11-dev lib32z1-dev libgl1-mesa-dev libxml2-utils xsltproc unzip fontconfig openjdk-17-jdk python3 python3-pip这里最容易被忽略的是JDK 版本。Android 13 对应的 AOSP 编译要求 JDK 17不是 11更不是 8。如果你机器上默认装的是 OpenJDK 11Ubuntu 20.04 的 apt 默认源里就没有 17需要先装 OpenJDK 17 或者手动配置那么编译到中间阶段会出现一堆“Unsupported class file major version”之类的报错。全志 SDK 里其实预置了编译脚本有些脚本会自动检测 JDK 版本并切换到自带的 JDK但如果你绕过了脚本直接跑 make这个坑就很容易踩中。Python 的版本也有讲究。T527 的 SDK 在宿主机上跑的一些工具脚本需要 Python 3.8 或更高版本Ubuntu 20.04 自带的 Python 3.8 够用但如果你的系统是老的 18.04Python 3.6会有部分脚本直接报语法错误。反过来也别手贱去硬刚 Python 3.10某些老脚本在 3.10 上会出现 module 导入路径变化导致的异常。2. 源码结构与第一次同步2.1 repo 同步与分支选择全志的 Android SDK 基本都走 repo 管理同步方式也和 AOSP 差不多。拿到厂商提供的信息后通常是本地建一个目录然后执行mkdir t527_android13 cd t527_android13 repo init -u manifest仓库地址 -b 分支名 repo sync -c -j8这里有个我反复强调的点分支名一定要用厂商 release 出来的稳定分支不要自己 pick 一个说“看起来是最新”的分支。全志的代码仓库里有些分支可能还在内部研发阶段提交历史不稳定同一个文件今天有、明天就删了你辛辛苦苦同步完编译的时候会发现 vendor 目录下的某个 prebuilt 库缺失根本没法用。repo sync 的并行数不需要太大。很多人以为 -j32 同步更快实际上 repo 同步的瓶颈在于网络延迟和 Git 服务器的并发限制而不是本地 CPU。我在实际使用中用 -j8 到 -j12 最稳偶尔网络抖动导致某个仓库同步中断重新执行同一个 repo sync 命令它会断点续传不需要删掉重来。如果出现反复失败的仓库可以先单独把它精简一下repo sync -c -j8 某个失败的仓库路径这个命令只同步指定的那一个仓库速度要快很多。国内网络环境同步大仓库的时候我建议在 repo init 阶段就配置好镜像源。Github 官方源和 AOSP 官方源在某些时段传输效率很低一个 100GB 的仓库同步三五天都不稀奇。配置国内高校或云厂商的 AOSP 镜像速度会有质的提升。这个操作不复杂就是在 repo init 的时候给 manifest 仓库换一个镜像地址后续的 repo sync 就会自动走镜像。2.2 SDK 里那些“看着没用但删了就废”的目录全志 SDK 在 vendor 目录下有大量闭源 prebuilt 组件这是与其他开源 AOSP 工程最大的不同。你会在 vendor/hardware、vendor/aw、vendor/softwinner 等路径下看到一堆预编译的 .so、二进制工具和 DSP 固件这些东西在编译时会被链接进最终的 system.img、vendor.img。很多第一次碰全志的人有个坏习惯为了省磁盘空间把看似“没用的第三方 so”删掉结果编译到后期报“cannot find -lxxx”或者镜像打包脚本找不到固件。我的建议是在第一次全编译通过之前不要删除任何位于 vendor、device、hardware 目录下的文件。甚至包括一些 README、.txt 说明文件也别动。全志的构建脚本有时候会把整个目录当作资源目录拷贝你少了一个无关紧要的空文件脚本可能都会因为找不到路径而报错。等第一次编译通过、固件能烧录启动了再慢慢清理不迟。2.3 磁盘空间规划前面提过磁盘空间至少留 500GB但这个规划不能只做一个分区。我给的建议是系统盘留 100GB 以上源码和 out 目录放在独立的大分区下。这里有一个 Linux 新手很容易忽略的问题/home 和 / 如果不在同一个分区那么 home 目录下的空间和根目录空间是分开计算的。默认安装 Ubuntu 时如果选了“整个磁盘自动分区”往往只有一个根分区不容易出事但如果手动分区就得留意源码目录到底挂在哪个挂载点下。编译 Android 是很典型的“重写频繁”场景建议把源码放在 SSD 上。机械硬盘在大量小文件编译时 IO 会卡到怀疑人生T527 全编一次在机械盘上多花两三个小时非常正常。如果预算允许用 NVMe 固态最好。另外给 out 目录和 ccache 桶留出足够余量否则编到一半磁盘满ninja 进程退出你之前的几个小时全白费。3. 环境变量、JDK/Python 与实际编译流程3.1 JDK 17 与 Python 版本的坑全志 SDK 虽然会自动检测但为了保险我会在编译前手动清楚指定一次。如果系统里存在多个 JDK 版本可以用 update-alternatives 切换sudo update-alternatives --config java选到 OpenJDK 17 之后确认一下当前 java 版本java -version输出里应该是 openjdk version “17.x.x”。如果还是 11别急着编译先用这个命令排查是不是 JAVA_HOME 环境变量把路径指向了旧版本。AOSP 的构建脚本对 JAVA_HOME 的判断并不完全可靠有时候你改了 java 命令的 alternativesJAVA_HOME 还指在旧路径上编译依然会炸。Python 那边确认默认 python3 指向的是 3.8 或 3.9。全志的许多工具脚本直接在 shebang 里写的是 python3如果你的系统只有一个 python3.6很可能会报语法错误。别尝试在系统层面随便改 python3 的软链接因为 Ubuntu 底层的 apt 也依赖 Python改乱了系统都起不来。正确的做法是在编译前设置 PATH把你要用的 Python 3.8 路径放到最前面或者用虚拟环境。3.2 build/envsetup 和 lunch 的完整流程回到工程目录标准流程如下source build/envsetup.sh lunchlunch 会列出一堆可用的 product 配置这些配置定义在 device/softwinner/t527 目录下的 AndroidProducts.mk 里。T527 一般会有几个典型选项比如 full_t527-eng、full_t527-userdebug。首次开发和调试阶段建议选 eng 版本因为它会开放 adb root、关闭一些安全限制更适合抓 log。如果是做最终量产固件用 userdebug 或者 user 版本。user 版本会有严格的权限限制蓝牙、WiFi、RIL 等功能的调试会很麻烦开发初期不要碰。lunch 之后如果你不确定当前选择的配置可以用 echo $TARGET_PRODUCT 和 echo $TARGET_BUILD_VARIANT 看一下。确定无误后直接开始编译make -j16 21 | tee build.log这里的 -j16 是根据机器配置设定的。我常用的计算方式是内存 64GB 的机器核数 16 到 24 之间都行32GB 内存的机器老老实实 -j1216GB 内存的机器-j4 到 -j6再多就是赌命。21 | tee build.log 这一步是我强烈建议保留的习惯。编译输出几千行终端滚动条根本看不过来把完整日志写到文件里出问题之后 grep 定位要方便得多。比如grep -n error: build.log | tail -50这条命令能快速拉出最后一批编译错误。ninja 在报错时会显示失败的目标文件路径和依赖关系照着定位通常比在终端翻历史记录高效十倍。3.3 make 并行数与日志记录继续展开一下并行数的问题。很多人觉得 -j 越大越好实际上 Android 13 的构建系统在 make 之后内部会切换成 ninjaninja 自己有一套并行度控制机制。如果你在 make 命令里给的 -j 太大内存会瞬间爆掉OOM killer 会把正在跑高内存的 ninja 子进程杀掉。典型的表现是编译进行到 30% 左右终端突然出现一堆“Killed”字样build.log 最后几行是 ninja: build stopped: killed by signal 或者直接没有输出。如果你已经开了 log 记录就能很清楚地看到 dead 之前最后一个被编译的模块是什么。这时候不用怀疑代码有问题先查一下 free -m看是不是内存被榨干了。我见过有人在这种“Killed”报错之后反复清理 out 目录重新编译折腾了两天最后发现只是 make 命令少换了一个更小的 -j 数字非常冤枉。ccache 是另一个值得一开始就配好的东西。在编译 T527 这种大型 SDK 的时候ccache 的作用非常明显尤其是你修改了 Framework 层或者 kernel 的少量文件后做增量编译命中率高的场景下能省掉接近一半时间。配置方法export USE_CCACHE1 export CCACHE_EXEC/usr/bin/ccache ccache -M 50Gccache 的缓存大小我建议设置 50GB 到 100GB。太小的话几次增量编译就把它撑满旧缓存被淘汰命中率直线下降太大则会占用大量磁盘空间和 out 目录抢地盘。首次全编时 ccache 会写入缓存速度会比不带 ccache 略慢一点但后面的增量编译会让这部分时间完全回本。4. 高频报错与排查思路实战速查这一节我直接按错误类型来整理每一条都是我在 T527 和相近的全志平台上实际遇到过的不是从网上抄来的理论。4.1 编译期崩溃类错误Out of memory / ninja: build stopped这个上面提过先查内存和 swap再考虑减小并行数。不要一上来就清理 out 重编查日志判断是哪个模块导致的峰值内存。另外如果你开了太多终端、跑着 IDE、还挂着一个 Docker 容器这些都会吃掉内存编译时尽量把这些都关掉。Unsupported class file major version基本可以断定是 JDK 版本不对。这个报错常见于编译 Java 相关的 Framework 模块时用 java -version 检查当前 JDK。另外注意 Android 13 的 build 系统在部分模块上要求的是 javac 17如果你通过某种方式把 JAVA_HOME 指到了 Android Studio 自带的 JBR 11 路径也会报这个错。/bin/bash^M: bad interpreter: No such file or directory脚本文件是 Windows 换行符 CRLF。这个经常出现在从 Windows 共享文件夹拷贝预编译脚本、补丁文件之后。用 dos2unix 转一下对应文件即可。我一般会整体扫描一遍自己拷贝进去的脚本目录find ./ -name *.sh -exec dos2unix {} \;不会影响正常的 Linux 换行符文件只会把 CRLF 转成 LF。DTC: Error: xxx.dts: yyyDTS 设备树编译错误。如果改过设备树八成是你写错了节点格式、引用了一个不存在的节点或者 include 的文件路径不对。如果没改过 DTS 就报这个错大概率是 kernel 仓库里的 dts 相关仓库没有同步完整检查一下 kernel 仓库的 git status确认是否有文件缺失。4.2 链接与第三方库方面的错误cannot find -lxxx这个 xxx 是某个库的名字比如 -lc、-lhardware。-l参数对应的库文件其实在源码里能找到但链接器搜索的路径不对或者对应的 prebuilt 库根本没有被编译出来。在 T527 上它常常出现在你修改了某个模块的 Android.bp却忘了检查它依赖的 shared library 是否在编译列表中。排查思路是先全库搜索这个库名字确认源文件或者 .so 在不在然后看 build.log 里这个模块到底编没编出来。ninja: error: ‘xxx’, needed by ‘yyy’, missing and no known rule to make it这是 Android 13 增量编译时最容易遇到的报错比全编还常出。原因是你的增量改动让某个产物失效但 ninja 找不到重新生成它的规则。最常见的触发场景是删除了某个模块的源文件或者改了 Android.bp 里的模块名、路径却没有清理旧的中间产物。解决办法rm -rf out/target/product/t527/obj/对应模块名_intermediates删完再重新 make 这个模块ninja 会重新生成构建规则。FAILED: out/target/product/t527/...img镜像打包阶段失败。先看具体是哪一步挂掉可能是磁盘空间不足、可能是有某个预编译文件路径缺失、也可能是打包脚本依赖的工具如 mkbootimg、ext4 工具没有安装。如果日志里提示找不到某个 .sh 脚本基本可以判断是你动了 vendor 目录下的文件。4.3 改了东西不生效的坑改完 SystemUI 重新 make发现系统里还是旧 UI这是顶层定制工程师问得最多的问题。原因通常是你只编了 SystemUI 模块却没有重新打包 system.img或者打包了但 adb push 时没有处理 odex/vdex。Android 13 默认会对 Framework 和 SystemUI 做 dex 预优化直接替换 APK 有时会启动时校验失败系统退回旧版本。正确做法详见第 5 章。改完 kernel 设备树烧录后启动 log 显示还是旧参数设备树不是打包在 boot.img 里的T527 这种平台一般会把 dtb 单独分区或者打进 boot.img 的 resource 区域。改完 DTS 后光重编内核不行还得重新打包 boot.img或者 boot0/uboot 相关分区。我建议每次改完设备树都执行一次make bootimage -j8这样确保新的 dtb 被打进 boot 镜像里。如果还是没生效检查打包脚本里用的是不是你修改的那个 dts 文件全志的 product 配置里有 device 宏它决定了最终使用哪个 dts。5. 典型定制场景SystemUI 定制与 RIL 替换T527 的很多项目不是原样出货而是要做深度定制。这里我把最常见的两个场景单独拎出来写因为它们的坑最集中。5.1 SystemUI 定制编译的正确打开方式T527 用的 Android 13 版本中SystemUI 的路径和标准 AOSP 一致在 frameworks/base/packages/SystemUI。很多人第一次改完代码直接 make SystemUI然后 adb push 过去发现系统直接黑屏或者 SystemUI 不停崩溃Logcat 里一堆 SecurityException。正确的增量编译流程是source build/envsetup.sh lunch full_t527-eng make SystemUI -j8编完之后不要直接 push APKAndroid 13 的 Framework 进程会做系统签名校验和权限保护。先把新编译的 SystemUI 输出到本地目录再通过 adb root adb remount 的方式把 APK 推送到 /system_ext/priv-app/SystemUI/具体路径取决于你的 product 配置同时推送对应生成的 odex/vdex。这一步最容易出错的是编译产物不匹配导致 dex2oat 失败。我个人更推荐的方式是直接重新打包系统镜像make snod这个命令会跳过重新编译直接用已有的模块产物重新打包 system.img。前提是你已经用 make SystemUI 把 SystemUI 模块编译出来了。然后烧录或者 adb push 整个 system.img 分区虽然比单文件 push 慢但能避免 odex 乱七八糟的问题适合验证大的改动。平时做 Framework 层修改时我还有个习惯把开发板的 adb root 打开用 adb sync 做增量同步。它对比本地 out 产物和设备的 system 分区只推送变化的部分速度比整包 push 快很多。不过在 T527 上你要先确认自己的 userdebug 版本没有关闭 adb sync 所需的一些权限。定制 SystemUI 时另一个高发问题资源编译冲突。你改了 strings.xml 或者 overlay 资源编译时报资源重复定义或者找不到资源。这时候先把对应模块的 intermediates 目录删掉再重新 makerm -rf out/target/product/t527/obj/APPS/SystemUI_intermediates这个目录缓存的编译状态经常会在资源更新后处于一个不干净的中间状态让它重新编一遍就好。别没事就 make clean那会把整个 out 目录都干掉下次编译等于全编太浪费时间。5.2 移远 libquectel-ril 接入 T527 的实战记录很多 T527 方案在网联类产品里会配移远Quectel的 4G/5G 模组比如 EC20、RM500Q 等。全志 SDK 自带的 RIL 通常是 reference-ril 或者全志自己的 lite-ril直接接移远模组时会出现开机无法识别 SIM 卡、打电话没声音这类问题。这时候通常需要编译移远提供的 libquectel-ril 进行替换。这里分享一个具体的接入流程。移远会提供一份包含源码或 prebuilt 的 ril 库包里面一般有 libquectel-ril.so 和对应的 Android.bp。你需要把它放到某个 vendor 目录比如 vendor/quectel/ril/下然后在 PRODUCT_PACKAGES 里加上这个模块同时把系统属性里 RIL 库路径指过去。关键的属性是rild.libpath/vendor/lib64/libquectel-ril.soT527 是 64 位系统注意是 lib64 而不是 lib。改完属性之后还要处理 SELinux 权限问题。全志的 userdebug 版本默认 enforce 可能开着RIL 进程起来后访问串口设备或网络接口会被 SELinux 拦截logcat 里出现 avc: denied 的报错。临时验证时可以先用 permissive 模式跑一遍确认功能正常后再补 sepolicy 规则。具体来说就是看 dmesg 和 logcat 里的 avc 信息对应到 sepolicy 的 te 文件里补 allow 规则。接入之后验证做得对不对最直接的方式是adb shell getprop | grep rild adb shell ps -A | grep rild再看 logcat -b radio 的日志。如果 rild 一直在重启说明要么库路径不对要么 SELinux 拦截顺着日志查就行。这里有个容易忽略的小细节替换 RIL 库之后最好清一下 /data 分区里的老 RIL 缓存数据否则有时候 Modem 状态同步会有诡异的问题。开发阶段可以直接 adb shell wipe data 或者 fastboot -w一了百了。6. 编译产出、镜像打包与烧录验证6.1 编译产物在哪全编成功之后主要产物都在 out/target/product/t527/ 目录下。你需要的核心东西是这些boot.img内核 设备树 ramdisk负责系统启动第一段。system.imgAndroid Framework、系统应用、系统库。vendor.img厂商私有库、HAL、不开源组件。全志的很多硬件能力都在这里。dtbo.img设备树 overlay某些平台会单独存放多个 DTB。vbmeta.img、boot0.img、uboot.img 这些是启动链相关的东西烧录时要留意对应关系。不同方案的命名可能略有不同但大同小异。全志 SDK 一般会提供一个打包脚本或者 mkimage 工具把 out 目录下这些零散镜像打包成可以直接烧录的整包固件。脚本通常在 device/config 目录下或者 SDK 根目录。6.2 打包与烧录看脚本内容之前先检查一个常见问题打包脚本里写的路径是不是和你实际输出的方案名一致。之前见过有人 lunch 选了 full_t527-eng但打包脚本默认指向另一个具体子配置比如 t527_xxx-eng结果每次打包出来的镜像都是空的或者旧的。这种坑很隐蔽因为脚本不报错它只是打包了另一个目录下的空产物。烧录这块全志官方常用的烧录工具是 PhoneixSuit 或者烧写卡工具支持 USB 烧录和 TF 卡量产烧录。第一次开发调试阶段我建议直接用 fastboot 分区烧录速度和灵活性都更好adb reboot bootloader fastboot flash boot out/target/product/t527/boot.img fastboot flash system out/target/product/t527/system.img fastboot flash vendor out/target/product/t527/vendor.img fastboot reboot如果你改动了设备树、要验证内核修改boot 分区一定要烧。如果只是 Framework 层改动烧 system 分区就够。速度上比整包烧录快得多也方便反复调试。整套流程走通一次之后你会发现 T527_Android13 编译这件事本身并不复杂真正致命的是那些“带节奏”的细节JDK 版本、磁盘余量、并行数、资源清理。我个人的体会是Android 13 这套构建系统的容错率比老版本低了不少一旦环境不对报错方式千奇百怪很容易把你往错误的方向带。所以每次开工之前我都习惯先花十分钟检查环境把磁盘、内存、JDK、分支状态都确认一遍再开始编译。这十分钟看起来是浪费时间实际上能避免你在错误环境里空转一整天。最后补一句实战经验第一次全编之前一定要先确认自己的 repo 仓库是完全干净的用 git status 在各个仓库里扫一遍确认没有本地修改残留尤其是 device、hardware、vendor 这几个目录。全志的构建系统在某些步骤会直接对源文件做 patch 或者生成临时文件如果你带着半年前的本地补丁去全编失败之后排查问题的难度会成倍上升。等你能稳定通过第一次全编后面的各种定制和优化就只是时间问题。