Android源代码编译笔记(支持5.x及以上版本) - Linux篇

2016/10/21
中科大负责镜像的同学回复说问题已解决。

2016/10/11
关于AOSP的镜像,中科大的目前不能正常使用。
可以暂时使用清华的源。

这是之前的笔记,主要记录了如何搭建Android源代码编译环境进行编译。里边有一些优化和问题的记录,可能会帮助大家更快地编译出来自己的Android系统。

系统与硬件

为了方便,我使用了VMWare虚拟机搭载Ubuntu 14.04作为我的操作系统。因为虚拟机确实比较方便,在这个硬盘便宜的时代,多占一些空间还是问题不大的。而且可以方便我们来回移动虚拟机,并且虚拟机文件在Windows系统和Mac OSX系统上都可以比较良好的运行。(Windows上用VMWare Workstation,Mac OSX上用VMWare Fusion,一家公司的产品,虚拟机文件兼容性没得说。)

虚拟机的硬盘空间我分配了上限120G(现在发现勉强够用,如果开ccache就有点够呛了),大家可以适当调大些。
内存根据你的实际情况,越大越好,因为在编译的后期很有可能会出现内存不足导致的编译失败。我分配了10G物理内存出来。
CPU核心也是看你的物理机的CPU,后边的”make -jN”命令里边的N和你的CPU核心数(总线程数)直接相关。

可以优化的地方

备份不同阶段的虚拟机

在某篇文章中看到这么一句话,”大家编译的时候总会遇到这样那样的问题,而且每次编译遇到的问题还不一样”。对这一点深有体会。所以我们可以在不同的阶段复制一份虚拟机文件作为备份,以后你想回到哪一个阶段都比较方便(或者你也可以把任何一个阶段的虚拟机分享给别人)。

为此,我专门买了一个大的移动硬盘存储了多份虚拟机文件。我备份了下面的几个阶段:

  1. 刚装好Ubuntu操作系统的(这个时候的系统文件最小,4-5G)
  2. 安装好安卓编译环境所需要的各种软件包并且代码下载完成(体积陡然变成40+G了)
  3. 编译好源代码的(我编译的是5.1.1,此时体积编程100+G了,还没有开ccache)
  4. …(个人喜好,可以备份任意阶段)

我并没有使用VM的快照功能,因为在操作虚拟机的时候都是直接将虚拟机文件复制到电脑的主硬盘上进行的,因为是SSD所以速度快一些,但是容量有限,所以能省则省。对于动辄几十G的操作变化来说,还是不要用快照了。

Ubuntu的源设置为中国

将Software&Updates的服务器切换到中国,我这里选择的是163

使用AOSP的国内镜像

网上找了一圈,发现了几个国内的镜像

  • 中科大:https://lug.ustc.edu.cn/wiki/mirrors/help/aosp
  • 清华大学:https://mirrors.tuna.tsinghua.edu.cn/help/AOSP/
  • 东软:http://mirrors.neusoft.edu.cn/more.we#android

这3个选择哪个呢?
东软的帮助里边只有SDK代理的配置,而没有AOSP的配置。
清华大学的同步源使用的是https协议,限制速度了,同步比较慢。
对比起来,中科大的源,速度较快,能达到5M+/S左右。下载源代码大概花了1个多小时,比Google源快太多了。

现在已经限制速度了,最快600K+/S左右

安装软件包

安装JDK

不同的分支需要不同的Java版本来进行编译

分支 Java版本
Master分支 OpenJDK 8
5.x - 6.0分支 OpenJDK 7
2.3.x - 4.4.x Java JDK 6

这里我下载的是5.1.1版本的源代码,需要安装OpenJDK 7,安装指令如下

$ sudo apt-get install openjdk-7-jdk

如果你要下载Master分支的源代码,则需要安装OpenJDK 8,安装指令如下(推荐16.04)

$ sudo apt-get update
$ sudo apt-get install openjdk-8-jdk

14.04安装OpenJDK 8比较麻烦,具体参考For Ubuntu LTS 14.04

安装必要的软件包

Ubuntu 14.04需要的软件包如下

$ sudo apt-get install git-core gnupg flex bison gperf build-essential \
  zip curl zlib1g-dev gcc-multilib g++-multilib libc6-dev-i386 \
  lib32ncurses5-dev x11proto-core-dev libx11-dev lib32z-dev ccache \
  libgl1-mesa-dev libxml2-utils xsltproc unzip

配置USB访问

指令如下

wget -S -O - http://source.android.com/source/51-android.rules | sed "s//$USER/" | sudo tee >/dev/null /etc/udev/rules.d/51-android.rules; sudo udevadm control --reload-rules

将上面的$USER更改为你的用户名

上面这个文件所预设的设备有限,如果你的设备无法正常访问(如提示没有权限),可以使用lsusb命令查询设备的id,并将其配置到上述文件中(/etc/udev/rules.d/51-android.rules)

比如,我的一个”未知”设备的信息如下:

$ lsusb
Bus 001 Device 002: ID 18d1:4ee7 Google Inc.
...

我们根据Google的写法,在文件最后加上

SUBSYSTEM=="usb", ATTR{idVendor}=="18d1", ATTR{idProduct}=="4ee7", MODE="0600", OWNER="$USER"

下载源代码

安装repo

repo是Google开发的一个Android源代码同步工具,如果你直接在Google官方下载代码,那么按照官网的教程安装就可以了。

如果你要使用国内镜像的源进行下载,那么要对repo进行一下简单的修改。它本身是一个python脚本文件,所以实际上是修改里边的REPO_URL变量。只不过中科大已经改好了,你直接从它那里下载repo不用修改就行了;而清华大学的需要你从Google下载好repo然后手动修改REPO_URL变量。

这里以中科大为例,贴一下指令

mkdir ~/bin
PATH=~/bin:$PATH
curl https://storage.googleapis.com/git-repo-downloads/repo > ~/bin/repo
## 如果上述 URL 不可访问,可以用下面的:
## curl https://storage-googleapis.lug.ustc.edu.cn/git-repo-downloads/repo > ~/bin/repo
chmod a+x ~/bin/repo

repo是一个python文件。
REPO_URL变量原始值为https://gerrit.googlesource.com/git-repo
中科大将其修改为了https://gerrit-googlesource.proxy.ustclug.org/git-repo

建立工作目录

工作目录的名字可以随意起

mkdir WORKING_DIRECTORY
cd WORKING_DIRECTORY

下载最新的镜像包(可选)

由于网络不稳定因素,所以可以采用中科大或者清华大学镜像源推荐的”先下载镜像tar包,然后再repo同步至最新代码”的方法。(具体步骤参见上面的链接)

下载好之后,将其解压到指定的目录。下面是一个示例:

$ tar xvf ~/Downloads/aosp-20171001.tar -C ./Volumes/android/aosp/

注意,上面的镜像包只是打包,并没有压缩。

然后

如果您已经从官方同步了 AOSP 仓库,现在希望使用科大的 AOSP 仓库,请修改.repo/manifests.git/config,将

url = https://android.googlesource.com/platform/manifest
修改成
url = git://mirrors.ustc.edu.cn/aosp/platform/manifest
即可。

然后执行repo sync进行同步

请注意保持上述文件中的地址与repo文件中的地址为同一个源的地址,以免发生错误。

初始化仓库

5.1.1版本已经不在Master分支了,所以我们需要手动指定分支。具体的分支名称可以从 Source Code Tags and Builds 查询到,5.1.1如下

LMY48Y android-5.1.1_r26 Lollipop Nexus 6

所以我的初始化指令就是

repo init -u git://mirrors.ustc.edu.cn/aosp/platform/manifest -b android-5.1.1_r26

如果你要同步Master分支,那么指令如下

repo init -u git://mirrors.ustc.edu.cn/aosp/platform/manifest -b master

开始下载源代码

repo sync

强烈建议大家使用国内镜像源,一个多小时就能下载完成。

编译

设置ccache缓存 - 可选

注意这一步是可选的,它的主要作用是你重新编译(make clean之后)的时候回大大加快速度。官方建议缓存的大小为50 - 100G,注意保证你的磁盘可用空间。因为我使用的是模拟器,这个会使得虚拟机文件大很多,所以我并没有开启这个缓存。
但是,命令还是贴在这里。

设置环境变量(可以写在.bashrc或类似的文件中)

export USE_CCACHE=1
export CCACHE_DIR=//.ccache    // optional

执行命令

prebuilts/misc/linux-x86/ccache/ccache -M 50G

注意,ccache可以手动指定目录,如不指定,默认目录是在~/.ccache

配置驱动软件包

官方文档介绍的是配置专有的二进制包(其实就是硬件驱动),因为这些是有专利的,所以不能开源,只能提供二进制包。
官方只提供Google Nexus系列的驱动,而我暂时只运行emulator,所以跳过这一节。

如果你的target是Nexus系列的硬件,请参考 Obtain proprietary binaries 下载对应的驱动。

如果是Nexus 5x和6p,在上面的地址是找不到对应的proprietary binaries的,可以从 Binaries Preview for Nexus Devices 找到。

关于上面这个问题,有几个讨论作为书签记录下来:

  1. Why has Google not released the binaries for Nexus 6p and 5x?
  2. AOSP flashed onto Nexus 5x missing vendor libraries? No Camera access?
  3. anestisb/android-prepare-vendor

配置编译所需的环境变量

两个命令,执行哪个都可以

$ source build/envsetup.sh

或者

$ . build/envsetup.sh

编译6.0或Master分支
Android 6.0之后,AOSP编译引入了Jack工具链,它替换了之前几个工具的组合(javac, ProGuard, jarjar和dx)。引入它的目的是为了提高编译速度。

幸运的是,我们无需做任何额外的事情来启用Jack,任然使用之前的编译命令即可。

更多信息请参考我翻译的文章 使用Jack编译,原文是 Compiling with Jack。

选择编译目标

执行lunch命令,会出现一个菜单让你选择build target。

这里我选择的是aosp_x86_64-eng,即x86_64平台的模拟器,如果是arm,那么运行速度实在是太慢了。

开始编译

编译时我们可以选择同时并行运行的任务,推荐配置是你CPU线程数量的1-2倍,我给虚拟机设置了4个线程的核心,那么命令就是

$ make –j8

分支切换

如果你需要在不同的分支之间切换,那么请参考 AOSP分支切换

编译时遇到的问题

内存不够

编译了3个多小时(正在编译libwebviewchromium.so),出现了下面的错误提示

collect2: error: ld terminated with signal 9 [Killed]

经过搜索,遇到这个错误的大有人在,原来是内存不够用了,这时候可以增加物理内存或者swap分区。因为Virtual Memory = RAM + Swap space/file。

检查一下配置发现,虚拟机的物理内存分配了4G,swap分区为2.1G,一共是6.1G。
可以选择增加swap分区,不过增加物理内存是更加简便易行(并且一劳永逸)的方法。
增加到了8G试试。
重新执行make –j8依然失败,无奈,make clean,然后将ccache设置为30G(磁盘快用完了),重新执行make –j8

多线程造成的失败

由于是在虚拟机里边进行编译,而物理硬盘又太小,所以暂且不开ccache了。
重新编译了一次,这次物理直接物理内存10G,swap还是2.1G,执行的是make –j8,3个半小时之后(其实我又换了一份没有编译过的虚拟机重新编译),提示编译失败,相应的错误信息有(比较多,这两句看着像错误原因):

Dumping all threads without appropriate locks held: thread list lock mutator lock
make: *** [out/target/product/generic_x86_64/obj/APPS/Contacts_intermediates/x86_64/package.odex] Aborted (core dumped)

猜测可能是由于多线程的问题造成编译失败,所以将线程数改为4个,执行make –j4,7分钟之后编译成功。

Jack “Out of memory error”

详细的错误日志如下

Out of memory error (version 1.2-rc4 'Carnac' (298900 f95d7bdecfceb327f9d201a1348397ed8a843843 by [email protected])).
GC overhead limit exceeded.
Try increasing heap size with java option '-Xmx'.
Warning: This may have produced partial or corrupted output.

详细的错误日志里边列出了问题并且已经给出了解决方案 - 增加Java虚拟机的-Xmx大小,即设置一个较大的堆内存上限。

可以修改Jack的配置文件prebuilts/sdk/tools/jack-admin
这时一个管理Jack的shell脚本,找到start-server函数,直接修改其启动参数,由原来的

JACK_SERVER_COMMAND="java -XX:MaxJavaStackTraceDepth=-1 -Djava.io.tmpdir=$TMPDIR $JACK_SERVER_VM_ARGUMENTS -cp $LAUNCHER_JAR $LAUNCHER_NAME"

更改为

JACK_SERVER_COMMAND="java -XX:MaxJavaStackTraceDepth=-1 -Djava.io.tmpdir=$TMPDIR $JACK_SERVER_VM_ARGUMENTS -Xmx4096m -cp $LAUNCHER_JAR $LAUNCHER_NAME"

此时Jack服务器仍然在后台执行,所以我们需要将其停止,然后重启启动(make会自动启动Jack服务器)才能使得修改后的参数生效。

我们执行下面的命令

$ ./prebuilts/sdk/tools/jack-admin stop-server

然后我们重新执行make -jN命令(N是你前边设置的并行任务数量)开始编译。

我们可以启动jconsole来对比查看Jack服务器更改设置前后的Maximum heap size,来确认设置是否已经生效。

这里有一个问题,通过jconsole看到的上述值为”3728384kb”,换算一下是”3641m”,并不等于”4096m”,不知原因为何。

unsupported reloc 42 (43) against global symbol art::Runtime::instance_

在Ubuntu 16.04上编译Android 6.0时有可能会碰到这个错误,错误信息如下

...
prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.15-4.8//x86_64-linux/bin/ld: error: out/host/linux-x86/obj32/SHARED_LIBRARIES/libart_intermediates/arch/x86/quick_entrypoints_x86.o: unsupported reloc 43 against global symbol art::Runtime::instance_
...
prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.15-4.8//x86_64-linux/bin/ld: error: out/host/linux-x86/obj/SHARED_LIBRARIES/libart_intermediates/arch/x86_64/quick_entrypoints_x86_64.o: unsupported reloc 42 against global symbol art::Runtime::instance_
...
out/host/linux-x86/obj/SHARED_LIBRARIES/libart_intermediates/arch/x86_64/quick_entrypoints_x86_64.o:function art_quick_deoptimize: error: unsupported reloc 42
clang: error: linker command failed with exit code 1 (use -v to see invocation)
build/core/host_shared_library_internal.mk:51: recipe for target 'out/host/linux-x86/obj/lib/libart.so' failed
make: *** [out/host/linux-x86/obj/lib/libart.so] Error 1
warning: string 'gsm_alphabet_default_charset' has no default translation.

问题的原因在于aosp中的预编译好的ld程序存在bug,我们使用Ubuntu系统默认的ld来替换它。具体步骤如下:

将aosp中的ld改名

$ cd prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.15-4.8/x86_64-linux/bin
$ mv ld ld.old

使用soft link,链接到全局ld

$ ln -s /usr/bin/ld.gold ld

上面的操作可以解决问题,但是引来了2个问题:

  • 为什么链接到ld.gold,而不是ld/ld.bfd

初步观察结果:
aosp中的ld就是ld.gold,虽然不是链接过去,但是你可以通过md5sum发现二者是同一个文件。
所以这里也链接到全局的ld.gold

  • ld是什么,ld.goldld.bfd到底区别在哪里呢?

参考

  1. 清华大学 - Android 镜像使用帮助
  2. 中科大 - Android 镜像使用帮助
  3. aosp. emulator and make sdk.
  4. Android源码官网
  5. Build android 5.0 for hammerhead (Nexus 5)
  6. Ubuntu Linux Create and Add Swap File Tutorial
  7. android 4.0.1源码编译,学习错误解决
  8. Android实战技巧之四十:Android5.1.1源代码编译与烧写
  9. OS X 10.11下载和编译Android6.0源码
  10. 关于android编译出现“GC overhead limit exceeded”的解决办法。
  11. 理解 Android Build 系统
  12. AOSP分支切换
  13. Ubuntu16.04 安装openjdk-7-jdk
  14. Android 6.0 problem toolchain unsupported relloc 42/43..
  15. v8 fails to compile - ‘unsupported reloc 42’
  16. Building Android from sources: unsupported reloc 43

你可能感兴趣的:(Android->AOSP研究)