--- title: 'Android 下載源碼、編譯系統' disqus: kyleAlien --- Android 下載源碼、編譯系統 === ## OverView of Content 如有引用該文章請標明引用,謝謝 :smile_cat: 以下是在 Ubuntu 20 進行 [TOC] ## Android Source 1. 個人使用 Ubuntu (`22.0.4` 版本) 2. 先使用 apt 安裝以下工具 ```shell= sudo apt upgrade sudo apt update sudo apt install -y \ vim \ git-core \ gnupg \ flex \ bison \ gperf \ build-essential \ zip \ unzip \ 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 \ libncurses5 \ libelf-dev \ libswitch-perl ``` 3. **安裝 google's repo 工具**來下載 Android Source、Android Kernel code :::info * **確認 Python**,如果沒安裝請按照以下步驟先進行安裝 ```shell= # 必須使用 python3 (2 已經不再支援) sudo apt install python3 # 創建 symbol 連結為 python (因為 repo 是使用 python 關鍵字) sudo ln -s /usr/bin/python3 /usr/bin/python ``` ::: * 下載安裝 repo 工具 ```shell= # 定義 REPO 變量 export REPO=$(mktemp /tmp/repo.XXXXXXXXX) # 下載指定 Url 到 REPO 中 curl -o ${REPO} https://storage.googleapis.com/git-repo-downloads/repo # gpg Key gpg --recv-key 8BB9AD793E8E6153AF0F9A4416530D5E920F5C65 # 創建 /bin 資料夾 mkdir ~/bin # 下載 repo 到 /bin 資料夾中 curl -s https://storage.googleapis.com/git-repo-downloads/repo.asc | gpg --verify - ${REPO} && install -m 755 ${REPO} ~/bin/repo ``` :::warning * `gpg` 驗證失敗 couldn't connect ```shell= gpg: requesting key 920F5C65 from hkp server keys.gnupg.net ?: keys.gnupg.net: Host not found gpgkeys: HTTP fetch error 7: couldn\'t connect: Success gpg: no valid OpenPGP data found. gpg: Total number processed: 0 ``` 其中可以替過替換金鑰伺服器來修復 ```shell= gpg --keyserver hkp://keyserver.ubuntu.com --recv-key 8BB9AD793E8E6153AF0F9A4416530D5E920F5C65 ``` ::: * 添加到當前 User 的環境變數 ```shell= vim ~/.bashrc # 添加內容如下 -------------------------------------------------- export PATH=$PATH:/home/alien/bin # --------------------------------- ## 編輯完後刷新 `.bashrc` source ~/.bashrc ``` * 使用以下指令確認 repo 是否安裝成功 ```shell= # 檢查 repo 版本 repo version ``` > ![image](https://hackmd.io/_uploads/ry_ZJkkUp.png) ### 下載 [Android Source](https://source.android.com/setup/build/downloading) 1. **創建 source 資料夾**:這個資料夾用來儲存 Android Source code ```shell= mkdir android_source && cd android_source ``` :::info * 確認有設定 git 的基本資訊,`name`、`email` ```shell= git config --global user.name <Your Name> git config --global user.email <you@example.com> ``` ::: 2. `repo init`:指定你要下載的 url & branch ([**branch name**](https://source.android.com/setup/start/build-numbers#source-code-tags-and-builds) 可以參考官方文檔) ```shell= ## 這裡下載 android-12.0.0_r1 版本 repo init -u https://android.googlesource.com/platform/manifest \ -b android-12.0.0_r1 ## 還有 android-10.0.0_r1 版本 repo init -u https://android.googlesource.com/platform/manifest \ -b android-10.0.0_r1 ``` > 產生 `.repo` 目錄 > > ![](https://i.imgur.com/XLyXYgY.png) :::success * 可以透過查看 `.repo/manifests/default.xml` 檔案來查看當前的設置 ```shell= cat .repo/manifests/default.xml | head -n 10 ``` > ![](https://hackmd.io/_uploads/H1XdbUFG6.png) ::: 3. `repo sync`:sync 關鍵字就是開始下載 ```shell= repo sync ## 如果想加速則可以指定 -j (線程使用數量)、-c、-q (安靜模式) repo sync -c -j12 ## 使用 12 核下載 ``` * 下載完後接近 192G,資料相當龐大~ 請分配好空間 (Build 後會更耗費空間) > ![](https://i.imgur.com/DzlgpcS.png) ### 編譯 Android Source :::info 建議使用真的 Ubuntu 編譯,我使用 WSL's Ubuntu 編譯出來的 emulator 不能正常使用 ::: :::danger * 如果是使用 `Window WSL ubuntu` 系統,請注意容量分配,一般來說預設容量是不足的,所以必須將 ubuntu 擴容 (自身預設為 250G,這裡我將它擴容到 512G),[**官方擴容文檔**](https://docs.microsoft.com/zh-tw/windows/wsl/vhd-size) 以下操作請使用 **系統管理員身分** 開啟 `powershell` 1. 查看 WSL ubuntu 的硬碟區塊 ```shell= Get-AppxPackage -Name "*Ubuntu*" | Select PackageFamilyName ``` > ![](https://i.imgur.com/PowDXTo.png) 2. 找到 `pathToVHD` 完整路徑: ```shell= ## 路徑格式 %LOCALAPPDATA%\Packages\<PackageFamilyName>\LocalState\<disk>.vhdx ## <PackageFamilyName> 就是我們第一步驟中所查的目標 ## <disk> 則是 ubuntu 分配的 disk ``` 3. 在 disk 模式下指定 ubuntu disk ```shell= ## 進入 disk 模式 diskpart ## 在該模式下選定要操作的硬碟區塊 ## <pathToVHD> 則是步驟 2 中的 "完全路徑" Select vdisk file="<pathToVHD>" ## 查看該硬碟的分配狀況 detail vdisk ## 拓展大小為 512M expand vdisk maximum=512000 ## 退出 disk 模式 exit ``` 4. ubuntu 拓展:雖然上面 window 已經分配更大空間給 ubuntu,但 ubuntu 仍需重新指定硬碟大小 ```shell= ## 查看當前硬碟 sudo mount -t devtmpfs none /dev mount | grep ext4 ## 重新指定大小 512M ## 以下的 /dev/sd<字母> 是透過上面查詢出來的 sudo resize2fs /dev/sdc 512M ``` ::: 1. **初始化環境**:在 Android 下載好的根資料夾下達以下命令 ```shell= ## 移動到下載的資料夾內 cd ~/android_source source ./build/envsetup.sh make clobber ## 編譯前清除 build 資料夾 ``` 2. **lunch 選擇編譯目標**:選擇要編譯的目標,**格式組成是 `BUILD`、`BUILDTYPE`** * Build 是指編譯完後要運行的平台 | BUILD | 目標設備 | 補充 | | -------- | -------- | -------- | | Full | 模擬器 | 全編譯,包含所有語言、應用、輸入法... 等等 | | full_manguro | manguro | 全編譯,運行於 Galaxy Nexus GSM/HSPA+("manguro") | | full_panda | panda | 全編譯,運行於 PandaBoard("panda") | * BUILDTYPE 則分為三種類型 | BUILDTYPE | 功能特點 | | -------- | -------- | | user | 發佈到市場上的版本,**預設沒有 root 權限、adb 關閉** | | userdebug | **開放 root、adb 權限**,一般用於真機調適 | | eng | 擁有最大權限 (**root**),並具有額外調適工具,**一般用於模擬器** | > 有再細分為 `x86`、`x86_64`... 等等 ```shell= # 查看所有可編譯的版本類型 lunch lunch 39 # 選擇 `aosp_x86-eng` 版本 ``` > ![reference link](https://i.imgur.com/s0gJjro.png) 3. `make` 編譯:可以按照自己電腦的 CPU 核心數量來編譯 ```shell= # 目前指定 12 核心 (全力編譯) # make -j$(nproc) make -j12 ``` > ![](https://i.imgur.com/jnWmxwD.png) :::success * **`sdk_phone` 的選項** Android 12 的 lunch 沒有輸出 `sdk_phone` 的選項,但這部分仍可以在 `build/target/product` 資料夾那找到,所以仍可以指定 > ![](https://i.imgur.com/jTkkpH9.png) ```shell= ## 隱藏選項 lunch sdk_phone_x86 ## repo sync -c -j12 make -j$(nproc --all) ``` > ![](https://i.imgur.com/kbl30S1.png) ::: ### Android Source 單編譯 - 模組 * 除了全部編譯以外,我們可以使用單單編譯某一個應用模塊 (eg. `Settings` 應用模塊) 1. 在 Android Source 根目錄中,執行以下命令 ```shell= source ./build/envsetup.sh lunch 39 ``` 2. 進入 Setting 目錄下,使用 `mm` 編譯 ```shell= ## 模塊編譯 (目前編譯 Settings 模塊) mmm packages/apps/Settings ``` 除了 `mm` 指令以外,還可以使用其他命令單編譯 | 指令 | 功能 | | -------- | -------- | | mm | 編譯當前目錄下 Module(必須進入指定工程的目錄中才能編譯) | | mmm | 編譯指定目錄下 Module,**但不編譯其依賴的 Module** (可以在 Android 源代碼的目錄結構中的任意級目錄,編譯任意指定的工程) | | mma | 編譯當前目錄下 Module,**包含其依賴的 Module** | | mmma | 編譯 **指定路徑下的 ++所有 Module++**,並包含其依賴 | > ![](https://hackmd.io/_uploads/Sk4_X8HGT.png) :::info * 在執行完 `source ./build/envsetup` 命令後,就可以使用 `help` 指令查看還有哪些命令可用 ::: * 範例:**單編譯 Setting 模塊後的輸出** > ![](https://i.imgur.com/peqJNpi.png) 使用編譯好的 apk 有兩種方式 1. `adb push`、`adb install` 命令安裝 APK 2. 使用 `make snod` 命令,生成新的 `system.img`,然後運行模擬器 ### 下載 [Android Kernel](https://source.android.com/setup/build/building-kernels#downloading) * **Android Kernel 並不存在 Android Source 中**,需要另外下載 (同樣 **可以使用 repo 工具**) :::warning * 選擇的內核版本要選用與模擬器版本盡量一致(否則可能模擬器無法運行) 像是 Pixel 3a XL 的核心就要使用 `android-msm-bonito-4.9-android12L` 核心 > ![image](https://hackmd.io/_uploads/SyILKez86.png) ::: 1. **創建 kernel 資料夾**:這個資料夾用來儲存 Android Kernel code ```shell= mkdir android_kernel && cd android_kernel ``` 2. `repo init`:指定你要下載的 url & branch ([**branch name**](https://source.android.com/setup/start/build-numbers#source-code-tags-and-builds) 可以參考官方文檔、[**Android source Manifest 文件**](https://android.googlesource.com/kernel/goldfish/+refs)、[**Kernel 文件**](https://source.android.com/docs/setup/build/building-kernels#downloading)) ```shell= ## 這裡下載 android-gs-raviole-5.10-android12L 版本 # (其他版本請看官網提供) repo init -u https://android.googlesource.com/kernel/manifest \ -b android-gs-raviole-5.10-android12L ``` > 產生 `.repo` 目錄 > > ![](https://i.imgur.com/XLyXYgY.png) 3. `repo sync`:sync 關鍵字就是開始下載 ```shell= repo sync ## 如果想加速則可以指定 -j (線程使用數量)、-c、-q (安靜模式) repo sync -c -j12 ## 使用 12 核下載 ``` > ![](https://i.imgur.com/gHKSU4m.png) ### 編譯 Android Kernel * 首先 apt 安裝 tool 編譯 Android kernel 時必須的一些工具 ```shell= ## 多安裝 openssl tool sudo apt install libssl-dev ``` * 進到 Android Kernel 下載好的資料夾根目錄中,並執行以下命令 ```shell= ## 進入資料夾 cd ~/android_kernel ## 編譯命令 ./build/build.sh ``` 最終編譯結果 > ![](https://i.imgur.com/yrFtxX3.png) ### 下載並編譯 Emulator Kernel - 手動指定並編譯 * Android Kernel 版本的不同 * 在 Android 10 (`Version Q`, `Kernel 4.14`) 之前 Android Kernel 把所有核心 `built-in` 到 **bzImage** 檔案中 * 從 Android 11 (`Version R`) 之後 (包含),為了硬體需求,**將部分核心驅動編譯到 `System Image` 中** * 編譯範例、順序: 下載 goldfish 版本編譯、並指定給模擬器作為核心 1. **`clone goldfish`** (Android Emulator 使用的 Kernel) ```shell= git clone https://android.googlesource.com/kernel/goldfish/ -b android-goldfish-4.14-dev.150 ``` 2. **`clone gcc` 編譯工具** ```shell= git clone https://android.googlesource.com/platform/prebuilts/gcc/linux-x86/x86/x86_64-linux-android-4.9 -b android10-release ``` 3. **將 gcc 編譯工具,加入臨時路徑到 Path 中** ```shell= export PATH=$PATH:$PWD/x86_64-linux-android-4.9/bin ``` 4. **設定編譯參數 `ARCH`、`CROSS_COMPILE`,並執行 make 編譯** ```shell= # 進入 kernel source cd goldfish make clean make mrproper # default setting(.config file) make ARCH=x86_64 x86_64_ranchu_defconfig make ARCH=x86_64 -j$(nproc --all) # 也可以手動設定 CROSS_COMPILE # ## make -j$(nproc --all) ARCH=x86_64 CROSS_COMPILE=x86_64-linux-android- ``` :::warning * 編譯錯誤 **multiple definition of `yylloc`**,這是因為電腦中有多個 gcc,但是目前編譯的 Kernel 並不能使用,這時須將 gcc 調整為 gcc9 ```shell= sudo apt install gcc-9 sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-9 100 ``` * `Can't find default configuration x86_64_ranchu_defconfig` 錯誤? * 每個 goldfish 版本的 Config 設定都也些許不同,請查看 `<android_kernel>/arch/<目標_cpu>/` 的目錄下有哪些設定 > 像是 goldfish 3.4 版本就沒有這個設定檔 > > ![image](https://hackmd.io/_uploads/HJZlLAfUp.png) * goldfish 模擬器則可以指定 `goldfish_armv7_defconfig` * `Cannot use CONFIG_CC_STACKPROTECTOR_STRONG` 錯誤:因為編譯本身不支援,所以要先 disable ```shell= scripts/config --disable CC_STACKPROTECTOR_STRONG ``` ::: * 運行模擬器,並設定指定 Kernel ```shell= # 當前使用 Android SDK 的 emulator emulator -avd Pixel_3_XL_API_29 -kernel ~/Desktop/android_kernel/goldfish/arch/x86_64/boot/bzImage & ``` :::danger * 由於這裡下載 `goldfish-4.14` 版本,所以最高就使用 Android 10 來運行,超過 Android 10 的版本就無法正常運行 !! ::: 驗證 Android Emulator 是否是正確 Kernel ```shell= adb shell cat /proc/version ``` * **Original Kernel Version** > ![](https://i.imgur.com/FTJxS8K.png) * **Custome Kernel Version** > ![](https://i.imgur.com/ynsEaQ4.png) ### 模擬器運行 * 如果你已經手動編譯完成,就不需要指定,直接在 Android source root 根資料夾下使用以下指令即可 > 如果你尚未編譯完,是無法直接使用 `emulator` 命令的 ```shell= source ./build/envsetup.sh lunch 39 emulator ``` > ![](https://i.imgur.com/SAQWY2x.png) :::warning * 無法在 ViertualBox 運行 (結果好像還是不行...) 由於這裡是使用 Oracle VM VirtualBox 運行 Ubuntu,運行模擬器需要虛擬化技術,這裡就需要 **設定巢狀虛擬化技術** 1. 先關閉 Ubuntu VM 系統 2. Window cmd 移動到 `C:\Program Files\Oracle\VirtualBox`,並執行以下命令 ```shell= ## 查詢 VM 系統名 VBoxManage.exe list vms ## 指定系統名,開啟虛擬化 VBoxManage.exe modifyvm "Ubuntu" --nested-hw-virt on ``` > ![](https://i.imgur.com/N77ibSJ.png) ::: :::danger * **缺失 `userdata-qemu.img`**: 這是缺失設備 SDK 導致 ! 切換到 lunch 編輯指定 sdk 版本,並重新指定下載、編譯 > ![](https://i.imgur.com/zUMlOck.png) ```shell= ## 切換到 lunch 66 lunch 66 repo sync -c -j12 make -j12 ``` > ![](https://i.imgur.com/FybRAzx.png) ::: ## Makefile 請看另外一篇 [**make 編譯管理**](https://hackmd.io/KrGjNjsGQeODDRZeCBqlRg) ## Android 編譯系統 Google 在 2006 年收購 Android 後有對編譯系統重新大整理一次,核心目標是 **==提升編譯效率==** * 讓依賴關係更可靠,以保證可以正確判斷出需要被編譯的模塊 * 不必被編譯的模塊也可以被判斷出來 ### 特色 Android 遵循了多個設計原則 & 策略,包括但不限於以下幾點 1. 同一套程式編譯出不同的建構目標,就像是 Linux 可以編譯出 Window 專用的模擬器 or 開發包等等,也稱為 **交叉編譯** 2. Non-Recursive Make:1997 年論文<<Recursive Make Considered Harmful\>>,其思想是 **在大型項目中應採用唯一的 Makefile 來組織所有文件的自動化編譯** 3. 可以單獨對 **++單一模塊進行驗證++**,不需要每次編譯全部項目 4. 編譯所產生的 ++中間文件++ & ++最終的編譯結果++ & ++Source Code++ 分離在不同的目錄上 ### Makefile 依賴樹 - 概念 * 在前面小節有示範如何使用 Makefile (SimpleMakefile),現在來看看這個範例**每個 TARGET 的依賴關係** (箭頭表示了依賴的對象) > ![](https://i.imgur.com/AjOoEAJ.png) 由這張圖可以看到,由上而下的分析方式,Android 編譯系統也可以使用相同的方式分析 ### Android 編譯系統抽象模型 * 因為是基於 Makefile 實現,整個編譯系統的核心仍是如何建構出有效的依賴樹 > ![](https://i.imgur.com/gP03NRw.png) 1. **初始化環境**:由於 Android 編譯過程涉及 Java、C/C++ 等等多種語言,而且還有分 Host & Target 平台,所以它的運行環境稍微複雜,需要我們在初始化環節做好環境搭建 (e.g 當前 DSK & Make 版本是否符合需求) 2. **依賴樹、編譯**:編譯的執行和傳統 Make 並無太多差異 3. **打包**:編譯系統的另外一個重要任務就是打包 (e.g `system.img`、`boot.img`、`userdata.img`...) ### Root node - droid 依賴樹 * 根結點也就是 Makefile 的 Target,也是編譯系統最終要產生的目標,但它是一個 **==偽目標==**,因為在編譯過程產生的其他檔案也是不同的 Targets 從 Android Source 的根目錄分析起,其下的 **Makefile 文件是編譯系統的起點(`build/core/Makefile`)**,它是一個簡單的文件轉向,直接引用了另一個 Makefile 文件 ```shell= # 這邊在 Source 沒有找到,在書本找到而已 include build/core/main.mk ``` * 這邊需要注意 **兩點** 1. 編譯系統中往往 **++不只一顆依賴樹++** (Android 系統下 make、make sdk 編譯的結果就不相同),沒有指定編譯目標時,第一個會被默認為 Make 的根結點 2. Make 程序會對 Makefile 中的內容進行順序解析,解析的過程有三個大步驟, * 變量賦值 & 環境檢測 & 初始化 * 按照規則生成依賴樹 * 根據用戶選擇的依賴樹從 **++葉到根++** 生成目標文件 (類似先有零件,在組成目標配備) :::warning * 如果源碼沒有調整,再次編譯,就會返回 `Nothing to be done for droid'`;不代表編譯失敗 > ![image](https://hackmd.io/_uploads/SkKDPHQ8a.png) ::: * 接下來看看 [**main.mk**](https://android.googlesource.com/platform/build/+/master/core/main.mk),找出 `make` 命令對應的依賴樹的根結點,其 **根結點就是 ==droid==** ```shell= ## build/core/main.mk 檔案 ## This is the default target. It must be the first declared target. .PHONY: droid DEFAULT_GOAL := droid $(DEFAULT_GOAL): droid_targets ``` **這裡的 droid 還是一個空殼**,相當於預先佔位,但實作部分會分為其他不同的路線 ```shell= # 1 判斷是否是 ANDROID_BUILD_EVERYTHING_BY_DEFAULT ifeq (true,$(ANDROID_BUILD_EVERYTHING_BY_DEFAULT)) droid: checkbuild # 2 執行條件 .PHONY: checkbuild # 以下有三個條件 checkbuild: $(modules_to_check) droid_targets check-elf-files # 3 .PHONY: apps_only ifneq ($(TARGET_BUILD_APPS),) # 編譯 APP # 3-1 apps_only: $(unbundled_build_modules) droid_targets: apps_only else ifeq (,$(TARGET_BUILD_UNBUNDLED)) # 編譯整個系統 # 3- 接下來會繼續分析 droidcore 產生 droid_targets: droidcore dist_files ``` * 接下來主要針對 **droidcore**、**dist_files** 進行分析 **--根依賴樹--** > ![](https://i.imgur.com/P53hwqo.png) ### droidcore 節點 * 在編譯整個 Android 系統時,droid(空殼)依賴於 **droidcore** & **dist_files**,這個小節先關注 **==droidcore== 的生成** ```shell= # Build files and then package it into the rom formats .PHONY: droidcore droidcore: $(filter $(HOST_OUT_ROOT)/%,$(modules_to_install)) \ $(INSTALLED_SYSTEMIMAGE_TARGET) \ $(INSTALLED_RAMDISK_TARGET) \ $(INSTALLED_BOOTIMAGE_TARGET) \ $(INSTALLED_RADIOIMAGE_TARGET) \ $(INSTALLED_DEBUG_RAMDISK_TARGET) \ $(INSTALLED_DEBUG_BOOTIMAGE_TARGET) \ $(INSTALLED_RECOVERYIMAGE_TARGET) \ $(INSTALLED_VBMETAIMAGE_TARGET) \ $(INSTALLED_VBMETA_SYSTEMIMAGE_TARGET) \ $(INSTALLED_VBMETA_VENDORIMAGE_TARGET) \ ... 省略部份 (還有很多 TARGET $(INSTALLED_FILES_FILE_ROOT) \ $(INSTALLED_FILES_JSON_ROOT) \ $(INSTALLED_FILES_FILE_RECOVERY) \ $(INSTALLED_FILES_JSON_RECOVERY) \ $(INSTALLED_ANDROID_INFO_TXT_TARGET) \ soong_docs ``` 來看看幾個重點 Prerquisite | Prerquisite | 描述 | 功能 | | -------- | -------- | -------- | | INSTALLED_BOOTIMAGE_TARGET | .PHONY: bootimage bootimage: $(INSTALLED_BOOTIMAGE_TARGET) | 生成 system.img | | INSTALLED_RECOVERYIMAGE_TARGET | - | 生成 recovery.img | | INSTALLED_CACHEIMAGE_TARGET | .PHONY: cacheimage cacheimage: $(INSTALLED_CACHEIMAGE_TARGET) | 生成 cache.img | | INSTALLED_VENDORIMAGE_TARGET | .PHONY: vendorimage vendorimage: $(INSTALLED_VENDORIMAGE_TARGET) | 生成 verdor.img | | INSTALLED_FILES_FILE | - | 生成 installed-files.txt,用於紀錄當前系統中預安裝的程序、Lib & Module | * **modules_to_install**:描述了系統需要安裝的模塊,主要模塊有 **product_target_FILES** ```shell= # /core/main.mk 檔案 # 1. All the droid stuff, in directories .PHONY: files files: $(modules_to_install) \ $(INSTALLED_ANDROID_INFO_TXT_TARGET) # 2. 描述了系統需要安裝的模塊,分成了 5 個模塊 modules_to_install := $(sort \ $(ALL_DEFAULT_INSTALLED_MODULES) \ $(product_target_FILES) \ $(product_host_FILES) \ $(CUSTOM_MODULES) \ ) # 3. 分析 product_target_FILES 模塊 ifdef FULL_BUILD product_target_FILES := $(call product-installed-files, $(INTERNAL_PRODUCT)) else product_target_FILES := # 4. 分析 product-installed-files # 在 Android 編譯機制中模塊可以指定 eng、debug、tests、asan、java_coverage $(eval _pif_modules := \ $(call get-product-var,$(1),PRODUCT_PACKAGES) \ $(if $(filter eng,$(tags_to_install)),$(call get-product-var,$(1),PRODUCT_PACKAGES_ENG)) \ $(if $(filter debug,$(tags_to_install)),$(call get-product-var,$(1),PRODUCT_PACKAGES_DEBUG)) \ $(if $(filter tests,$(tags_to_install)),$(call get-product-var,$(1),PRODUCT_PACKAGES_TESTS)) \ $(if $(filter asan,$(tags_to_install)),$(call get-product-var,$(1),PRODUCT_PACKAGES_DEBUG_ASAN)) \ $(if $(filter java_coverage,$(tags_to_install)),$(call get-product-var,$(1),PRODUCT_PACKAGES_DEBUG_JAVA_COVERAGE)) \ $(call auto-included-modules) \ ) \ ... 省略部份 endef # 5. tags_to_install,在這邊會過濾出需要安裝的模塊 ifeq ($(TARGET_BUILD_VARIANT),eng) tags_to_install := debug eng ifneq ($(filter ro.setupwizard.mode=ENABLED, $(call collapse-pairs, $(ADDITIONAL_SYSTEM_PROPERTIES))),) # Don't require the setup wizard on eng builds ADDITIONAL_SYSTEM_PROPERTIES := $(filter-out ro.setupwizard.mode=%,\ $(call collapse-pairs, $(ADDITIONAL_SYSTEM_PROPERTIES))) \ ro.setupwizard.mode=OPTIONAL endif ``` ### dist_file 節點 * 這是 Android 編譯系統時的另外一個節點,整個編譯項目指出現了一次,如果開啟這個開關,**其功能是在 out 目錄下產生專用的 dist 資料夾,用於儲存多種分發包(類似多渠道)** ```shell= # dist_files only for putting your library into the dist directory with a full build. .PHONY: dist_files ``` ## [**main.mk**](https://android.googlesource.com/platform/frameworks/base/+/cd92588/Android.mk) 解析 * 在分析 droidcore、dist_files 之前,**先分析其架構**。除了建構 droid 等等依賴外,main 有一大半的內容是為了完成以下幾點 1. 對編譯環境的檢查 2. 必要文件的前期處理 3. 引用其他 MakeFile 檔案 (Source 中有多個 Makefile 檔案,在整個 main.mk 中可以看到許多引用) > eg. config.mk、cleanbuild.mk 4. 設定全局變量 5. 函數實現 (Android 編譯系統中有時現許多有用的函數) eg. print-vars 用來打印變量列表,my-dir 可以知道當前的路徑位置 * 以下是 mk 檔之間的依賴調用關係 (時序圖) > ![](https://i.imgur.com/BdRa83V.png) * 如果需要定之一款 Android 設備,需要定義以下幾個檔案,^1^ verdorsetup.sh,^2.^ AndroidProducts.mk ,^3^ BoardConfig.mk,^4^ Android.mk | mk 檔案 | 功能 | | -------- | -------- | | **javac.mk** | 選取適合的 java 編譯器 | | **envsetup.mk** | 環境變量的配置 | | **AndroidProducts.mk** & **BoardConfig.mk** | 訂製設備 | | **Android.mk** | 可以從上面關係圖看出它是最後被 main.mk 調用的,它代表了 **零件** 的組成建構 | ### [java.mk](https://android.googlesource.com/platform/build/+/master/core/java.mk) 介紹 * 負責與 java 語言相關的編譯實現,是 `java_library.mk` 的基礎 * java.mk 中定義了多個中間和最中產物 ```shell= ## core/java.mk ## 中間產物 LOCAL_INTERMEDIATE_TARGETS LOCAL_INTERMEDIATE_TARGETS += \ $(full_classes_turbine_jar) \ $(full_classes_compiled_jar) \ $(full_classes_jarjar_jar) \ $(full_classes_jar) \ $(full_classes_combined_jar) \ $(built_dex_intermediate) \ $(built_dex) \ $(full_classes_stubs_jar) \ $(java_source_list_file) ``` * 使用 **全局變量**,最終產生我們需要的檔案 > ![](https://i.imgur.com/DbJW52E.png) | 全局變量 | 最終產物 | 規則 | | -------- | -------- | -------- | | full_classes_compiled_jar | classes-full-debug.jar | ![](https://i.imgur.com/912rB4b.png) | | full_classes_processed_jar | classes-processed.jar | ![](https://i.imgur.com/lvtBZf0.png) | | full_classes_jarjar_jar | classes-jarjar.jar | ![](https://i.imgur.com/oP9XErA.png) | | full_classes_jar | classes.jar | ![](https://i.imgur.com/qphoz9a.png) | | full_classes_combined_jar | classes-combined.jar | ![](https://i.imgur.com/jUvbD99.png) | | built_dex_intermediate | classes.dex | ![](https://i.imgur.com/BaWEZcT.png) | | built_dex | classes.dex | ![](https://i.imgur.com/wGnaI7O.png) | | full_classes_stubs_jar | stubs.jar | ![](https://i.imgur.com/rplQt9w.png) | | java_sources_deps | Non | ![](https://i.imgur.com/bQEprL0.png) | | java_source_list_file | java-source-list | ![](https://i.imgur.com/EvSSJtI.png) | | full_classes_turbine_jar | classes-turbine.jar | ![](https://i.imgur.com/TJs8yOq.png) | * 從上面可以看到許多的變數是相互依賴的關係,如下圖 > ![](https://i.imgur.com/uWSKY4c.png) ## Android.mk 編寫規則 Android.mk 檔案在 source code 中有大量的出現,引用方式就在 [**main.mk**](https://android.googlesource.com/platform/build/+/master/core/main.mk) 中,Android.mk 對於系統的拓展很有幫中,讓需要的廠商不必了解整個系統的編譯,就可添加系統應用 ```shell= # # Include all of the makefiles in the system # subdir_makefiles := $(SOONG_ANDROID_MK) $(file <$(OUT_DIR)/.module_paths/Android.mk.list) $(SOONG_OUT_DIR)/late-$(TARGET_PRODUCT).mk subdir_makefiles_total := $(words int $(subdir_makefiles) post finish) .KATI_READONLY := subdir_makefiles_total # 遍歷 && include $(foreach mk,$(subdir_makefiles),$(info [$(call inc_and_print,subdir_makefiles_inc)/$(subdir_makefiles_total)] including $(mk) ...)$(eval include $(mk))) ``` **在編譯整個系統的情況下,系統所找到所有的 ==Android.mk 會先存入 subdir_makefiles== 這個變量中,之後一次性 include 進系統中** ### mk 檔 - 設置參數 * Android mk 檔常見的 **參數如下表** | 變量名稱 | 說明 | 補充 | | -------- | -------- | - | | `LOCAL_PATH` | 用於確定源碼所在的目錄,最好把它放在 `CLEAR_VARS` 變量引用之前,因為 `LOCAL_PATH` 不會被清除,每個 Andriod.mk 只須定義一次 | | | `CLEAR_VARS` | 它清空很多以 "`LOCAL_`" 開頭的變量(`LOCAL_PATH` 除外),Makefile 都是在一個編譯環境中執行的,因此變量的定義理論上都是全局,每個 Module 前進行清理工作 | | | `LOCAL_SRC_FILES` | 模塊編譯過程所涉及的原文件,如果是 Java 程序,可以考慮要 `all-subfir-java-files` 來一次性添加目錄(包括子目錄)下所有的 java 文件,因為有 `LOCAL_PATH` 所以這裡只要給出相對路徑 | | | `LOCAL_CFLAGS` | C 語言編譯時的而外選項 | | | `LOCAL_MODULE` | 模塊名,需保證在整個編譯系統中唯一的存在,且中間不可以有空格 | | | `LOCAL_MODULE_TAGS` | 代表當前工程(Android.mk 文件所在的目錄),在哪個模式下被編譯 | 與 **全域參數 `TARGET_BUILD_VARIANT`** 的設置有關 | | `LOCAL_STATIC_LIBRARIES` | 編譯所需的靜態厙列表 | | | `BUILD_HOST_EXTCUTEABLE` | 編譯模塊 | | | `BUILD_SHARED_LIBRARY` | 編譯成 so 文件 | 可執行文件的建立路徑則是 `<Android source>/out/target/product/generic/system/lib/<驅動名>.so` | | `BUILD_EXTCUTEABLE` | 編譯為可執行模塊 | 可執行文件的建立路徑則是 `<Android source>/out/target/product/generic/system/bin/<驅動名>` | ### Adb 中的 [Android.mk](https://android.googlesource.com/platform/system/core/+/android-4.4_r1/adb/Android.mk) * Adb 的源碼路徑在 `system/core/adb` 中,以下先來看看該資料夾中的 Android.mk 檔案,注意註解部份 ADB 編譯主要分為了 ^1^ **ADB Host**、^2^ **ADBD** (ADB Daemon) ```shell= ## Android.mk for adb LOCAL_PATH:= $(call my-dir) # 清除 LOCAL_PATH 以外的上百個變量 (var) include $(CLEAR_VARS) # 特別將這兩個變量提出來,因為在每個系統中,USB 的驅動都不同 USB_SRCS := EXTRA_SRCS := # 假設目前在 Linux 系統中(其他系統就自己看囉~) ifeq ($(HOST_OS),linux) USB_SRCS := usb_linux.c # Linux 專用的 usb 驅動 (usb_linux.c) EXTRA_SRCS := get_my_path_linux.c LOCAL_LDLIBS += -lrt -ldl -lpthread LOCAL_CFLAGS += -DWORKAROUND_BUG6558362 endif ...省略部份 # LOCAL_SRC_FILES 是相當重要的變量,它定義了該模塊 ++ 編譯所涉及的所有原文件 ++ # 可以看到 EXTRA_SRCS、EXTRA_SRCS 也被加入了變量中 LOCAL_SRC_FILES := \ adb.c \ console.c \ transport.c \ transport_local.c \ transport_usb.c \ commandline.c \ adb_client.c \ adb_auth_host.c \ sockets.c \ services.c \ file_sync_client.c \ $(EXTRA_SRCS) \ $(EXTRA_SRCS) \ usb_vendors.c # 根據狀況拓展 LOCAL_SRC_FILES 變量 ifneq ($(USE_SYSDEPS_WIN32),) # 32 位元系統 LOCAL_SRC_FILES += sysdeps_win32.c else LOCAL_SRC_FILES += fdevent.c endif # 添加編譯標誌,在編譯過程中會起作用 LOCAL_CFLAGS += -O2 -g -DADB_HOST=1 -Wall -Wno-unused-parameter LOCAL_CFLAGS += -D_XOPEN_SOURCE -D_GNU_SOURCE # 生成模塊的名稱 LOCAL_MODULE := adb # 編譯過程中需要的厙 LOCAL_STATIC_LIBRARIES := libzipfile libunz libcrypto_static $(EXTRA_STATIC_LIBS) ifeq ($(USE_SYSDEPS_WIN32),) LOCAL_STATIC_LIBRARIES += libcutils # 生成一個 Host 可執行程序 include $(BUILD_HOST_EXECUTABLE) ########################################################################### # adbd device daemon # ========================================================= # 第二個模塊編譯開始的標誌 include $(CLEAR_VARS) # 與上面的第一個模塊部份重複 LOCAL_SRC_FILES := \ adb.c \ backup_service.c \ fdevent.c \ transport.c \ transport_local.c \ transport_usb.c \ adb_auth_client.c \ sockets.c \ services.c \ file_sync_service.c \ jdwp_service.c \ framebuffer_service.c \ remount_service.c \ usb_linux_client.c \ log_service.c # 添加編譯標誌位 LOCAL_CFLAGS := -O2 -g -DADB_HOST=0 -Wall -Wno-unused-parameter LOCAL_CFLAGS += -D_XOPEN_SOURCE -D_GNU_SOURCE # 模塊名稱 LOCAL_MODULE := adbd # 編譯時使用到的厙 LOCAL_STATIC_LIBRARIES := liblog libcutils libc libmincrypt # 生成一個可執行程序 include $(BUILD_EXECUTABLE) ``` :::info * CLEAR_VARS 定義在 build/core/clear [**vars.mk**](https://android.googlesource.com/platform/ndk/+/froyo/build/core/clear-vars.mk) 中,清除了 LOCAL_PATH 以外的上百個變量 ::: ## Jack ToolChain **從 Android 6.0 版本開始**,Android 編譯系統中有了較大的變化,**可以使用新的 Java 編譯鏈(Java Android Compiler Kit)** **Jack 的主要任務是取代以前的 javac、proguard、jarjar、dex 等等的諸多工具,以全新的方式將 Java 原文件編譯程 Android 的 Dex 字節碼** :::info 並請內建 shrinking、obfuscation、repackaging、multidex 並完全開源 ::: ### JILL * Jack 有自己的文件格式,這就不可避免的需要利用一個句將它與傳統的 .jar 文件進行轉換,也就是 JILL(Jack Intermediate Library Linker),如下圖 > ![](https://i.imgur.com/e4qCzVQ.png) * 編譯出來的結果並不會有中間的 .jar 文件,會 **直接得到 dex 檔案** ## Appendix & FAQ :::warning * Android Kernel 編譯 [**參考**](https://www.owalle.com/2020/05/11/android-emulator/) * [Android 4 編譯出錯時的解決方案](https://blog.csdn.net/zhangwenhaojf40it/article/details/78110106)、[**Android 4 常見錯誤**](https://jzq84229.github.io/2016/05/18/android-source-make-errors.html)、[**-lGL 錯誤**](https://stackoverflow.com/questions/45556184/how-to-solve-usr-bin-ld-cannot-find-lgl) > ... Android 4 問題滿多的,要多去 google > > 建議使用 [**gcc 4.4、g++ 4.4**](https://blog.csdn.net/zyfzhangyafei/article/details/90410390) * Root node - droid:Makefile 內沒有書上的內容 ::: ###### tags: `Android 系統` `Jack Tool`