简介面向安卓底层开发与嵌入式工程师的MT6799开发板源码编译实战笔记聚焦Ubuntu 16.04环境下安卓7.0Nougat兼顾4.4的下载与编译流程。资源为单份Word文档体积约895KB内容涵盖虚拟机环境搭建、软件源替换、共享文件夹配置、依赖工具安装、推荐编译指令及高频报错排查结构清晰可按步骤对照操作。已有633人浏览学习。文档不仅给出从源码解压到执行编译的完整操作链路还针对平台变量未定义、构建子任务失败、内存不足三类编译报错提供具体原因与解决思路可帮助初学者避开环境配置和内存不足等常见坑点。文中对备份源列表、添加阿里云源与更新源等命令示例对共享目录建立、内存和处理器核心数的配置都有说明能让刚接触MT6799平台的开发者更快搭建可用的编译环境。1. MT6799 Android 源码编译最容易被环境拖死的两件事MT6799 开发板的 Android Source code 下载和编译是所有拿到这套板子的人绕不开的第一步。Android Nougat 7.0 源码在 Ubuntu 16.04.5 虚拟机里编环境对了就只是时间问题环境不对就是连环报错TAREGT_BRM_PLATFORM is undefined、ninja_wrapper failed、fatal: fork: Cannot allocate memory随便一个都能卡住半天。我这次重新装 VMware 15.5把 16G 内存、2 处理器 2 核心、300G 硬盘的虚拟机从头配了一遍最后能稳定编出 systemimage。这篇文章按当时实际操作顺序记录重点交代哪些参数必须手动改哪些报错可以暂时忽略适合正在折腾 MT6799 编译环境、又不太愿意把源码解压三遍的人。2. 虚拟机环境软件源、共享文件夹和 VMware 15.5 的选型如果只是拿现成镜像虚拟机配置可能不用太讲究。但 MT6799 的 Android Nougat 7.0 源码解压后接近几十 G编译输出还要占一份空间随便给个 8G 内存的小虚拟机make 跑到一半就会被 Linux 的 OOM 机制杀掉。这一章先把宿主机侧和虚拟机侧的环境固定下来Ubuntu 16.04.5 桌面版、VMware 15.5、内存 16G、处理器 2 核 2 线程、硬盘 300G。下面的配置顺序是我反复试过比较顺畅的每一步都有目的。2.1 修改 Ubuntu 软件源阿里源这样配最省事Ubuntu 16.04 默认的源在国外apt-get update 经常等半天装依赖的时候很容易超时。换源这个操作看起来简单最容易翻车的是把新源追加到旧源后面造成重复或者把 xenial 写成 bionic。我的做法是先把原始 sources.list 备份再打开编辑sudo cp /etc/apt/sources.list /etc/apt/sources.list.backup sudo gedit /etc/apt/sources.list备份路径放在同目录下后缀 .backup以后想恢复只要把备份文件覆盖回去。gedit 是 Ubuntu 16.04 桌面版自带的编辑器如果你用的是服务器版没有图形界面把最后一句换成 vim、nano 都行关键是保证文件以 UTF-8 无 BOM 保存否则 apt 会 warnings。在文件末尾追加阿里云源时我直接用了下面的完整块deb http://mirrors.aliyun.com/ubuntu/ xenial main restricted deb-src http://mirrors.aliyun.com/ubuntu/ xenial main restricted multiverse universe deb http://mirrors.aliyun.com/ubuntu/ xenial-updates main restricted deb-src http://mirrors.aliyun.com/ubuntu/ xenial-updates main restricted multiverse universe deb http://mirrors.aliyun.com/ubuntu/ xenial universe deb http://mirrors.aliyun.com/ubuntu/ xenial-updates universe deb http://mirrors.aliyun.com/ubuntu/ xenial multiverse deb http://mirrors.aliyun.com/ubuntu/ xenial-updates multiverse deb http://mirrors.aliyun.com/ubuntu/ xenial-backports main restricted universe multiverse deb-src http://mirrors.aliyun.com/ubuntu/ xenial-backports main restricted universe multiverse deb http://archive.canonical.com/ubuntu xenial partner deb-src http://archive.canonical.com/ubuntu xenial partner deb http://mirrors.aliyun.com/ubuntu/ xenial-security main restricted deb-src http://mirrors.aliyun.com/ubuntu/ xenial-security main restricted multiverse universe deb http://mirrors.aliyun.com/ubuntu/ xenial-security universe deb http://mirrors.aliyun.com/ubuntu/ xenial-security multiverse这里出现 xenial 是因为 Ubuntu 16.04 的代号就是 xenialupdates 对应软件更新security 对应安全更新backports 是后移包。deb-src 是源码索引Android 编译有些库需要自己拉源码检查时用得到建议别省。写完保存后执行sudo apt-get updateupdate 只是更新索引不需要太长时间。如果看到 “Hit” 和 “Get” 说明源连通正常如果出现 “404 Not Found” 或 “NO_PUBKEY”多半是源列表里有空格或换行问题回头检查文件尾部有没有粘贴时弄乱的注释。2.2 VMware Tools 共享文件夹压缩包必须拷到 Linux 里解压源码包在宿主机 Windows 上虚拟机里要读到它最常规的做法是 VMware 的共享文件夹挂载点一般是 /mnt/hgfs。很多人习惯直接在 /mnt/hgfs 下解压源码这个是血泪教训共享文件夹走的是 vmhgfs 文件系统解压大量小文件时权限和符号链接都会出问题make 到一半会出现莫名其妙的 Permission denied。所以不管源码包多大都要先复制进 Linux 虚拟磁盘再解压。要能使用共享文件夹先装 VMware Tools。安装包一般是 VMware 软件安装目录里的 VMwareTools-10.1.15-6627299.tar.gz在虚拟机里解压tar zxvf VMwareTools-10.1.15-6627299.tar.gz cd vmware-tools-distrib/ ./vmware-install.pl安装过程中会出现 YES/路径提示遇到 YES 就输入 Y 再回车遇到路径直接回车采用默认值。安装完建议重启一次虚拟机然后进入共享文件夹验证cd /mnt/hgfs ls -l注意 /mnt/hgfs 是区分大小写的写成 /mnt/HGFS 会 No such file or directory。共享文件夹在 VMware 菜单需要设置为 “Always enabled”只设为 “Enabled” 的虚拟机在挂起再恢复后有时会丢挂载属于 VMware 15.5 的老毛病。2.3 VMware 15.5 硬件配置内存 16G、处理器 2 核 2 线程怎么设虚拟机创建时我用的镜像是 ubuntu-16.04.5-desktop-amd64.iso安装工具是 VMware-workstation-full-15.5.0-14665864_15.5.0.49504.exe。硬件分配建议固定为一套参数硬盘 300G内存 16G处理器 2 个每个处理器 2 核。配置项建议值说明硬盘300G源码解压 out 目录 依赖200G 以下容易中途撑爆内存16G低于 12G 编译后期会 fatal: fork: Cannot allocate memory处理器2x2给 4 核以上-j4 并行度上去了内存却不够一样翻车处理器这项虚拟机设置里有 “处理器数量” 和 “每个处理器的核心数量”我习惯设成 2 和 2Windows 任务管理器看到的是 4 个逻辑处理器。编译的时候用 -j4这和核心数匹配不容易触发内存峰值。如果你宿主机是 32G 内存可以给虚拟机 20G 并把 -j 调到 6但 MT6799 这份源码我实测 -j4 已经稳定。虚拟机磁盘文件存放位置也要注意。我的电脑只有 500G曾图省事把虚拟机放在 C 盘系统目录结果编译时有些目录没有写入权限连 VMware 自带的碎片整理都跑不了。把虚拟机放到 C 盘普通目录比如 C:\Download 这种没有系统权限保护的地方后续调整磁盘大小、做碎片整理都会顺很多。这个问题不解决后面 make 报错你会怀疑是编译参数实际是宿主机磁盘权限在拖后腿。3. 源码拷贝与依赖安装从 tar.bz2 到 OpenJDK 8虚拟机环境验证通过后接下来就是把 MT6799 的 Android Nougat 7.0 源码包弄进来。源码包通常是按卷拆分的 tar.bz2比如 MT6799_Android_Nougat_7.0.tar.bz2.aa、ab 这种形式不能只解压其中一卷也不能在共享文件夹里直接解压。依赖工具和 JDK 版本错误是后面两个常见报错的根源。3.1 源文件解压和 md5 校验报错先别慌先把共享文件夹里的所有分卷文件复制到 Linux 虚拟磁盘的独立目录比如 ~/android 下。复制完执行cat MT6799_Android_Nougat_7.0.tar.bz2.* | tar jvxf -cat 把分卷按顺序拼起来管道给 tar 做流式解压避免把所有分卷先拼成一个完整包再解压而多占一份磁盘空间。tar 加 j 表示处理 bzip2v 会输出解压的文件名f 指定用后面的 - 从标准输入读取。这个过程比较慢一个几十 G 的源码包解压大约要二十分钟以上期间不要动虚拟机。解压完成后源码根目录下会有一个 md5 校验文件md5sum -c MT6799_Android_Nougat_7.0.tar.bz2.md5这个命令会逐个校验分卷的 MD5。如果报错先别急着重新下载因为只要后面编译能正常跑起来功能没问题个别分卷校验失败往往不影响最终镜像。我自己遇到过 md5 报错但编译完全正常的案例这属于分发文档里比较暧昧的提示宁可把时间花在编译上也不要为了一个校验值把几十 G 重新拉一遍。3.2 编译依赖工具批量安装Ubuntu 16.04 的依赖列表Android Nougat 7.0 的编译工具链依赖不少 32 位库所以不能只装 build-essential。我按文档里列出来的最小集合先装了一遍sudo apt-get install build-essential bc flex unzip zip bison gperf curl zlib1g zlib1g-dev tofrodos libxml2-utils gitbuild-essential 提供 gcc/g/makeflex 和 bison 是词法语法解析器gperf 生成哈希函数bc 用于内核编译时的数值计算。libxml2-utils 提供 xmllintAOSP 编译会用到。zlib1g-dev 不装的话后面解压 host 工具时经常报缺头文件。如果你是在 Ubuntu 16.04 桌面版上从零开始建环境我建议把下面的完整依赖块也执行一遍它把 32 位兼容库一次补齐sudo apt-get install -y git flex bison gperf build-essential libncurses5-dev:i386 sudo apt-get install libx11-dev:i386 libreadline6-dev:i386 libgl1-mesa-dev g-multilib sudo apt-get install tofrodos python-markdown libxml2-utils xsltproc zlib1g-dev:i386 sudo apt-get install dpkg-dev libsdl1.2-dev libesd0-dev sudo apt-get install git-core gnupg flex bison gperf build-essential sudo apt-get install zip curl zlib1g-dev gcc-multilib g-multilib sudo apt-get install libc6-dev-i386 sudo apt-get install lib32ncurses5-dev x11proto-core-dev libx11-dev sudo apt-get install lib32z-dev ccache sudo apt-get install libgl1-mesa-dev libxml2-utils xsltproc unzip m4这里面很多包会重复出现apt 不会重复安装幂等执行没有副作用。带 i386 后缀的是 32 位版本AOSP 的 host 工具很多是 32 位的缺一个就会在编到某个模块时出现 “fatal error: curses.h: No such file or directory” 之类的错。libesd0-dev 是旧版音频守护进程的开发包Nougat 编译偶尔还会引用 esd 头文件。ccache 是编译缓存第二次全量编译会快很多但注意它占磁盘建议在 ~/.bashrc 里设 export USE_CCACHE1 并给个 10G 的缓存上限。3.3 OpenJDK 8用 PPA 安装并切默认版本Android Nougat 7.0 明确要求 JDK 8Ubuntu 16.04 自带源里的 openjdk-8 可能版本旧或者你没装。最省事的是先添加 openjdk 的 PPAsudo add-apt-repository ppa:openjdk-r/ppa sudo apt-get update sudo apt-get install openjdk-8-jdk添加 PPA 后 update 会拉取新的索引安装过程大约两三分钟。装完以后系统里可能同时存在多个 JDK比如 openjdk-7 或者 Oracle JDK需要手动切换默认的 javac 和 javasudo update-alternatives --config javac sudo update-alternatives --config java执行后会列出当前系统所有候选 JDK 版本输入对应的编号回车即可。如果你执行完只看到 “There is only one alternative”说明系统里没有其他 JDK 冲突不用管它文档里那句“出现这样信息代表不用安装”指的就是这个。切换完建议验证一下java -version javac -version必须看到 1.8.0 开头的版本号。如果 java 已经是 8 但 javac 还是老版本说明 alternatives 配置没切全重新执行一次 update-alternatives 选择列表里最新那个 openjdk-8。4. 编译指令从 envsetup 到 make 的完整顺序很多源码编译教程上来就让你 source envsetup.sh lunch make忽略了 jack 服务和 clobber。MT6799 这份源码在 Ubuntu 16.04 上jack 服务不启动或参数不对ninja 报错率极高。这一章按我实际能跑通的顺序来每一步都是下一步的前提。4.1 清理历史输出make clobber 和 jack-admin 服务源码刚解压完理论上 out 目录是空的但还是建议先做一次清理。第一次编译失败后再继续make clobber 能清掉所有中间产物避免脏文件影响后续构建。make clobber ./prebuilts/sdk/tools/jack-admin list-server ./prebuilts/sdk/tools/jack-admin start-server ./jack_install.shmake clobber 等价于删除 out 目录但比手删安全它知道哪些目录是构建产物。jack-admin 是 prebuilts 里管理 Jack 编译服务的工具list-server 查看当前有没有服务在跑start-server 启动服务。jack_install.sh 这一步很多人漏掉它会把 Jack 服务注册到当前工程务必放到你的源码工程根目录下执行。如果你解压后直接跳过它后面报 TAREGT_BRM_PLATFORM is undefined 就不要觉得奇怪。Jack 服务本质上是一个常驻 JVMJava 编译由它代理。如果 list-server 显示的端口不对可以先 stop-server 再 start-server。这里有个细节jack-admin 脚本默认占用 1G 内存后面第 5 章会讲怎么用 -Xmx4096m 把堆调大。4.2 初始化编译环境envsetup.sh 和 lunch target清理完环境后进入源码根目录执行source build/envsetup.sh lunch full_amt6799_evb_n-eng export LC_ALLCsource 是在当前 shell 里加载 envsetup.sh不是直接执行否则环境变量不会保留。lunch 的参数 full_amt6799_evb_n-eng 是 MT6799 EVB 板对应的 product 完整 target最后三段分别是设备名、编译类型。eng 是 engineering 版本带 root 权限和调试工具适合开发userdebug 适合调试但会少一些调试工具user 是正式版不建议首次编译选。lunch 执行后会打印出平台信息和构建类型如果提示 “** Dont have a product spec for full_amt6799_evb_n-eng”说明 device 目录下对应的 vendor 配置没有被 include先去检查 device 相关 mk 文件。export LC_ALLC 是把本地化环境固定为 C避免系统语言和区域设置干扰编译输出。不设的话在中文 locale 下手动编译偶尔会出现 grep 期望和实际输出不一致的诡异问题。4.3 make 镜像systemimage、bootimage、kernel 怎么选lunch 完成后直接全量 make 当然也行但风险是如果环境有问题等几个小时后才发现。所以我一般先单独编一个镜像验证最常用的是 systemimagemake -j4 systemimage如果只想验证内核编译和模块链接可以换成make -j4 bootimage make -j4 kernel make -j4 userdataimage最后再跑make -j4-j4 是并行任务数配 4 个逻辑处理器。千万不要在 16G 内存虚拟机上用 -j8ninja 会瞬间创建大量进程直接触发 fork: Cannot allocate memory。如果你宿主机内存充足可以把 -j 调成 6但需要同步把 Jack 的堆调大。make 的 target 名称也要注意Android 编译系统里 systemimage 才是整包 system 镜像system 不是一个可用的 Make target。第一次跑 systemimage 成功后再逐步扩大范围。target产物常用场景systemimagesystem.img验证整体系统环境是否正常bootimageboot.img验证内核和 ramdisk 是否链接成功kernelkernel 镜像只做内核编译时最快userdataimageuserdata.img验证数据分区镜像不加 target全部镜像最终发布前的全量编译增量编译时不要再随便执行 make clobber否则会把 out 目录清空等于从头来过。我一般只在两类情况下才 make clobber一是 Jack 参数改完之后二是同一份源码之前已经编轴过、确定环境有问题时。其他时候直接 make 对应 targetninja 会自己分析哪些模块需要重建。5. 编译报错排查TAREGT_BRM_PLATFORM、ninja 和内存不足这一章是重点。我装了三遍 MT6799 环境第一遍是在没有 install 完整依赖的情况下直接编译第二遍是 Jack 参数加错位置第三遍才稳定跑通。下面每个坑都按“现象 → 原因 → 解决”写明白这些都是源码编译常见的拦路虎。5.1 报错一TAREGT_BRM_PLATFORM is undefined现象编译刚开始定位到 vendor/amt/libs/hwcomposer.mt6799.1.5.0/Android.mk 时报错信息显示vendor/amt/libs/hwcomposer.mt6799.1.5.0/Android.mk:4: *** TAREGT_BRM_PLATFORM is undefined. Please make sure the device-vendor.mk is correctly included..原因最直接的是 device-vendor.mk 没有被正确 include但更多时候是编译依赖没有装全导致某些模块需要的变量没有定义。另一个常见原因是 jack_install.sh 没有运行或者运行的位置不对。这份源码的编译顺序必须跟 MT6799_Basic_v1.pdf 文档保持一致不按文档做vendor 的 mk 变量就会被跳过。解决先把系统包升级一遍sudo apt-get upgrade然后补装一批编译依赖sudo apt-get install g-multilib gcc-multilib lib32ncurses5-dev lib32readline-gplv2-dev lib32z1-dev bison再把 jack_install.sh 放到源码工程根目录下运行确保它和 device/vendor 的目录结构在同一层级。如果还报同样的错把第 3 章的完整依赖全部执行一遍不要挑着装。这个报错最忌讳的是只搜报错标题然后不停改 vendor 里的 mk其实根源在环境多过在代码。5.2 报错二ninja: build stopped: subcommand failed现象make 运行一段时间后终端最后几行出现build/core/ninja.mk:148: recipe for target ninja_wrapper failed make: *** [ninja_wrapper] Error 1原因Jack server 的 JVM 内存不够或者启动参数里没有显式指定堆上限。AOSP 在 Nougat 时期用 Jack 做 Java 编译默认的 JACK_SERVER_VM_ARGUMENTS 没有设 -Xmx遇到稍大的模块就会 OOM然后 ninja 认为子命令失败。解决修改 prebuilts/sdk/tools/jack-admin 脚本。用 vim 打开vim prebuilts/sdk/tools/jack-admin把原来这行JACK_SERVER_COMMANDjava -XX:MaxJavaStackTraceDepth-1 -Djava.io.tmpdir$TMPDIR$JACK_SERVER_VM_ARGUMENTS -cp $LAUNCHER_JAR $LAUNCHER_NAME改成加上 -Xmx4096mJACK_SERVER_COMMANDjava -XX:MaxJavaStackTraceDepth-1 -Djava.io.tmpdir$TMPDIR$JACK_SERVER_VM_ARGUMENTS -Xmx4096m -cp $LAUNCHER_JAR $LAUNCHER_NAME同时找到 JACK_SERVER_VM_ARGUMENTS 的赋值处在 -XX:TieredCompilation 后面也加上 -Xmx4096mJACK_SERVER_VM_ARGUMENTS${JACK_SERVER_VM_ARGUMENTS:-Dfile.encodingUTF-8 -XX:TieredCompilation -Xmx4096m }注意两处的变量名一个是 -Xmx4096m一个是大写 M 的 -Xmx4096M大小写不一致会造成 JVM 启动参数失效。我之前用小写 m 一直没问题某次更新后又报错改成大写 M 再 make clobber 突然就好了这属于典型的环境玄学。改完以后必须按顺序重启 Jack 服务make clobber ./prebuilts/sdk/tools/jack-admin stop-server ./prebuilts/sdk/tools/jack-admin start-servermake clobber 放在 stop-server 前面清掉旧输出再重启服务缺这一步的话很多时候你以为参数加上了实际上 Jack 复用旧进程改了等于没改。我第一次就是漏掉 make clobber白折腾了一晚上。5.3 报错三fatal: fork: Cannot allocate memory现象编译进行到 70%~80%日志停在[ 78% 48705/61945] target thumb C: .../o/drv/cam/isp_function_cam.pipechk.cpp ninja: fatal: fork: Cannot allocate memory原因这种情况要分两种。一是 Linux 的 pid_max 上限太小编译进程数逼近系统允许的最大 PID 值导致 fork 新进程失败二是虚拟机内存确实不够不是进程数不够。区分方法很简单先看当前进程数是不是快到上限。sysctl kernel.pid_max ps -eLf | wc -l如果 ps 统计出来的线程数接近 pid_max那就是第一种情况。临时调大echo 1000000 /proc/sys/kernel/pid_max要让重启后仍然生效写入 /etc/sysctl.confecho kernel.pid_max1000000 /etc/sysctl.conf sysctl -p如果进程数远没到 pid_max那就是内存不够。我当时查看虚拟机内存实际只给了 6G 左右编译到一半系统几乎没内存可分配于是关闭虚拟机把内存调到 16G问题直接消失。如果宿主机没法加内存也可以创建 swap 应急sudo dd if/dev/zero of/swapfile bs2048 count524288 sudo mkswap /swapfile sudo swapon /swapfile这条命令创建 1G 的 swap 文件bs2048count524288容量大约是 2048*524288 字节即约 1G。ls -lh /swapfile 能看到文件存在。注意 swap 不是万能药如果频繁触发 swap编译速度会明显下降重点是优先把虚拟机内存调够swap 只用于临时绕过 OOM。5.4 其他坑虚拟机磁盘权限和重复源码目录现象编译中途出现无法写入 out 目录或Permission denied并且虚拟机磁盘的碎片整理功能不可用。另一类现象是 JDK tool 报错定位到源码根目录下 prebuilts 里的工具冲突。原因虚拟机文件放在 C 盘系统目录时某些目录受系统权限保护VMware 进程无法完全读写另一个是在同一个虚拟机里放了两份不同平台的源码共用同一个 Jack 工具和 prebuilts导致服务互相干扰。解决虚拟机文件放到 C 盘普通目录比如 C:\Download避开系统保留目录。两份源码不要共用同一套虚拟环境删除另一份源码或单独开虚拟机然后重新启动 Jack 服务。这个坑没有编译报错提示却会在最意想不到的时候冒出来建议一开始就从磁盘路径和源码数量上规避。6. 编译完成后怎么验证镜像文件和编译日志编译结束不能只看 “Build completed successfully” 就收工。我见过 make 返回 0但指定 target 其实没生成的情况。所以每次编完我都会做一套固定的验证流程重点检查镜像文件、时间戳和 ninja 退出状态。6.1 检查镜像文件和大小找到 out 目录下的 product 输出目录ls -lh out/target/product/full_amt6799_evb_n/这里应当看到 system.img、boot.img、userdata.img 之类的镜像文件。system.img 一般在 500MB 到 2GB 之间boot.img 通常只有几十 MB。如果 system.img 文件存在但大小只有几 KB说明镜像没有真正链接成功需要回到编译日志看是否被 ninja 跳过。用 ls -lt 看时间戳确认文件时间是刚才编译生成的时间而不是源码包解压时带出来的时间。6.2 看退出码和日志关键字make 命令结束后执行echo $?如果输出 0说明 ninja 认为所有目标都成功如果输出 1哪怕日志里有 “Build completed” 也不能信。再配合 grep 检查日志make -j4 systemimage 21 | tee build.log grep -i FAILED build.log | tail -20tee 把终端输出同时写到 build.log后面 grep 直接定位失败模块。ninja 的失败通常不会让 make 立即退出而是把所有失败任务汇总后再报 ninja_wrapper failed所以要看完整日志不能只看最后几行。6.3 一个最小自检技巧先编 systemimage 再全量从那以后我每次搭新平台环境都会强制走一遍同样的验证先单独 make -j4 systemimage确认通过后再 make -j4 全量。如果 systemimage 都能出来说明依赖、Jack、内存、进程数这几大坑都已经稳住剩下的大概率只是个别模块的源码问题。这个顺序看起来多花了一点时间实际省掉的是反复解压源码的次数。这些做法都是从那份文档里一条条验证过来的有它垫底至少不用像我一样把源码解压三遍。希望帮到你。本文还有配套的精品资源点击获取