---
# System prepended metadata

title: Android 下載源碼、編譯系統
tags: [Jack Tool, Android 系統]

---

---
title: 'Android 下載源碼、編譯系統'
disqus: kyleAlien
---

Android 下載源碼、編譯系統
===

## OverView of Content

如有引用該文章請標明引用，謝謝 :smile_cat: 

以下是在 Ubuntu20 LTS 環境下進行

[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 關鍵字就是開始下載；如果下載中途發生異常，仍可以使用 `repo sync` 再次同步繼續下載

    ```shell=
    repo sync
    
    ## 如果想加速則可以指定 -j (線程使用數量)、-c、-q (安靜模式)
    repo sync -c -j12 ## 使用 12 核下載
    
    
    ## FIX SYNC ERROR CMD
    # find .repo -name "*.lock" -delete
    # repo sync -c -j1 --force-sync --no-tags
    ```

    下載完後接近 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`
