2023-03-06 16:34:01 +08:00
..
2023-03-06 16:34:01 +08:00

第二章 系统开发环境与工具

2.1 重新看待系统定制

经过第一章的学习对AOSP定制进行简略的介绍后相信此时系统定制开发这个领域在读者的心中会有大致的了解。简单来说所谓的系统定制相当于在一款成熟的产品上进行二次开发。和常见的软件项目的二次开发的学习步骤类似不会有太大的出入细节的区别就在于Android源码相比其他软件项目要更加庞大复杂修改编译以及测试系统所花费的时间周期更长。

尽管Android源码结构非常庞大但对于初学者并不需要完整的吃透所有代码。重点的是掌握系统代码分析的思路阅读理解工程的整体结构了解Android系统框架的运行原理结合思考与实践达到自定义定制的目标。

学习的流程需要循序渐进有的放矢以免迷失在纷繁复杂的代码海洋中。通常第一步需要了解如何将整个系统项目成功编译并刷机。这一章将详细讲解在各种不同的环境下应该如何编译Android源码并将其刷入手机中。

2.2 环境准备

安卓系统在版本10之前是支持macOS系统上编译AOSP代码的。在新版本系统的演进过程中安卓官方已经放弃在macOS系统平台上做AOSP开发的支持官方开发指导环境采用了Linux上比较流行的Ubuntu发行版本。

在实际的开发过程中可以使用Windows系统下的WSL2或Docker来构建一个Ubuntu系统运行环境同样可以完成AOSP编译与开发工作。

这一节将会介绍在Windows系统与Linux系统上如何完成环境准备工作。

2.2.1 Windows

由于在Windows中缺少了各种底层编译器与开发库的支持一般情况下开发人员不会直接在Windows环境中编译而是选择在Windows中创建一个Linux的虚拟环境然后在虚拟环境中安装编译所需要用到的底层依赖。在Windows系统上部署Ubuntu虚拟环境有多种可选方案例如Docker、WSLWindows Subsystem for Linux、Vmware虚拟机QEMU、HyperV虚拟机平台等等。

几种方案经过编译对比测试发现Docker在Windows系统上的体验并不怎么好主要体现在编译这类大型项目时需要较大的磁盘存储空间选择外挂磁盘映射时编译时IO性能较弱而选择创建虚拟磁盘时对宿主机的开机耗时明显变高。这里不太建议在Windows下采用Docker来编译源码。

WSL是Windows下内置的Linux子系统最新的版本号为2通常将其称为WSL2。它是一个非常轻量化的Linux系统让那些想在Windows中编译与运行Linux程序的开发人员爱不释手。安装好WSL2后只要在终端中输入一个wsl命令就可以启动环境。使用起来的感觉就好似直接使用命令行一样。并且编译性能相比Vmware这类虚拟机要更加高效。在我的笔记本环境中WSL2完整编译的耗时为130分钟Vmware虚拟机的耗时是170分钟这是因为WSL2采用直通计算机硬件IO性能有着较为显著的提升。

如果你的系统上Windows10那么你需要先查询当前系统版本必须是18917或更高的版本才支持WSL2。在cmd命令行中输入winver命令查看当前系统版本号。

image-20230102183339463

由于是系统自带的,所以安装起来非常方便,可以直接在控制面版->程序->启动或关闭Window功能中开启支持即可如下图

img

或者是采用命令的方式开启虚拟机平台和Linux子系统使用管理员权限启动

image-20230102183708998

执行下面的命令开启功能

//启用虚拟机平台
Enable-WindowsOptionalFeature -Online -FeatureName VirtualMachinePlatform
//启用Linux子系统
Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Windows-Subsystem-Linux

启动完成这些特性后,重新启动计算机,然后,就可以开始安装一个Ubuntu系统了。打开Microsoft Store应用商店搜索Ubuntu系统然后选择自己需要的版本即可例如我安装的是22.04版本,如下图。

image-20230102184626538

成功获取Ubuntu系统后从应用中启动系统即开始正式安装。安装过程只需要设置好用户名与密码即可。完成后会进行一个shell环境供用户输入。 需要注意的是应用商店默认会将WSL安装在C盘中而编译系统会占用相当大的空间如果你的系统盘空间不够需要做一个迁移操作将子系统迁移到其他硬盘中。操作方法是桌面任意位置右键选择终端在打开的终端环境中自毁长城下面的命令查询当前的子系统名称。

wsl -l -v

  NAME        STATE           VERSION
* ubuntu22    Running         2

然后,执行wsl --export命令,将子系统导出到其它较大空间的分区中;接着,执行wsl --unregister将其注销,最后,执行wsl --import 再重新导入放在其他分区或磁盘上的子系统。如下所示。

//导出子系统
wsl --export ubuntu22 E:\wsl2\ubuntu22.tar
//注销之前的虚拟机
wsl --unregister ubuntu22
//重新导入虚拟机,并且指定新的虚拟机存放位置
wsl --import ubuntu22 E:\wsl2\ubuntu22_wsl E:\wsl2\ubuntu22.tar

现在,再次执行wsl命令即可进入子系统的shell环境。

使用WSL2主要是在于轻量级和更优的高性能一般都是命令模式的Linux图形界面的程序可以通过安装一些依赖来解决但这不是WSL2的强项。使用WSL2搭建开发环境时使用远程开发模式不失为一种优雅的技术方案典型的有使用vscode配合wsl插件可以快速的远程访问WSL2环境上的代码与程序另外WSL2安装ssh服务后vscode配合使用remote ssh插件也可以进行开发环境的搭建。

如果需要完整的Linux系统环境使用VMware虚拟机会更加的合适。步骤也非常简单,流程如下。

1、下载并安装VMware虚拟机然后下载Ubuntu22.04系统ISO镜像文件。

2、VWware创建虚拟机选择指定镜像

image-20230102194041709

3、设置初始账号密码

image-20230102194243774

4、选择虚拟机保存位置这里不要保存在C盘记得磁盘要有至少300G的空间

image-20230102194331141

5、虚拟硬件CPU核心根据你的电脑配置进行调整尽量多分点给虚拟机。

image-20230102194543812

6、虚拟内存分配至少保证16G以上的内存否则可能会碰到内存不足编译失败的情况。

image-20230102194722427

7、虚拟硬盘分配这里至少分配500G的空间考虑到性能我选择的是单文件吗这里如果选择立即分配所有磁盘空间能提高一定的性能。如果你的电脑配置不是很高建议你选择立即分配。

image-20230102194952517

虚拟机开机后将默认进入Ubuntu安装界面按照提示进行选择语言区域等待安装完成即可。

2.2.2 Linux

Linux系统的选择非常多本书中选择最新的Ubuntu 22.04 LTS稳定版。这里假定读者已经在自己的硬件上安装好了系统环境安装方法与Vmware安装系统的操作流程类似

首先,安装必备的开发工具。

1、Android Studio下载并安装下载地址https://developer.android.google.cn/studio/

2、Clion下载并安装下载地址https://www.jetbrains.com/zh-cn/clion/

3、vscode下载并安装下载地址https://code.visualstudio.com/

然后,执行下面的命令配置好pythonpip

// 更新软件列表
sudo apt update -y && sudo apt upgrade -y

// 安装python和apt-utils
sudo apt-get install -y apt-utils python3 python3-pip python2

// 安装pip
pip install -U pip

// 设置pip使用国内源
python -m pip install --upgrade pip
pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
pip install pytest

到这里Ubuntu系统上的AOSP编译开发环境就补步准备好了。

2.3 源码拉取与同步

在开始拉取代码前首选需要了解自己需要编译的AOSP分支版本可以参考官网对版本的说明链接。https://source.android.com/docs/setup/about/build-numbers?hl=zh-cn

2.3.1 分支选择策略

根据需求比如想要在Android10的基础上进行二次开发那么就找到对应的版本描述根据下图可以看到各个版本号关联的代码分支Android版本支持哪些设备。

image-20230103220519836

这么多版本,需要选一个最适合的版本,选择策略如下:

1、优先选择与你的测试机兼容的版本。

2、除了支持你的这个设备外还支持更多设备的版本。

3、满足上面两个条件的最高分支版本即优先最新的代码分支。

如果选择使用虚拟机那么选择支持版本最多的分支即可。这里我的测试设备是pixel 3所以选择了版本SP1A.210812.016.A1,对应的分支代码是android-12.0.0_r3,如下图。

image-20230103220838404

2.3.2 repo配置

AOSP官方使用repo管理项目。repo是一个以git为基础包装的代码版本管理工具,内部是由python脚本构成的,对git命令进行包装,方便管理大型的项目。

repo配置前需要先安装与配置好git。执行如下命令:

// 安装git
sudo apt-get install git

//设置git身份
git config --global user.email "xxxx@qq.com"
git config --global user.name "xxxx"

接着下载配置repo。执行

// 创建bin目录并加入PATH
mkdir ~/bin
PATH=~/bin:$PATH

// 安装curl
sudo apt-get install curl

// 下载repo并设置权限
curl https://mirrors.tuna.tsinghua.edu.cn/git/git-repo > ~/bin/repo
chmod a+x ~/bin/repo

// 设置使用国内源拉取代码
export REPO_URL='https://mirrors.tuna.tsinghua.edu.cn/git/git-repo/'

2.3.3 源码拉取与同步

上面知道了需要的目标分支,接下来的步骤是拉取代码。执行如下命令:

// 创建源码存放的目录
mkdir aosp_12 && cd aosp_12

// 初始化仓库
repo init -u https://aosp.tuna.tsinghua.edu.cn/platform/manifest

// 指定分支版本
repo init -u https://aosp.tuna.tsinghua.edu.cn/platform/manifest -b android-12.0.0_r3

// 同步代码
repo sync -c -j8

同步代码使用repo sync -c -j8的命令,其中,-c表示只同步当前分支代码,可以提高同步速度,而-j是设置同步使用的线程数这里我使用了8个线程并不是线程越多速度越快而是根据cpu的核心数使用最合理的线程数才能达到最佳的并发效果。

// 查看可用cpu数量我的环境显示为16
nproc --all

// 直接使用最佳线程数
repo sync -c -j16

//也可以直接省略成一句
repo sync -c -j$(nproc --all)

代码同步完成后,会提示Success,如果失败了,就重新拉取即可,多拉取几次后,基本都能同步成功。

2.4 系统编译

接下来,开始安装编译的底层依赖。

2.4.1 AOSP编译依赖库安装

执行下面的命令,安装相关依赖:

// AOSP编译的相关依赖安装
sudo apt-get install -y git-core gnupg flex bison build-essential \
	zip curl zlib1g-dev gcc-multilib g++-multilib libc6-dev-i386 lib32ncurses5-dev \
	x11proto-core-dev libx11-dev lib32z1-dev libgl1-mesa-dev libxml2-utils xsltproc unzip \
	fontconfig libncurses5 procps rsync libsqlite3-0

2.4.2 系统编译

注意编译AOSP需要大量的磁盘空间通常300G的空间足够存放代码与编译输出的结果。如果你希望将输出的结果存放在其它目录。这一点通过设置OUT_DIR环境变量来调整编译结果的输出目录。如下所示,

vim ./build/envsetup.sh
// 在底部加上环境变量设置为和源码同一级的目录,我当前源码路径为~/android_src/aosp12
export OUT_DIR=~/android_src/aosp12_out

在开始编译前,还需要准备对应设备的驱动,根据前面选择的版本号SP1A.210812.016.A1,在官网地址:https://developers.google.com/android/drivers中找到对应的版本号,并且可以看到Pixel 3的手机对应的代号是blueline

image-20230103232052738

第一个文件Vendor是用来存储厂商特定的文件比如设备驱动程序。Android驱动会根据提供的这些设备驱动来正确的加载硬件。这个文件通常由设备厂商提供。如果你成功编译Android后输出目录缺少vendor.img文件那么你就需要检查下是否忘记导入对应型号的设备驱动了。

第二个文件是高通提供的相关设备驱动程序比如GPS摄像头传感器等设备的闭源二进制文件。

点击Link下载然后将下载的文件拷贝到Android源码根目录下。然后解压并导出相关驱动文件。

// 解压驱动文件
tar -xvf qcom-blueline-sp1a.210812.016.a1-33e668b9.tgz
tar -xvf google_devices-blueline-sp1a.210812.016.a1-d10754e0.tgz

// 解压会得到两个文件extract-google_devices-blueline.sh和extract-qcom-blueline.sh
// 依次运行两个文件运行后会提示许可说明按回车键然后按q跳过最后手动输入I ACCEPT后回车即可
./extract-google_devices-blueline.sh
./extract-qcom-blueline.sh

导入设备驱动完成后,准备工作基本完成,可以开始编译源码了。

// 初始化构建环境参数
source ./build/envsetup.sh

// 选择编译的版本
lunch

//下面是我这边显示的结果
Lunch menu... pick a combo:
     1. aosp_arm-eng
     2. aosp_arm64-eng
     3. aosp_barbet-userdebug
     4. aosp_blueline-userdebug
     5. aosp_blueline_car-userdebug
     6. aosp_bonito-userdebug
     7. aosp_bonito_car-userdebug
     8. aosp_bramble-userdebug
     9. aosp_bramble_car-userdebug
Which would you like? [aosp_arm-eng]

// 选择版本可以填写直接填写aosp_blueline-userdebug或者是填写编号4
// 同样可以省略成一句直接lunch 4或者是lunch aosp_blueline-userdebug
4

// 和上面一样。直接使用当前cpu的核心数作为编译的并发线程
make -j$(nproc --all)

在上面选择版本中,可以看到aosp_arm-engaosp_arm64-eng的选项,这两个是模拟器使用的版本。而模拟器使用的版本是可以不需要导入设备驱动文件的。如果在lunch的菜单中没有看到你要编译的版本,并且直接lunch aosp_blueline-userdebug 也提示错误,可能是没有成功导入驱动文件,或者下载的驱动文件错误。

同一个代号的编译有三种编译版本选择。分别如下:

1、aosp_blueline-user 为用户版本,一般是默认的编译版本。主要用于发布版本,这种版本编译的环境会默认开启大多数的安全机制,比如ro.secure值为1ro.debuggable值为0需要自行用第三方工具获取root权限。厂商设备出厂时设备通常会编译为user版本。

2、aosp_blueline-userdebug 为用户调试版本通常用于测试和调试Android系统会启动一些调试工具例如默认开启adb调试,ro.debuggable值为1系统自带root权限等。

3、aosp_blueline-eng 为工程版本,同样也是用于测试和调试的环境,但是系统限制比userdebug要更加少,会禁用一些安全机制,比如签名验证,关闭一些编译优化等。

第一次完整编译非常的漫长笔者的电脑耗时约2个小时成功编译。编译成功后检查一下输出的文件。

// 查看输出目录的所有镜像文件
ls /root/android_src/aosp12_out/target/product/blueline | grep img

// 输出结果如下
boot-debug.img
boot.img
bootloader.img
boot-test-harness.img
dtb.img
dtbo.img
persist.img
radio.img
ramdisk-debug.img
ramdisk.img
ramdisk-recovery.img
ramdisk-test-harness.img
super_empty.img
system_ext.img
system.img
system_other.img
userdata.img
vbmeta.img
vendor.img

确定有编译出vendor.img、system.img、boot.img等镜像文件,就说明编译成功了。

2.5 模块编译

前文在编译的过程中介绍到,使用source ./build/envsetup.sh初始化环境的时候,导入了多个命令来辅助编译。接下来,先看看有哪些常用的命令。

通过命令hmm查看提供的命令帮助。

hmm

Run "m help" for help with the build system itself.

Invoke ". build/envsetup.sh" from your shell to add the following functions to your environment:
- lunch:      lunch <product_name>-<build_variant>
              Selects <product_name> as the product to build, and <build_variant> as the variant to
              build, and stores those selections in the environment to be read by subsequent
              invocations of 'm' etc.
- tapas:      tapas [<App1> <App2> ...] [arm|x86|arm64|x86_64] [eng|userdebug|user]
              Sets up the build environment for building unbundled apps (APKs).
- banchan:    banchan <module1> [<module2> ...] [arm|x86|arm64|x86_64] [eng|userdebug|user]
              Sets up the build environment for building unbundled modules (APEXes).
- croot:      Changes directory to the top of the tree, or a subdirectory thereof.
- m:          Makes from the top of the tree.
- mm:         Builds and installs all of the modules in the current directory, and their
              dependencies.
- mmm:        Builds and installs all of the modules in the supplied directories, and their
              dependencies.
              To limit the modules being built use the syntax: mmm
// 省略
......

croot 命令可以跳转根目录,或者是根目录下的任意子目录

m 命令会直接在根目录运行编译,即使当前目录是在子目录也是相当于在根目录编译。也可以指定名称来编译单独的目标,例如m droid

mm 编译当前目录中的所有模块及依赖项

mmm 编译指定目录中的所有模块及依赖项

clean 清除编译的结果相当于删掉out目录中的内容

可以通过m help查看可以单独编译哪些选项

m help

Common goals are:

    clean                   (aka clobber) equivalent to rm -rf out/
    checkbuild              Build every module defined in the source tree
    droid                   Default target
    nothing                 Do not build anything, just parse and validate the build structure

    java                    Build all the java code in the source tree
    native                  Build all the native code in the source tree

    host                    Build all the host code (not to be run on a device) in the source tree
    target                  Build all the target code (to be run on the device) in the source tree

    (java|native)-(host|target)
    (host|target)-(java|native)
                            Build the intersection of the two given arguments

    snod                    Quickly rebuild the system image from built packages
                            Stands for "System, NO Dependencies"
    vnod                    Quickly rebuild the vendor image from built packages
                            Stands for "Vendor, NO Dependencies"
    pnod                    Quickly rebuild the product image from built packages
                            Stands for "Product, NO Dependencies"
    senod                   Quickly rebuild the system_ext image from built packages
                            Stands for "SystemExt, NO Dependencies"
    onod                    Quickly rebuild the odm image from built packages
                            Stands for "Odm, NO Dependencies"
    vdnod                   Quickly rebuild the vendor_dlkm image from built packages
                            Stands for "VendorDlkm, NO Dependencies"
    odnod                   Quickly rebuild the odm_dlkm image from built packages
                            Stands for "OdmDlkm, NO Dependencies"

通过帮助命令的提示,可以看到m snod就是单独编译system模块,命令m vnod就是单独编译Vendor。大多数时候,修改的内容都是在system模块中。可以根据自己对系统的修改情况,执行不同的模块编译。

2.6 内核编译

系统编译完成后,可以在编译的镜像结果中看到文件boot.img这个文件是内核镜像文件。但是这个内核默认采用Android源码树中预编译好的内核文件并不使用编译出来的如果想要为编译的系统纳入自编译的内核需要拉取对应分支的内核代码参与编译并将编译结果放入Android源码树中的指定路径最后再重新编译打包Android镜像。这样生成的系统刷入手机后使用的内核就是自编译的版本了。

首先,找到对应当前手机的内核分支,官网提供了详细的说明文档。https://source.android.com/docs/setup/build/building-kernels。根据下图可以看到对应Pixel 3测试机分支是android-msm-crosshatch-4.9-android12

image-20230105221730348

接下来,按照官网的说明拉取代码并编译。

// 内核编译的相关依赖安装
sudo apt install p7zip-full wget curl git tree -y
sudo apt-get install dialog file python3 python3-pip python2 libelf-dev gpg gpg-agent tree flex bison libssl-dev zip unzip curl wget tree build-essential bc software-properties-common libstdc++6 libpulse0 libglu1-mesa locales lcov --no-install-recommends -y
sudo apt-get install pahole libreadline-dev -y

// 创建内核的源码目录不用放再Android源码目录下
mkdir android-kernel && cd android-kernel

// 初始化指定分支
repo init -u https://android.googlesource.com/kernel/manifest -b android-msm-crosshatch-4.9-android12

// 同步分支代码
repo sync -j$(nproc --all)

// 编译内核
build/build.sh

// 编译完成后查看编译结果最后输出显示Image.lz4文件就表示内核编译是成功的。
ls /root/android_src/android-kernel/out/android-msm-pixel-4.9/dist |grep Image

编译成功后还需要指定Android源码编译时使用这个内核文件。只需要设置环境变量TARGET_PREBUILT_KERNEL,指定内核文件的完整路径即可。方式如下。

export TARGET_PREBUILT_KERNEL=/root/android_src/android-kernel/out/android-msm-pixel-4.9/dist/Image.lz4

为了以后方便可以将路径相关的环境变量写在envsetup.sh这个初始化导入环境命令的脚本中。如下所示

vim ./build/envsetup.sh

// 在最底部添加
export TARGET_PREBUILT_KERNEL=/root/android_src/android-kernel/out/android-msm-pixel-4.9/dist/Image.lz4

// 保存配置后,重新加载一下
source ./build/envsetup.sh

配置生效后,执行下面的命令再次编译生成内核boot.img

// 选择编译版本
lunch aosp_blueline-userdebug

// 单独编译内核镜像
make bootimage

2.7 刷机

大多数情况下Android系统的玩机爱好者通常会使用傻瓜式的一键刷机工具例如刷机大师、刷机精灵、奇兔等工具来刷机。这种刷机方式属于软刷软件刷机除此之外还有第一章中介绍到的线刷和卡刷。不论刷机的方式是什么他们最终都是对刷机包进行处理然后将ROM文件写入对应的分区替换掉原始文件。下面将介绍如何进行线刷和卡刷。

2.7.1 线刷

上面编译操作完成后,在目录aosp12_out/target/product/blueline/中能看到若干个后缀为img的镜像文件。笔者的输出路径aosp12_out是手动指定的输出目录,如果读者没有设置,这些文件默认存放在aosp12/out/target/product/blueline/目录下,目录中的blueline是对应编译的设备的代号,如果你是其他型号的机器,就需要在对应的其它代号目录下查看。

执行adb reboot bootloader进入刷机模式,然后设置环境变量ANDROID_PRODUCT_OUT指定系统镜像的路径,然后使用fastboot命令完成刷机。详细流程如下。

// 进入刷机模式
adb reboot bootloader

// 设置刷机包的路径到环境变量
export ANDROID_PRODUCT_OUT=/home/king/android_src/mikrom_out/target/product/blueline

// 查询fastboot是否能成功看到设备
fastboot devices

// 上面的查看命令显示的结果
8ARX0Y7EP	fastboot

// 完整刷机
fastboot flashall -w

等待刷机结束即可刷机结束后会自动进入Android系统。如果只想刷单个分区镜像流程如下。

// 进入刷机模式
adb reboot bootloader

// 进入编译结果的目录
cd /home/king/android_src/mikrom_out/target/product/blueline

// 单独刷入内核
fastboot flash boot ./boot.img

// 单独刷入系统
fastboot flash system ./system.img

// 部分机型可能会出现如下错误提示
fastboot: error: The partition you are trying to flash is dynamic, and should be flashed via fastbootd. Please run:

    fastboot reboot fastboot

And try again. If you are intentionally trying to overwrite a fixed partition, use --force.

// 这种情况按照它的提示操作即可执行下面的命令后发现进入了fastbootd模式
fastboot reboot fastboot

// 重新刷入系统
fastboot flash system ./system.img

// 刷入共享系统
fastboot flash system_ext ./system_ext.img

// 刷入硬件驱动
fastboot flash vendor ./vendor.img

// 重启
fastboot reboot

2.7.2 卡刷

前面步骤编译出来的是系统线刷包,如果需要卡刷包,就需要使用make otapackage命令来进行编译。注意这种方式需要预先编译好线刷包。具体的编译方法如下。

// 下面是简单的编译卡刷包
cd aosp12
source ./build/envsetup.sh
lunch aosp_blueline-userdebug
make otapackage

编译完成后可以在前面线刷包的同样路径下看到zip格式的卡刷包文件这里的文件名是aosp_blueline-ota-eng.king.zip。除了上面的方式,还可以执行make dist命令完整编译卡刷包,具体的编译方式如下。

//下面是完整编译卡刷包
cd aosp12
source ./build/envsetup.sh
lunch aosp_blueline-userdebug
mkdir dist_output
make dist DIST_DIR=dist_output

编译完成后,在目录dist_output中看到卡刷包结果。

接下来是如何刷入卡刷包,有两种刷入方式,一种是使用adb sideload命令刷入另一种方式是使用第三方的Recovery工具TWRP刷入。下面演示两种不同方式的刷机流程。

1、adb sideloadTODO这里待补充

首先进入fastbootd

adb reboot bootloader
fastboot reboot fastboot

这时的界面如下图,使用音量键减,切换到Enter recovery,然后按电源键进入recovery模式

image-20230108190236615

接下来进入下面的界面,选择Apply update from ADB

image-20230108190631803

2、twrpTODO这里待补充

2.8 源码的开发环境搭建

Android系统是一个非常庞大的工程需要采用合适的编辑器或IDE来阅读与修改代码。如果改动不多使用vscode导入工作区即可开始修改代码。vscode的智能提示和跳转相对IDE较为简陋如果想要更加友好的开发体验可以选择将源码导入Android Studio中编辑java部分代码导入Clion中编辑native部分代码。下面介绍如何将源码导入Android Studio

cd ~/aosp12
source build/envsetup.sh
lunch aosp_blueline-userdebug

// 编译生成idegen.jar
make idegen

// 在源码根目录生成android.ipr和android.iml
development/tools/idegen/idegen.sh

// 编辑iml文件找到excludeFolder的属性位置新增排除掉一些基本不怎么修改或者是native代码相关的部分
vim ./android.iml

// 例如新增下面这些部分
<excludeFolder url="file://$MODULE_DIR$/abi"/>
<excludeFolder url="file://$MODULE_DIR$/art"/>
<excludeFolder url="file://$MODULE_DIR$/bionic"/>
<excludeFolder url="file://$MODULE_DIR$/bootable"/>
<excludeFolder url="file://$MODULE_DIR$/build"/>
<excludeFolder url="file://$MODULE_DIR$/cts"/>
<excludeFolder url="file://$MODULE_DIR$/dalvik"/>
<excludeFolder url="file://$MODULE_DIR$/developers"/>
<excludeFolder url="file://$MODULE_DIR$/development"/>
<excludeFolder url="file://$MODULE_DIR$/device"/>
<excludeFolder url="file://$MODULE_DIR$/docs"/>
<excludeFolder url="file://$MODULE_DIR$/external"/>
<excludeFolder url="file://$MODULE_DIR$/hardware"/>
<excludeFolder url="file://$MODULE_DIR$/libcore"/>
<excludeFolder url="file://$MODULE_DIR$/libnativehelper"/>
<excludeFolder url="file://$MODULE_DIR$/ndk"/>
<excludeFolder url="file://$MODULE_DIR$/out"/>
<excludeFolder url="file://$MODULE_DIR$/pdk"/>
<excludeFolder url="file://$MODULE_DIR$/prebuilts"/>
<excludeFolder url="file://$MODULE_DIR$/sdk"/>
<excludeFolder url="file://$MODULE_DIR$/system"/>
<excludeFolder url="file://$MODULE_DIR$/tools"/>
<excludeFolder url="file://$MODULE_DIR$/kernel"/>

修改好配置后,使用Android studio打开android.ipr文件即可。接下来,介绍将代码导入Clion

// 设置环境变量,在编译时生成CMakeLists.txt文件
export SOONG_GEN_CMAKEFILES=1
export SOONG_GEN_CMAKEFILES_DEBUG=1

// 正常编译一次
cd ~/aosp12
source build/envsetup.sh
lunch aosp_blueline-userdebug
make -j$(nproc --all)

// 查看clion目录下面生成了大量的CMakeLists.txt
tree out/development/ide/clion/

// 在clion目录下创建一个CMakeLists.txt来合并导入需要使用的各个模块
touch out/development/ide/clion/CMakeLists.txt

// 配置CMakeLists.txt导入模块
vim out/development/ide/clion/CMakeLists.txt

// CMakeLists.txt文件添加下面的内容单独导入一个先
cmake_minimum_required(VERSION 3.6)
project(AOSP-Native)
// 添加子模块,导入了部分工程
add_subdirectory(frameworks/native)
add_subdirectory(art/dalvikvm/dalvikvm-arm64-android)
add_subdirectory(art/libdexfile/libdexfile-arm64-android)
add_subdirectory(art/runtime/libart-arm64-android)
add_subdirectory(bionic/libc/libc_bionic-arm64-android)
add_subdirectory(bionic/libc/libc_bionic_ndk-arm64-android)
add_subdirectory(bionic/libc/system_properties/libsystemproperties-arm64-android)
add_subdirectory(external/compiler-rt/lib/sanitizer_common/libsan-arm64-android)
add_subdirectory(frameworks/av/media/libaaudio/src/libaaudio-arm64-android)
add_subdirectory(frameworks/av/soundtrigger/libsoundtrigger-arm64-android)
add_subdirectory(frameworks/base/core/jni/libandroid_runtime-arm64-android)
add_subdirectory(frameworks/native/cmds/installd/installd-arm64-android)
add_subdirectory(frameworks/native/cmds/servicemanager/servicemanager-arm64-android)
add_subdirectory(frameworks/native/libs/binder/libbinder-arm64-android)
add_subdirectory(libcore/libjavacore-arm64-android)
add_subdirectory(libcore/libopenjdk-arm64-android)
add_subdirectory(libnativehelper/libnativehelper-arm64-android)
add_subdirectory(libnativehelper/libnativehelper_compat_libc++-arm64-android)
add_subdirectory(system/core/base/libbase-arm64-android)
add_subdirectory(system/core/init/libinit-arm64-android)
add_subdirectory(system/core/libziparchive/libziparchive-arm64-android)
add_subdirectory(system/core/liblog/liblog-arm64-android)
add_subdirectory(system/core/libcutils/libcutils-arm64-android)
add_subdirectory(system/core/libutils/libutils-arm64-android)
add_subdirectory(system/core/libprocessgroup/libprocessgroup-arm64-android)
add_subdirectory(system/core/logcat/logcatd-arm64-android)
add_subdirectory(system/core/logcat/liblogcat-arm64-android)
add_subdirectory(system/core/logd/logd-arm64-android)
add_subdirectory(system/core/logd/liblogd-arm64-android)
add_subdirectory(system/core/lmkd/liblmkd_utils-arm64-android)
add_subdirectory(system/core/lmkd/lmkd-arm64-android)

配置好cmake工程后,使用clion打开项目,选择配置好的CMakeLists.txt文件所在的目录out/development/ide/clion。导入成功后,修改工程的根目录,Tools->Cmake->Change Project Root,然后选择源码根目录即可。

2.9 gitlab配合repo管理源码

将源码导入idea中后已经可以正常的开始修改源码了。在日常的项目开中需要考虑到源码的管理便于随时能够查看自己的修改切换不同的分支进行开发。这样一个巨大的项目一个月后再想要查找当时修改的逻辑就非常困难了。如果你是个人开发并且修改的逻辑不是特别复杂或者是刚开始学习那么可以选择跳过这个部分内容。

首先,需要对repo进行一定的了解,在前文中,有简单的介绍到,repopython脚本实现的,是对git命令的封装用来管理大型项目关联多个子项目的。重新回顾一下下载Android代码的过程。前文中使用repo进行初始化指定分支,在完成初始化后,会在当前目录生成一个.repo的目录查看目录中的manifest.xml文件内容如下。

<?xml version="1.0" encoding="UTF-8"?>
<!--
DO NOT EDIT THIS FILE!  It is generated by repo and changes will be discarded.
If you want to use a different manifest, use `repo init -m <file>` instead.

If you want to customize your checkout by overriding manifest settings, use
the local_manifests/ directory instead.

For more information on repo manifests, check out:
https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
-->
<manifest>
  <include name="default.xml" />
</manifest>

可以看到内部其实导入了一个default.xml文件。查看这个配置文件。

<manifest>
  <remote  name="aosp"
           fetch=".."
           review="https://android-review.googlesource.com/" />
  <default revision="refs/tags/android-12.0.0_r3"
           remote="aosp"
           sync-j="4" />

  <superproject name="platform/superproject" remote="aosp" revision="android-12.0.0_r3" />
  <contactinfo bugurl="go/repo-bug" />

  <project path="build/make" name="platform/build" groups="pdk" >
    <copyfile src="core/root.mk" dest="Makefile" />
    <linkfile src="CleanSpec.mk" dest="build/CleanSpec.mk" />
    <linkfile src="buildspec.mk.default" dest="build/buildspec.mk.default" />
    <linkfile src="core" dest="build/core" />
    <linkfile src="envsetup.sh" dest="build/envsetup.sh" />
    <linkfile src="target" dest="build/target" />
    <linkfile src="tools" dest="build/tools" />
  </project>
  <project path="build/bazel" name="platform/build/bazel" groups="pdk" >
    <linkfile src="bazel.WORKSPACE" dest="WORKSPACE" />
    <linkfile src="bazel.sh" dest="tools/bazel" />
    <linkfile src="bazel.BUILD" dest="BUILD" />
  </project>
  <project path="build/blueprint" name="platform/build/blueprint" groups="pdk,tradefed" />
  <project path="build/pesto" name="platform/build/pesto" groups="pdk" />
  <project path="build/soong" name="platform/build/soong" groups="pdk,tradefed" >
    <linkfile src="root.bp" dest="Android.bp" />
    <linkfile src="bootstrap.bash" dest="bootstrap.bash" />
  </project>
  <project path="art" name="platform/art" groups="pdk" />
  <project path="bionic" name="platform/bionic" groups="pdk" />
  .....
</manifest>

这个文件的内容实际上是一份git仓库清单,repo init初始化的过程就是下载git仓库清单文件,以及下载repo工具的仓库也就是git-repo项目使用国内网络进行初始化时的速度非常慢的主要原因在于git-repo项目较大且必须通过外网访问很多读者使用国内源进行repo init前还需要通过设置环境变量REPO_URL修改git-repo的拉取地址。而repo sync步骤就是就是将清单文件中对应的子模块全部拉取下来。而default.xml中的元素主要为以下几种。

  1. manifest根元素所有元素都要定义再根元素中。
  2. remotegit仓库的地址以及名称。
  3. default仓库默认的属性比如路径、分支、远程仓库名称。
  4. project子模块仓库的名称、路径、默认分支等信息。
  5. remove-project需要从清单中删除的仓库。
  6. copyfile同步代码时要复制的文件和目录。
  7. include导入另外一个清单文件比如觉得一个清单看起来太复杂可以根据目录分类存放。 8. linkfile定义对应的文件或目录的软连接。

在配置文件中,有大量的project元素,在这里先记住以下信息,project元素中的path指的是项目拉取到本地之后存放的路径,name才是指在git仓库中存放的路径。

清楚了使用repo同步代码的原理,以及git清单中元素的作用后就可以开始搭建自己的Android源码远程仓库了。项目较大可以在本地搭建一个gitlab服务,然后将项目上传至gitlab中进行管理,如下是搭建gitlab服务的步骤。

// 安装gitlab服务的依赖
sudo apt-get update
sudo apt-get install -y curl openssh-server ca-certificates
sudo apt-get install -y postfix

// 信任gitlab的GPG公钥
curl https://packages.gitlab.com/gpg.key 2> /dev/null | sudo apt-key add - &>/dev/null

// 添加gitlab的源
vim /etc/apt/sources.list.d/gitlab-ce.list
// 加入下面的代码后保存
deb https://mirrors.tuna.tsinghua.edu.cn/gitlab-ce/ubuntu xenial main

// 安装gitlab-ce
sudo apt-get update
sudo apt-get install gitlab-ce

// 执行配置
sudo gitlab-ctl reconfigure

// 启动gitlab
sudo gitlab-ctl start

接下来在浏览器中输入局域网ip地址来访问gitlab页面,比如http://192.168.2.189/。然后注册一个账号。在登录的时候出现了下面这个错误

Your account has been blocked. Please contact your GitLab administrator if you think this is an error.

这是因为注册的账号需要审批激活后才能使用。回到终端上通过下面的命令激活账号

// 进入gitlab的控制台
gitlab-rails console

// 查找刚刚注册的账号
user = User.find_by_email("myuser@example.com")

// 将状态设为激活
user.state = "active"

// 保存修改并退出
user.save
exit

到这里gitlab服务准备就绪登录账号后就可以创建AOSP的子模块仓库了。

根据前面repo的介绍知道了源码一共是三个部分git-repo.git的工具仓库、manifests.git的子模块清单仓库、AOSP源码子模块仓库。接下来将代码同步的流程分割为下面几个步骤。

1. 参考.repo/manifests/default.xml配置修改为自己的gitlab地址并创建一个manifests.git仓库。

  1. 使用脚本批量创建子模块仓库。

3. 使用脚本批量同步子模块代码。

4. 使用自己的gitlab地址同步代码测试。

后面需要创建大量的子模块仓库不可能在web页面上手动一个个的创建下面使用命令来创建一个manifests.git仓库。这种方式需要gitlab账号的Access Token。可以在web中登录账号点击右上角的用户图标选择Preferences来到用户设置页面然后进入Access Tokens栏目填写token名称以及勾选权限最后点击生成例如生成的token为27zctxyWZP9Txksenkxb。流程见下图。

image-20230216211544482

首选在gitlab中手动创建一个根目录的group这里创建了一个android12_r3的组所有的子模块仓库都将在这个分组下。在gitlab页面中点击左上角Groups->your Groups。点击New group创建分组。成功创建后记录下这个分组的id比如我的根目录组id是6.

然后就可以使用curl命令通过token访问gitlab的API创建一个空白的仓库。

// 创建一个名称为manifests的空白仓库namespace_id是根目录的分组id
curl --header "PRIVATE-TOKEN: 27zctxyWZP9Txksenkxb" \
     --data "name=manifest&namespace_id=6" \
     --request POST "http://192.168.2.189/api/v4/projects"

接下来修改配置并且将清单项目上传到gitlab中

// 创建项目目录
mkdir manifest && cd manifest

// 拷贝安卓源码中的git子模块清单文件
cp ~/android_src/aosp12/.repo/manifests/default.xml ./

//编辑清单
vim default.xml

//修改内容如下
<manifest>

  // 修改name为orgin修改review为自己的服务器地址
  <remote  name="origin"
           fetch=".."
           review="http://192.168.2.189/" />
  // 修改remote为上面定义的name
  <default revision="master"
           remote="origin"
           sync-j="4" />
  // 修改remote为上面定义的name
  <superproject name="platform/superproject" remote="origin" revision="master" />
  <contactinfo bugurl="go/repo-bug" />
  .....
</manifest>

// 保存上面的修改,然后提交到仓库
git init
git remote add origin git@192.168.2.189:android12_r3/manifest.git
git add . && git commit -m "init"
git push

准备好清单文件后接下来创建所有子模块仓库了。首先需要了解有哪些子模块需要上传而这个通过default.xml中的project元素很容易查找到。编写一个python脚本来匹配出所有project中的path属性然后创建group和仓库。下面是一份读取default.xml文件自动创建所有仓库的代码。

#!/usr/bin/python3

import gitlab
import os
import re
import time
// 读取的配置文件
MANIFEST_XML = "default.xml"
ROOT = os.getcwd()
# gitlab中自己手动创建这个group
ROOT_GROUP = "android12_r3"
MANIFEST_XML_PATH_NAME_RE = re.compile(r"<project\s+path=\"(?P<path>[^\"]+)\"\s+name=\"(?P<name>[^\"]+)\"",
                                       re.DOTALL)
# 修改成自己的gitlab服务地址以及账号的token
gl = gitlab.Gitlab('http://192.168.2.189/', private_token='27zctxyWZP9Txksenkxb')

manifest_xml_project_paths = []

# 加载default.xml。取出所有需要创建的子仓库需要验证一下这里的数量和你的project是否一致。
def parse_repo_manifest():
    with open(os.path.join(ROOT, MANIFEST_XML), "rb") as strReader:
        for line in strReader:
            if line is not None and len(line) != 0:
                this_temp_line = line.decode()
                if line.find("path".encode(encoding="utf-8")):

                    s = MANIFEST_XML_PATH_NAME_RE.search(this_temp_line)

                    if s is not None:
                        manifest_xml_project_paths.append(s.group("name"))

    print("manifest_xml_project_paths=" + str(manifest_xml_project_paths))
    print("manifest_xml_project_paths len=" + str(len(manifest_xml_project_paths)))

# 创建组以及对应的子模块仓库
def create_group_and_project():
    all_groups = gl.groups.list(all=True)
    print("all_groups=" + str(all_groups))
    group_parent = None
	# 遍历所有组查找根组android12_r3是否存在
    for g in all_groups:
        if g.name == ROOT_GROUP:
            group_parent = g
            break
    print("group parent=" + str(group_parent))
	# 遍历所有子仓库路径
    for path in manifest_xml_project_paths:
        print("path=" + path)
        paths = path.split("/")
        print("paths=" + str(paths))

        last_path_index = len(paths) - 1

        group = group_parent
        for index in range(0, last_path_index):
            p = paths[index]
            print("p=" + p)
            # is the group exist
            print("parent group=" + group.name)
            try:
                all_groups = group.subgroups.list(all=True)
            except AttributeError:
                all_groups = []
                print("AttributeError: clear all subgroups")
			# 遍历所有组,找当前子模块的组是否存在
            is_group_exist = False
            for g in all_groups:
                if g.name == p:
                    is_group_exist = True
                    group = g
                    print("group exist=" + g.name)
                    break
            if is_group_exist:
                continue
            # create subgroup
            data = {
                "name": p,
                "path": p,
                "parent_id": group.id
            }
			# 不存在则创建子模块所属的组
            try:
                group = gl.groups.create(data)
                print("group create success name=" + p)
                time.sleep(1)
            except gitlab.exceptions.GitlabCreateError as e:
                if e.response_code == 400:
                    print("group:" + p + " has already been created")

                    query_groups = gl.groups.list(all=True)
                    print("query_groups:" + str(query_groups))
                    for query_group in query_groups:
                        if query_group.name == p and query_group.parent_id == group.id:
                            group = query_group
                            print("update exit group:" + group.name)
                            break
		# 创建子模块仓库,创建前先遍历是否仓库已存在
        project = paths[last_path_index]
        print("group project list group=" + group.name)
        real_group = gl.groups.get(group.id, lazy=True)
        all_projects = real_group.projects.list(all=True)
        print("group all projects=" + str(all_projects))
        is_project_exist = False
        for p in all_projects:
            if p.name == project:
                is_project_exist = True
                print("project exist=" + p.name)
                break
        if not is_project_exist:
            print("create project=" + project)
            gl.projects.create({'name': project, 'path': project, 'namespace_id': group.id})
            print("project create success name=" + project)
            time.sleep(1)

# 测试是否能成功创建
def test_create_project_with_dot_name():
    # need use path field, if don't use path, GitLab url will replace "." to "_"
    res=gl.projects.create({'name': "xxx.yy.xy", 'path': "xxx.yy.xy"})
    print(res)

if __name__ == '__main__':
    parse_repo_manifest()
    create_group_and_project()
    # test_create_project_with_dot_name()

子模块仓库建立完成最后还需要将代码上传到对应的仓库中。下面的代码可以完成这个工作。要注意的是default.xml文件中project元素的属性path的是本地路径,而name才是指的git仓库的路径,代码如下。

#!/usr/bin/python3

import os
import re,time
# 代码放在之前准备好的清单仓库中然后指定default.xml路径
MANIFEST_XML = "./manifests/default.xml"
ROOT = os.getcwd()
LOG_FILE_PATH = os.path.join(ROOT, "push.log")
# 匹配path
MANIFEST_XML_PATH_NAME_RE = re.compile(r"<project\s+path=\"(?P<path>[^\"]+)\"\s+name=\"(?P<name>[^\"]+)\"\s+",
                                       re.DOTALL)
# 设置源码路径
SOURCE_CODE_ROOT = "/home/king/android_src/android12_r3/"
# 设置gitlab仓库的根目录分组
REMOTE = "git@192.168.2.189:android12_r3/"
manifest_xml_project_paths = []

# 读取配置文件中的所有子模块路径
def parse_repo_manifest():
    with open(os.path.join(ROOT, MANIFEST_XML), "rb") as strReader:
        for line in strReader:
            if line is not None and len(line) != 0:
                this_temp_line = line.decode()
                if line.find("path".encode(encoding="utf-8")):

                    s = MANIFEST_XML_PATH_NAME_RE.search(this_temp_line)

                    if s is not None:
                        manifest_xml_project_paths.append({"path":s.group("path"),"name":s.group("name")})

    print("manifest_xml_project_paths=" + str(manifest_xml_project_paths))
    print("manifest_xml_project_paths len=" + str(len(manifest_xml_project_paths)))

# 上传源码
def push_source_code_by_folder(str_writer):
    # 遍历所有路径
    for path in manifest_xml_project_paths:
        print("path=" + path["path"])
        abs_path = SOURCE_CODE_ROOT + path["path"]
        # 路径存在则进行上传
        if os.path.exists(abs_path):
            # change current work dir
            os.chdir(abs_path + "/")
            # 1\. delete .git & .gitignore folder
            rm_git_cmd = "rm -rf .git"
            rm_gitignore_cmd = "rm -rf .gitignore"
            os.system(rm_git_cmd)
            os.system(rm_gitignore_cmd)

            # 2\. list dir
            dir_data = os.listdir(os.getcwd())

            cmd_list = []

            print("changed cwd=" + os.getcwd())

            if len(dir_data) == 0:
                echo_cmd = "echo \"This is a empty repository.\" > ReadMe.md"
                str_writer.write(f"empty repository:{abs_path}".encode() )
                str_writer.write("\r\n".encode())
                cmd_list.append(echo_cmd)
			# 将所有上传命令组装好
            git_init_cmd = "git init"
            cmd_list.append(git_init_cmd)

            git_remote_cmd = "git remote add origin " + REMOTE + path["name"] + ".git"
            cmd_list.append(git_remote_cmd)

            git_add_dot_cmd = "git add ."
            cmd_list.append(git_add_dot_cmd)

            git_commit_cmd = "git commit -m \"Initial commit\""
            cmd_list.append(git_commit_cmd)

            git_push_cmd = "git push -u origin master"
            cmd_list.append(git_push_cmd)
			# 执行上传命令
            for cmd in cmd_list:
                print("begin exec cmd=" + cmd)
                os.system(cmd)
                print("end exec cmd=" + cmd)
        else:
            print("abs_path=" + abs_path + " is not exist.")
            str_writer.write(f"folder not exist:{abs_path}".encode() )
            str_writer.write("\r\n".encode())

def wrapper_push_source_code_write_log():
    with open(LOG_FILE_PATH, 'wb+') as strWriter:
        push_source_code_by_folder(strWriter)
        strWriter.close()


if __name__ == '__main__':
    parse_repo_manifest()
    wrapper_push_source_code_write_log()

上传过程较慢等待所有仓库上传完成最后将git-repo工具子模块上传到仓库。首先在gitlab中创建一个分组android-tools。在分组中创建一个仓库git-repo。然后从github下载一份git-repo的工具源码传到gitlab。过程如下。

// 从github下载git-repo源码并上传到gitlab仓库
git clone https://github.com/GerritCodeReview/git-repo.git && cd git-repo
rm .git -rf
git init
git remote add origin git@192.2.189:android-tools/git-repo.git
git add .
git commit -m "init"
git push -u origin master

// 将这里的repo拿来使用
cp ./repo ~/bin/
PATH=~/bin:$PATH

终于一切准备就绪,那么开始拉取代码吧。

// 创建存放源码的目录
mkdir myandroid12 && cd myandroid12

// 指定使用自己的git-repo
repo init -u git@192.168.2.189:android12_r3/manifest.git --repo-url=git@192.168.2.189:android-tools/git-repo.git --no-repo-verify

// 出现了下面的错误
repo: error: unable to resolve "stable"
fatal: double check your --repo-rev setting.
fatal: cloning the git-repo repository failed, will remove '.repo/repo'

// 修改使用master分支再重新执行上面的repo init命令
export REPO_REV=refs/heads/master

repo init -u git@192.168.2.189:android12_r3/manifest.git --repo-url=git@192.168.2.189:android-tools/git-repo.git --no-repo-verify

//同步代码
repo sync -j8

在同步的过程中,可能出现以下两个问题。首先第一个是出现如下错误。

remote:
remote: ========================================================================
remote:
remote: The project you were looking for could not be found or you don't have permission to view it.
remote:
remote: ========================================================================
remote:
fatal: 无法读取远程仓库。

请确认您有正确的访问权限并且仓库存在。

platform/build/bazel:

检测代码后发现bazel仓库在路径build中不存在这个仓库被建立在了platform下。导致这个问题的原因是前面的创建git的脚本中发现build被指定为project,所以创建为仓库,而bazel必须是在一个group下,路径才会成立。而build的仓库已经存在,创建这个group失败后,就默认使用了更上一层的group。而解决办法也非常简单直接将default中的几个build路径下的几个project重新命名不要放在buildgroup下即可。下面是解决后的default.xml配置。

<project path="build/make" name="platform/build" groups="pdk" >
    <copyfile src="core/root.mk" dest="Makefile" />
    <linkfile src="CleanSpec.mk" dest="build/CleanSpec.mk" />
    <linkfile src="buildspec.mk.default" dest="build/buildspec.mk.default" />
    <linkfile src="core" dest="build/core" />
    <linkfile src="envsetup.sh" dest="build/envsetup.sh" />
    <linkfile src="target" dest="build/target" />
    <linkfile src="tools" dest="build/tools" />
</project>
<project path="build/bazel" name="platform/build_bazel" groups="pdk" >
	<linkfile src="bazel.WORKSPACE" dest="WORKSPACE" />
	<linkfile src="bazel.sh" dest="tools/bazel" />
	<linkfile src="bazel.BUILD" dest="BUILD" />
</project>
<project path="build/blueprint" name="platform/build_blueprint" groups="pdk,tradefed" />
<project path="build/pesto" name="platform/build_pesto" groups="pdk" />
<project path="build/soong" name="platform/build_soong" groups="pdk,tradefed" >
    <linkfile src="root.bp" dest="Android.bp" />
    <linkfile src="bootstrap.bash" dest="bootstrap.bash" />
</project>

另外一个问题也非常类似。错误如下。

请确认您有正确的访问权限并且仓库存在。
device/mediatek/wembley-sepolicy: sleeping 4.0 seconds before retrying

这是由于这个仓库在default.xml中的配置如下

<project name="device/mediatek/wembley-sepolicy" path="device/mediatek/wembley-sepolicy" groups="device"/>

看了创建仓库和批量提交代码的逻辑就明白了,是的,namepath的顺序反了,导致正则表达式未能成功匹配到这个仓库,调整一下namepath的顺序即可。

成功拉取完成后,如果在编译时碰到找不到文件的问题,这是由于有些子模块仓库下的子目录中有.gitignore文件,将一些应该提交的文件给过滤掉了。回到同步代码的目录中,找到指定的git仓库,使用下面的方式重新提交一下。回到同步下来的代码处,重新拉取更新的代码。

// 进入缺少文件的子模块仓库目录
cd ~/external/angle/
git add . -f
git commit -m "init"
git push -u origin master
cd ~/android_src/myandroid12/
repo sync -j8

到这里就完成了gitlab源码管理AOSP源码开发了。最后如何使用git提交和查看历史记录我就不在这里叙述了。

2.10 小结

本章主要讲述了如何从零开始在Windows系统与Ubuntu系统中搭建一个编译Android源码的环境。在环境搭建好后讲解了如何选择合适的需要开发修改的AOSP版本在系统版本确定后讲解了拉取代码并编译的流程接着讲解了为系统集成自己编译的内核代码然后将编译好的系统镜像尝试多种方式刷入测试手机设备中。

在编译与刷机完成后,为后续开发和阅览代码做准备。讲述了如何使用Android StudioClion导入源码。

最后,为了便于工程的长期维护和持续性的开发,讲解了搭建gitlab配合repo管理Android源码。

终于将一切准备就绪了。本章的内容操作性强,不同步骤之间由于各种原因可能会出现错误,出现操作流程被打断的情况下,不要急躁,冷静分析原因,结合网络上的搜索结果,发现定位并解决问题后,重复操作,直到编译刷机完成,配置好环境,为后面的课程做好准备。